diff --git a/REFERENCE.md b/REFERENCE.md
index d38a01ba..26f459fa 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -36,6 +36,7 @@
* [`systemd::network`](#systemd--network): Creates network config for systemd-networkd
* [`systemd::service_limits`](#systemd--service_limits): Adds a set of custom limits to the service
* [`systemd::timer`](#systemd--timer): Create a timer and optionally a service unit to execute with the timer unit
+* [`systemd::timer_wrapper`](#systemd--timer_wrapper): Helper to define timer and accompanying services for a given task (cron like interface).
* [`systemd::tmpfile`](#systemd--tmpfile): Creates a systemd tmpfile
* [`systemd::udev::rule`](#systemd--udev--rule): Adds a custom udev rule
* [`systemd::unit_file`](#systemd--unit_file): Creates a systemd unit file
@@ -67,6 +68,7 @@
* [`Systemd::Unit::Service::Exec`](#Systemd--Unit--Service--Exec): Possible strings for ExecStart, ExecStartPrep, ...
* [`Systemd::Unit::Socket`](#Systemd--Unit--Socket): Possible keys for the [Socket] section of a unit file
* [`Systemd::Unit::Timer`](#Systemd--Unit--Timer): Possible keys for the [Timer] section of a unit file
+* [`Systemd::Unit::Timer::TimerSpec`](#Systemd--Unit--Timer--TimerSpec): Timer specification for systemd timers
* [`Systemd::Unit::Unit`](#Systemd--Unit--Unit): Possible keys for the [Unit] section of a unit file
## Classes
@@ -1677,6 +1679,166 @@ Call `systemd::daemon_reload`
Default value: `true`
+### `systemd::timer_wrapper`
+
+Helper to define timer and accompanying services for a given task (cron like interface).
+
+#### Examples
+
+##### Create a timer that runs every 5 minutes
+
+```puppet
+systemd::timer_wrapper { 'my_timer':
+ ensure => 'present',
+ command => '/usr/bin/echo "Hello World"',
+ on_calendar => '*:0/5',
+}
+```
+
+##### Create a timer with overrides for the service and timer
+
+```puppet
+systemd::timer_wrapper { 'my_timer':
+ ensure => 'present',
+ command => '/usr/bin/echo "Hello World"',
+ on_calendar => '*:0/5',
+ service_overrides => { 'Group' => 'nobody' },
+ timer_overrides => { 'OnBootSec' => '10' },
+}
+```
+
+##### Create a timer with overrides for the service_unit and timer_unit
+
+```puppet
+systemd::timer_wrapper { 'my_timer':
+ ensure => 'present',
+ command => '/usr/bin/echo "Hello World"',
+ on_calendar => '*:0/5',
+ service_unit_overrides => { 'Wants' => 'network-online.target' },
+ timer_unit_overrides => { 'Description' => 'Very special timer' },
+}
+```
+
+#### Parameters
+
+The following parameters are available in the `systemd::timer_wrapper` defined type:
+
+* [`ensure`](#-systemd--timer_wrapper--ensure)
+* [`command`](#-systemd--timer_wrapper--command)
+* [`user`](#-systemd--timer_wrapper--user)
+* [`on_active_sec`](#-systemd--timer_wrapper--on_active_sec)
+* [`on_boot_sec`](#-systemd--timer_wrapper--on_boot_sec)
+* [`on_start_up_sec`](#-systemd--timer_wrapper--on_start_up_sec)
+* [`on_unit_active_sec`](#-systemd--timer_wrapper--on_unit_active_sec)
+* [`on_unit_inactive_sec`](#-systemd--timer_wrapper--on_unit_inactive_sec)
+* [`on_calendar`](#-systemd--timer_wrapper--on_calendar)
+* [`service_overrides`](#-systemd--timer_wrapper--service_overrides)
+* [`timer_overrides`](#-systemd--timer_wrapper--timer_overrides)
+* [`service_unit_overrides`](#-systemd--timer_wrapper--service_unit_overrides)
+* [`timer_unit_overrides`](#-systemd--timer_wrapper--timer_unit_overrides)
+
+##### `ensure`
+
+Data type: `Enum['present', 'absent']`
+
+whether the timer and service should be present or absent
+
+##### `command`
+
+Data type: `Optional[Systemd::Unit::Service::Exec]`
+
+the command for the systemd servie to execute
+
+Default value: `undef`
+
+##### `user`
+
+Data type: `Optional[String[1]]`
+
+the user to run the command as
+
+Default value: `undef`
+
+##### `on_active_sec`
+
+Data type: `Optional[Systemd::Unit::Timer::TimerSpec]`
+
+run service relative to the time when the timer was activated
+
+Default value: `undef`
+
+##### `on_boot_sec`
+
+Data type: `Optional[Systemd::Unit::Timer::TimerSpec]`
+
+run service relative to when the machine was booted
+
+Default value: `undef`
+
+##### `on_start_up_sec`
+
+Data type: `Optional[Systemd::Unit::Timer::TimerSpec]`
+
+run service relative to when the service manager was started
+
+Default value: `undef`
+
+##### `on_unit_active_sec`
+
+Data type: `Optional[Systemd::Unit::Timer::TimerSpec]`
+
+run service relative to when the unit was last activated
+
+Default value: `undef`
+
+##### `on_unit_inactive_sec`
+
+Data type: `Optional[Systemd::Unit::Timer::TimerSpec]`
+
+run service relative to when the unit was last deactivated
+
+Default value: `undef`
+
+##### `on_calendar`
+
+Data type: `Optional[Systemd::Unit::Timer::TimerSpec]`
+
+the calendar event expressions time to run the service
+
+Default value: `undef`
+
+##### `service_overrides`
+
+Data type: `Systemd::Unit::Service`
+
+override for the`[Service]` section of the service
+
+Default value: `{}`
+
+##### `timer_overrides`
+
+Data type: `Systemd::Unit::Timer`
+
+override for the`[Timer]` section of the timer
+
+Default value: `{}`
+
+##### `service_unit_overrides`
+
+Data type: `Systemd::Unit::Unit`
+
+override for the`[Unit]` section of the service
+
+Default value: `{}`
+
+##### `timer_unit_overrides`
+
+Data type: `Systemd::Unit::Unit`
+
+override for the `[Unit]` section of the timer
+
+Default value: `{}`
+
### `systemd::tmpfile`
Creates a systemd tmpfile
@@ -2578,12 +2740,12 @@ Alias of
```puppet
Struct[{
- Optional['OnActiveSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnBootSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnStartUpSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnUnitActiveSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnUnitInactiveSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnCalendar'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
+ Optional['OnActiveSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnBootSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnStartUpSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnUnitActiveSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnUnitInactiveSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnCalendar'] => Systemd::Unit::Timer::TimerSpec,
Optional['AccuracySec'] => Variant[Integer[0],String],
Optional['RandomizedDelaySec'] => Variant[Integer[0],String],
Optional['FixedRandomDelay'] => Boolean,
@@ -2596,6 +2758,15 @@ Struct[{
}]
```
+### `Systemd::Unit::Timer::TimerSpec`
+
+Timer specification for systemd timers
+
+* **See also**
+ * https://www.freedesktop.org/software/systemd/man/systemd.timer.html
+
+Alias of `Variant[Integer[0], String, Array[Variant[Integer[0],String]]]`
+
### `Systemd::Unit::Unit`
Possible keys for the [Unit] section of a unit file
diff --git a/manifests/timer_wrapper.pp b/manifests/timer_wrapper.pp
new file mode 100644
index 00000000..62dadf64
--- /dev/null
+++ b/manifests/timer_wrapper.pp
@@ -0,0 +1,113 @@
+# @summary
+# Helper to define timer and accompanying services for a given task (cron like interface).
+# @param ensure whether the timer and service should be present or absent
+# @param command the command for the systemd servie to execute
+# @param user the user to run the command as
+# @param on_active_sec run service relative to the time when the timer was activated
+# @param on_boot_sec run service relative to when the machine was booted
+# @param on_start_up_sec run service relative to when the service manager was started
+# @param on_unit_active_sec run service relative to when the unit was last activated
+# @param on_unit_inactive_sec run service relative to when the unit was last deactivated
+# @param on_calendar the calendar event expressions time to run the service
+# @param service_overrides override for the`[Service]` section of the service
+# @param timer_overrides override for the`[Timer]` section of the timer
+# @param service_unit_overrides override for the`[Unit]` section of the service
+# @param timer_unit_overrides override for the `[Unit]` section of the timer
+# @example Create a timer that runs every 5 minutes
+# systemd::timer_wrapper { 'my_timer':
+# ensure => 'present',
+# command => '/usr/bin/echo "Hello World"',
+# on_calendar => '*:0/5',
+# }
+# @example Create a timer with overrides for the service and timer
+# systemd::timer_wrapper { 'my_timer':
+# ensure => 'present',
+# command => '/usr/bin/echo "Hello World"',
+# on_calendar => '*:0/5',
+# service_overrides => { 'Group' => 'nobody' },
+# timer_overrides => { 'OnBootSec' => '10' },
+# }
+# @example Create a timer with overrides for the service_unit and timer_unit
+# systemd::timer_wrapper { 'my_timer':
+# ensure => 'present',
+# command => '/usr/bin/echo "Hello World"',
+# on_calendar => '*:0/5',
+# service_unit_overrides => { 'Wants' => 'network-online.target' },
+# timer_unit_overrides => { 'Description' => 'Very special timer' },
+# }
+define systemd::timer_wrapper (
+ Enum['present', 'absent'] $ensure,
+ Optional[Systemd::Unit::Service::Exec] $command = undef,
+ Optional[String[1]] $user = undef,
+ Optional[Systemd::Unit::Timer::TimerSpec] $on_active_sec = undef,
+ Optional[Systemd::Unit::Timer::TimerSpec] $on_boot_sec = undef,
+ Optional[Systemd::Unit::Timer::TimerSpec] $on_start_up_sec = undef,
+ Optional[Systemd::Unit::Timer::TimerSpec] $on_unit_active_sec = undef,
+ Optional[Systemd::Unit::Timer::TimerSpec] $on_unit_inactive_sec = undef,
+ Optional[Systemd::Unit::Timer::TimerSpec] $on_calendar = undef,
+ Systemd::Unit::Service $service_overrides = {},
+ Systemd::Unit::Timer $timer_overrides = {},
+ Systemd::Unit::Unit $timer_unit_overrides = {},
+ Systemd::Unit::Unit $service_unit_overrides = {},
+) {
+ $_timer_spec = {
+ 'OnActiveSec' => $on_active_sec,
+ 'OnBootSec' => $on_boot_sec,
+ 'OnStartUpSec' => $on_start_up_sec,
+ 'OnUnitActiveSec' => $on_unit_active_sec,
+ 'OnUnitInactiveSec' => $on_unit_inactive_sec,
+ 'OnCalendar' => $on_calendar,
+ }.filter |$k, $v| { $v =~ NotUndef }
+
+ if $ensure == 'present' {
+ if $_timer_spec == {} {
+ fail('At least one of on_active_sec,
+ on_boot_sec,
+ on_start_up_sec,
+ on_unit_active_sec,
+ on_unit_inactive_sec,
+ or on_calendar must be set'
+ )
+ }
+ if ! $command {
+ fail('command must be set')
+ }
+ }
+
+ $service_ensure = $ensure ? { 'absent' => false, default => true, }
+ $unit_name = systemd::escape($title)
+
+ systemd::manage_unit { "${unit_name}.service":
+ ensure => $ensure,
+ unit_entry => $service_unit_overrides,
+ service_entry => {
+ 'ExecStart' => $command, # if ensure present command is defined is checked above
+ 'User' => $user, # defaults apply
+ 'Type' => 'oneshot',
+ }.filter |$key, $val| { $val =~ NotUndef } + $service_overrides,
+ }
+ systemd::manage_unit { "${unit_name}.timer":
+ ensure => $ensure,
+ unit_entry => $timer_unit_overrides,
+ timer_entry => $_timer_spec + $timer_overrides,
+ install_entry => {
+ 'WantedBy' => 'timers.target',
+ },
+ }
+
+ service { "${unit_name}.timer":
+ ensure => $service_ensure,
+ enable => $service_ensure,
+ }
+
+ if $ensure == 'present' {
+ Systemd::Manage_unit["${unit_name}.service"]
+ -> Systemd::Manage_unit["${unit_name}.timer"]
+ -> Service["${unit_name}.timer"]
+ } else {
+ # Ensure the timer is stopped and disabled before the service
+ Service["${unit_name}.timer"]
+ -> Systemd::Manage_unit["${unit_name}.timer"]
+ -> Systemd::Manage_unit["${unit_name}.service"]
+ }
+}
diff --git a/spec/defines/timer_wrapper_spec.rb b/spec/defines/timer_wrapper_spec.rb
new file mode 100644
index 00000000..b6a8e577
--- /dev/null
+++ b/spec/defines/timer_wrapper_spec.rb
@@ -0,0 +1,182 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'systemd::timer_wrapper' do
+ context 'supported operating systems' do
+ on_supported_os.each do |os, facts|
+ context "on #{os}" do
+ let(:facts) { facts }
+ let(:title) { 'timer_name' }
+
+ context 'simple usage' do
+ let :params do
+ {
+ ensure: 'present',
+ on_calendar: '*:0/10',
+ command: '/bin/date',
+ user: 'root',
+ }
+ end
+
+ it do
+ is_expected.to compile.with_all_deps
+ is_expected.to contain_file("/etc/systemd/system/#{title}.service").
+ with_content(%r{# Deployed with puppet}).
+ with_content(%r{Type=oneshot}).
+ with_content(%r{ExecStart=/bin/date}).
+ with_content(%r{Type=oneshot}).
+ with_content(%r{User=root})
+ is_expected.to contain_file("/etc/systemd/system/#{title}.timer").
+ with_content(%r{OnCalendar=\*:0/10}).
+ with_content(%r{WantedBy=timers.target})
+ is_expected.to contain_Systemd__Unit_file("#{title}.service").
+ that_comes_before("Systemd::Unit_file[#{title}.timer]")
+ is_expected.to contain_Systemd__Unit_file("#{title}.service").
+ that_comes_before("Systemd::Unit_file[#{title}.timer]")
+ is_expected.to contain_Exec("systemd-#{title}.service-systemctl-daemon-reload")
+ is_expected.to contain_Exec("systemd-#{title}.timer-systemctl-daemon-reload")
+ is_expected.to contain_Service("#{title}.timer")
+ is_expected.to contain_Systemd__Daemon_reload("#{title}.service")
+ is_expected.to contain_Systemd__Daemon_reload("#{title}.timer")
+ is_expected.to contain_Systemd__Manage_unit("#{title}.service")
+ is_expected.to contain_Systemd__Manage_unit("#{title}.timer")
+ is_expected.to contain_Systemd__Unit_file("#{title}.timer")
+ end
+ end
+
+ context 'failue when not passing calendar spec' do
+ let :params do
+ {
+ ensure: 'present',
+ command: '/bin/date',
+ user: 'root',
+ }
+ end
+
+ it do
+ is_expected.to compile.and_raise_error(%r{At least one of on_active_sec})
+ end
+ end
+
+ context 'with / in title' do
+ let :title do
+ 't/i/t/l/e'
+ end
+ let :params do
+ {
+ ensure: 'present',
+ on_calendar: '*:0/10',
+ command: '/bin/true',
+ user: 'root',
+ }
+ end
+
+ it {
+ is_expected.to compile
+ is_expected.to contain_file('/etc/systemd/system/t-i-t-l-e.timer')
+ is_expected.to contain_systemd__manage_unit('t-i-t-l-e.timer')
+ is_expected.to contain_systemd__unit_file('t-i-t-l-e.timer')
+ is_expected.to contain_file('/etc/systemd/system/t-i-t-l-e.service')
+ is_expected.to contain_systemd__manage_unit('t-i-t-l-e.service')
+ is_expected.to contain_systemd__unit_file('t-i-t-l-e.service')
+ is_expected.to contain_service('t-i-t-l-e.timer')
+ is_expected.to contain_exec('systemd-t-i-t-l-e.service-systemctl-daemon-reload')
+ is_expected.to contain_exec('systemd-t-i-t-l-e.timer-systemctl-daemon-reload')
+ is_expected.to contain_Systemd__Daemon_reload('t-i-t-l-e.service')
+ is_expected.to contain_Systemd__Daemon_reload('t-i-t-l-e.timer')
+ }
+ end
+
+ context 'ensure absent' do
+ let :params do
+ {
+ ensure: 'absent',
+ }
+ end
+
+ it {
+ is_expected.to contain_Systemd__Manage_unit("#{title}.timer").
+ with_ensure('absent')
+ is_expected.to contain_Systemd__Manage_unit("#{title}.service").
+ with_ensure('absent')
+ is_expected.to contain_Service("#{title}.timer").
+ that_comes_before("Systemd::Unit_file[#{title}.timer]").
+ with_ensure(false)
+ is_expected.to contain_Systemd__Unit_file("#{title}.timer").
+ that_comes_before("Systemd::Unit_file[#{title}.service]").
+ with_ensure('absent')
+ }
+ end
+
+ context 'applies service_overrides' do
+ let :params do
+ {
+ ensure: 'present',
+ command: 'date',
+ service_overrides: { 'Group' => 'bob' },
+ on_boot_sec: 100,
+ user: 'root',
+ }
+ end
+
+ it {
+ is_expected.to contain_file("/etc/systemd/system/#{title}.service").
+ with_content(%r{Group=bob})
+ }
+ end
+
+ context 'applies service_unit_overrides' do
+ let :params do
+ {
+ ensure: 'present',
+ command: 'date',
+ service_unit_overrides: { 'Wants' => 'network-online.target' },
+ on_boot_sec: 100,
+ user: 'root',
+ }
+ end
+
+ it {
+ is_expected.to contain_file("/etc/systemd/system/#{title}.service").
+ with_content(%r{Wants=network-online.target})
+ }
+ end
+
+ context 'applies timer_overrides' do
+ let :params do
+ {
+ ensure: 'present',
+ command: 'date',
+ timer_overrides: { 'OnBootSec' => '200' },
+ on_boot_sec: 100,
+ user: 'root',
+ }
+ end
+
+ it {
+ is_expected.to contain_file("/etc/systemd/system/#{title}.timer").
+ with_content(%r{OnBootSec=200})
+ }
+ end
+
+ context 'applies timer_unit_overrides' do
+ let :params do
+ {
+ ensure: 'present',
+ command: 'date',
+ timer_unit_overrides: { 'Wants' => 'network-online.target' },
+ on_boot_sec: 100,
+ user: 'root',
+ }
+ end
+
+ it {
+ is_expected.to contain_file("/etc/systemd/system/#{title}.timer").
+ with_content(%r{Wants=network-online.target})
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/type_aliases/system_unit_timer_timerspec_spec.rb b/spec/type_aliases/system_unit_timer_timerspec_spec.rb
new file mode 100644
index 00000000..419a3cde
--- /dev/null
+++ b/spec/type_aliases/system_unit_timer_timerspec_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Systemd::Unit::Timer::TimerSpec' do
+ context 'with a permitted value' do
+ # permitted durations and as arrays as well
+ [
+ '',
+ 10,
+ '5m',
+ 'daily',
+ '5h 30min',
+ '24hours',
+ ['', 5, '50s', '5h 30min']
+ ].each do |value|
+ it { is_expected.to allow_value(value) }
+ end
+ end
+end
diff --git a/spec/type_aliases/systemd_unit_timer_spec.rb b/spec/type_aliases/systemd_unit_timer_spec.rb
index af50198e..c95646a4 100644
--- a/spec/type_aliases/systemd_unit_timer_spec.rb
+++ b/spec/type_aliases/systemd_unit_timer_spec.rb
@@ -3,26 +3,6 @@
require 'spec_helper'
describe 'Systemd::Unit::Timer' do
- # permitted durations and as arrays as well
- %w[OnActiveSec OnBootSec OnStartUpSec OnUnitActiveSec OnUnitInactiveSec].each do |assert|
- context "with a key of #{assert} can have values of units" do
- it { is_expected.to allow_value({ assert => '' }) }
- it { is_expected.to allow_value({ assert => 10 }) }
- it { is_expected.to allow_value({ assert => '5min' }) }
- it { is_expected.to allow_value({ assert => ['', 5, '50s', '5h 30min'] }) }
- end
- end
-
- %w[OnCalendar].each do |assert|
- context "with a key of #{assert} can have values of units" do
- it { is_expected.to allow_value({ assert => '' }) }
- it { is_expected.to allow_value({ assert => '24hours' }) }
- it { is_expected.to allow_value({ assert => 'daily' }) }
- it { is_expected.to allow_value({ assert => '1min 30s' }) }
- it { is_expected.to allow_value({ assert => ['', 'daily', '1min 30s'] }) }
- end
- end
-
# permitted single values
%w[AccuracySec RandomizedDelaySec].each do |assert|
context "with a key of #{assert} can have values of units" do
diff --git a/types/unit/timer.pp b/types/unit/timer.pp
index d3c927d8..e0412af1 100644
--- a/types/unit/timer.pp
+++ b/types/unit/timer.pp
@@ -3,12 +3,12 @@
#
type Systemd::Unit::Timer = Struct[
{
- Optional['OnActiveSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnBootSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnStartUpSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnUnitActiveSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnUnitInactiveSec'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
- Optional['OnCalendar'] => Variant[Integer[0],String,Array[Variant[Integer[0],String]]],
+ Optional['OnActiveSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnBootSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnStartUpSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnUnitActiveSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnUnitInactiveSec'] => Systemd::Unit::Timer::TimerSpec,
+ Optional['OnCalendar'] => Systemd::Unit::Timer::TimerSpec,
Optional['AccuracySec'] => Variant[Integer[0],String],
Optional['RandomizedDelaySec'] => Variant[Integer[0],String],
Optional['FixedRandomDelay'] => Boolean,
diff --git a/types/unit/timer/timerspec.pp b/types/unit/timer/timerspec.pp
new file mode 100644
index 00000000..3ea8fc04
--- /dev/null
+++ b/types/unit/timer/timerspec.pp
@@ -0,0 +1,4 @@
+# @summary Timer specification for systemd timers
+# @see https://www.freedesktop.org/software/systemd/man/systemd.timer.html
+#
+type Systemd::Unit::Timer::TimerSpec = Variant[Integer[0],String,Array[Variant[Integer[0],String]]]