diff --git a/REFERENCE.md b/REFERENCE.md index a2e51bb9..e78c9249 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -40,6 +40,7 @@ * [`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 +* [`systemd::user_service`](#systemd--user_service): Manage a user service running under systemd --user ### Resource types @@ -2251,6 +2252,104 @@ restart (notify) the service when unit file changed Default value: `true` +### `systemd::user_service` + +Manage a user service running under systemd --user + +#### Examples + +##### Enable a service for all users + +```puppet +systemd::user_service { 'systemd-tmpfiles-clean.timer': + enable => true, + global => true, +} +``` + +##### Enable a particular user's service + +```puppet +systemd::user_service { 'podman-auto-update.timer': + ensure => true, + enable => true, + user => 'steve', +} +``` + +##### Notify a user's service to restart it + +```puppet +file{ '/home/steve/.config/systemd/user/podman-auto-update.timer': + ensure => file, + content => ..., + notify => Systemd::User_service['steve-podman-auto-update.timer'] +} + +systemd::user_service { 'steve-podman-auto-update.timer': + ensure => true, + enable => true, + unit => 'podman-auto-update.timer', + user => 'steve', +} + +@param unit Unit name to work on +@param ensure Should the unit be started or stopped. Can only be true if user is specified. +@param enable Should the unit be enabled or disabled +@param user User name of user whose unit should be acted upon. Mutually exclusive with +@param global Act globally for all users. Mutually exclusibe with `user`. +``` + +#### Parameters + +The following parameters are available in the `systemd::user_service` defined type: + +* [`unit`](#-systemd--user_service--unit) +* [`ensure`](#-systemd--user_service--ensure) +* [`enable`](#-systemd--user_service--enable) +* [`global`](#-systemd--user_service--global) +* [`user`](#-systemd--user_service--user) + +##### `unit` + +Data type: `Systemd::Unit` + + + +Default value: `$title` + +##### `ensure` + +Data type: `Variant[Boolean,Enum['stopped','running']]` + + + +Default value: `false` + +##### `enable` + +Data type: `Boolean` + + + +Default value: `false` + +##### `global` + +Data type: `Boolean` + + + +Default value: `false` + +##### `user` + +Data type: `Optional[String[1]]` + + + +Default value: `undef` + ## Resource types ### `loginctl_user` diff --git a/manifests/user_service.pp b/manifests/user_service.pp new file mode 100644 index 00000000..b1fc930f --- /dev/null +++ b/manifests/user_service.pp @@ -0,0 +1,136 @@ +# @summary Manage a user service running under systemd --user +# +# @example Enable a service for all users +# systemd::user_service { 'systemd-tmpfiles-clean.timer': +# enable => true, +# global => true, +# } +# +# @example Enable a particular user's service +# systemd::user_service { 'podman-auto-update.timer': +# ensure => true, +# enable => true, +# user => 'steve', +# } +# +# @example Notify a user's service to restart it +# file{ '/home/steve/.config/systemd/user/podman-auto-update.timer': +# ensure => file, +# content => ..., +# notify => Systemd::User_service['steve-podman-auto-update.timer'] +# } +# +# systemd::user_service { 'steve-podman-auto-update.timer': +# ensure => true, +# enable => true, +# unit => 'podman-auto-update.timer', +# user => 'steve', +# } +# +# @param unit Unit name to work on +# @param ensure Should the unit be started or stopped. Can only be true if user is specified. +# @param enable Should the unit be enabled or disabled +# @param user User name of user whose unit should be acted upon. Mutually exclusive with +# @param global Act globally for all users. Mutually exclusibe with `user`. +# +define systemd::user_service ( + Systemd::Unit $unit = $title, + Variant[Boolean,Enum['stopped','running']] $ensure = false, + Boolean $enable = false, + Boolean $global = false, + Optional[String[1]] $user = undef, +) { + $_ensure = $ensure ? { + 'stopped' => false, + 'running' => true, + default => $ensure + } + + if ( $global and $user ) or ( ! $global and ! $user) { + fail('Exactly one of the "user" or "global" parameters must be defined') + } + + if $global and $_ensure { + fail('Cannot ensure a service is running for all users globally') + } + + if $global { + if $enable { + $_title = "Enable user service ${unit} globally" + $_command = ['systemctl', '--global', 'enable', $unit] + $_unless = [['systemctl', '--global', 'is-enabled', $unit]] + $_onlyif = undef + } else { + $_title = "Disable user service ${unit} globally" + $_command = ['systemctl', '--global', 'disable', $unit] + $_unless = undef + $_onlyif = [['systemctl', '--global', 'is-enabled', $unit]] + } + exec { $_title: + command => $_command, + unless => $_unless, + onlyif => $_onlyif, + path => $facts['path'], + } + } else { # per user services + + $_systemctl_user = [ + 'systemd-run', '--pipe', '--wait', '--user', '--machine', "${user}@.host", + 'systemctl', '--user', + ] + + # To accept notifies of this type. + exec { "try-reload-or-restart-${user}-${unit}": + command => $_systemctl_user + ['try-reload-or-restart', $unit], + refreshonly => true, + path => $facts['path'], + } + + if $_ensure { + $_ensure_title = "Start user service ${unit} for user ${user}" + $_ensure_command = $_systemctl_user + ['start', $unit] + $_ensure_unless = [$_systemctl_user + ['is-active', $unit]] + $_ensure_onlyif = undef + + # Don't reload just after starting + Exec["try-reload-or-restart-${user}-${unit}"] -> Exec[$_ensure_title] + } else { + $_ensure_title = "Stop user service ${unit} for user ${user}" + $_ensure_command = $_systemctl_user + ['stop', $unit] + $_ensure_unless = undef + $_ensure_onlyif = [$_systemctl_user + ['is-active', $unit]] + } + + exec { $_ensure_title: + command => $_ensure_command, + unless => $_ensure_unless, + onlyif => $_ensure_onlyif, + path => $facts['path'], + } + + if $enable { + $_enable_title = "Enable user service ${unit} for user ${user}" + $_enable_command = $_systemctl_user + ['enable', $unit] + $_enable_unless = [$_systemctl_user + ['is-enabled', $unit]] + $_enable_onlyif = undef + + # Puppet does this for services so lets copy that logic + # don't enable if you can't start. + if $_ensure { + Exec[$_ensure_title] -> Exec[$_enable_title] + } + } else { + $_enable_title = "Disable user service ${unit} for user ${user}" + $_enable_command = $_systemctl_user + ['disable', $unit] + $_enable_unless = undef + $_enable_onlyif = [$_systemctl_user + ['is-enabled', $unit]] + } + + exec { $_enable_title: + command => $_enable_command, + unless => $_enable_unless, + onlyif => $_enable_onlyif, + path => $facts['path'], + } + } +} diff --git a/metadata.json b/metadata.json index 9e1f8837..edde66d2 100644 --- a/metadata.json +++ b/metadata.json @@ -100,7 +100,7 @@ "requirements": [ { "name": "puppet", - "version_requirement": ">= 7.0.0 < 9.0.0" + "version_requirement": ">= 7.9.0 < 9.0.0" } ] } diff --git a/spec/defines/user_service_spec.rb b/spec/defines/user_service_spec.rb new file mode 100644 index 00000000..81e2f70c --- /dev/null +++ b/spec/defines/user_service_spec.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'systemd::user_service' do + context 'supported operating systems' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { facts } + let(:title) { 'mine.timer' } + + context 'with defaults' do + it { is_expected.to compile.and_raise_error(%r{"user" or "global"}) } + end + + context 'with user and global set' do + let(:params) do + { global: true, user: 'steve' } + end + + it { is_expected.to compile.and_raise_error(%r{"user" or "global"}) } + end + + context 'with global and ensure' do + let(:params) do + { global: true, ensure: 'running' } + end + + it { is_expected.to compile.and_raise_error(%r{Cannot ensure a service is running for all users globally}) } + end + + context 'with global enable' do + let(:params) do + { global: true, enable: true } + end + + it { + is_expected.to contain_exec('Enable user service mine.timer globally') + } + + it { + is_expected.to contain_exec('Enable user service mine.timer globally'). + with_command(['systemctl', '--global', 'enable', 'mine.timer']). + with_unless([['systemctl', '--global', 'is-enabled', 'mine.timer']]). + without_onlyif + } + end + + context 'with global disable' do + let(:params) do + { global: true, enable: false } + end + + it { + is_expected.to contain_exec('Disable user service mine.timer globally'). + with_command(['systemctl', '--global', 'disable', 'mine.timer']). + without_unless. + with_onlyif([['systemctl', '--global', 'is-enabled', 'mine.timer']]) + } + end + + context 'with a user specified' do + let(:params) do + { user: 'steve' } + end + + it { is_expected.to contain_exec('try-reload-or-restart-steve-mine.timer') } + + context 'with enable and ensure false' do + let(:params) do + super().merge(enable: false, ensure: 'stopped') + end + + it { + is_expected.to contain_exec('Stop user service mine.timer for user steve'). + with_command([ + 'systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', + 'systemctl', '--user', 'stop', 'mine.timer', + ]). + with_onlyif([[ + 'systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', + 'systemctl', '--user', 'is-active', 'mine.timer', + ]]). + without_unless + } + + it { + is_expected.to contain_exec('Disable user service mine.timer for user steve'). + with_command([ + 'systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', + 'systemctl', '--user', 'disable', 'mine.timer', + ]). + with_onlyif([[ + 'systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', + 'systemctl', '--user', 'is-enabled', 'mine.timer', + ]]). + without_unless + } + end + + context 'with enable and ensure true' do + let(:params) do + super().merge(enable: true, ensure: 'running') + end + + it { + is_expected.to contain_exec('Start user service mine.timer for user steve'). + with_command([ + 'systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', + 'systemctl', '--user', 'start', 'mine.timer', + ]). + without_onlyif. + with_unless([[ + 'systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', + 'systemctl', '--user', 'is-active', 'mine.timer', + ]]) + } + + it { + is_expected.to contain_exec('Enable user service mine.timer for user steve'). + with_command([ + 'systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', + 'systemctl', '--user', 'enable', 'mine.timer', + ]). + without_onlif. + with_unless([[ + 'systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', + 'systemctl', '--user', 'is-enabled', 'mine.timer', + ]]) + } + end + + context 'with enable true and ensure false' do + let(:params) do + super().merge(enable: true, ensure: false) + end + + it { is_expected.to contain_exec('Stop user service mine.timer for user steve') } + it { is_expected.to contain_exec('Enable user service mine.timer for user steve') } + end + + context 'with enable false and ensure true' do + let(:params) do + super().merge(enable: false, ensure: true) + end + + it { is_expected.to contain_exec('Start user service mine.timer for user steve') } + it { is_expected.to contain_exec('Disable user service mine.timer for user steve') } + end + end + end + end + end +end