diff --git a/REFERENCE.md b/REFERENCE.md index cff5ef1c..f2caa5ac 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -35,6 +35,7 @@ * [`systemd::modules_load`](#systemd--modules_load): Creates a modules-load.d drop file * [`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::socket_service`](#systemd--socket_service): Create a systemd socket activated service * [`systemd::timer`](#systemd--timer): Create a timer and optionally a service unit to execute with the timer unit * [`systemd::tmpfile`](#systemd--tmpfile): Creates a systemd tmpfile * [`systemd::udev::rule`](#systemd--udev--rule): Adds a custom udev rule @@ -1333,6 +1334,62 @@ Restart the managed service after setting the limits Default value: `true` +### `systemd::socket_service` + +Systemd socket activated services have their own dependencies. This is a +convenience wrapper around systemd::unit_file. + +#### Parameters + +The following parameters are available in the `systemd::socket_service` defined type: + +* [`name`](#-systemd--socket_service--name) +* [`ensure`](#-systemd--socket_service--ensure) +* [`socket_content`](#-systemd--socket_service--socket_content) +* [`service_content`](#-systemd--socket_service--service_content) +* [`enable`](#-systemd--socket_service--enable) + +##### `name` + +Data type: `Pattern['^[^/]+$']` + +The target unit file to create + +##### `ensure` + +Data type: `Enum['running', 'stopped', 'present', 'absent']` + +State of the socket service to ensure. Present means it ensures it's +present, but doesn't ensure the service state. + +Default value: `'running'` + +##### `socket_content` + +Data type: `Optional[String[1]]` + +The content for the socket unit file. Required if ensure isn't absent. + +Default value: `undef` + +##### `service_content` + +Data type: `Optional[String[1]]` + +The content for the service unit file. Required if ensure isn't absent. + +Default value: `undef` + +##### `enable` + +Data type: `Optional[Boolean]` + +Whether to enable or disable the service. By default this is derived from +$ensure but can be overridden for advanced use cases where the service is +running during a migration but shouldn't be enabled on boot. + +Default value: `undef` + ### `systemd::timer` Create a timer and optionally a service unit to execute with the timer unit diff --git a/manifests/socket_service.pp b/manifests/socket_service.pp new file mode 100644 index 00000000..4c6ff980 --- /dev/null +++ b/manifests/socket_service.pp @@ -0,0 +1,69 @@ +# @summary Create a systemd socket activated service +# @api public +# +# Systemd socket activated services have their own dependencies. This is a +# convenience wrapper around systemd::unit_file. +# +# @param name [Pattern['^[^/]+$']] +# The target unit file to create +# @param ensure +# State of the socket service to ensure. Present means it ensures it's +# present, but doesn't ensure the service state. +# @param socket_content +# The content for the socket unit file. Required if ensure isn't absent. +# @param service_content +# The content for the service unit file. Required if ensure isn't absent. +# @param enable +# Whether to enable or disable the service. By default this is derived from +# $ensure but can be overridden for advanced use cases where the service is +# running during a migration but shouldn't be enabled on boot. +define systemd::socket_service ( + Enum['running', 'stopped', 'present', 'absent'] $ensure = 'running', + Optional[String[1]] $socket_content = undef, + Optional[String[1]] $service_content = undef, + Optional[Boolean] $enable = undef, +) { + assert_type(Pattern['^[^/]+$'], $name) + + if $ensure != 'absent' { + assert_type(NotUndef, $socket_content) + assert_type(NotUndef, $service_content) + } + + $active = $ensure ? { + 'running' => true, + 'stopped' => false, + 'absent' => false, + default => undef, + } + # https://tickets.puppetlabs.com/browse/MODULES-11018 + if $enable == undef and $active == undef { + $real_enable = undef + } else { + $real_enable = pick($enable, $active) + } + + $unit_file_ensure = bool2str($ensure == 'absent', 'absent', 'present') + + systemd::unit_file { "${name}.socket": + ensure => $unit_file_ensure, + content => $socket_content, + active => $active, + enable => $real_enable, + } + + systemd::unit_file { "${name}.service": + ensure => $unit_file_ensure, + content => $service_content, + active => $active, + enable => $real_enable, + } + + if $active != undef or $real_enable != undef { + # Systemd needs both .socket and .service to be loaded when starting the + # service. The unit_file takes care of matching, this ensures the + # non-matching order. + File["/etc/systemd/system/${name}.socket"] -> Service["${name}.service"] + File["/etc/systemd/system/${name}.service"] -> Service["${name}.socket"] + } +} diff --git a/spec/defines/socket_service_spec.rb b/spec/defines/socket_service_spec.rb new file mode 100644 index 00000000..aa076c2e --- /dev/null +++ b/spec/defines/socket_service_spec.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'systemd::socket_service' do + let(:title) { 'myservice' } + + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts } + + context 'ensure => running' do + let(:params) do + { + ensure: 'running', + socket_content: "[Socket]\nListenStream=/run/myservice.socket\n", + service_content: "[Service]\nType=notify\n", + } + end + + it { is_expected.to compile.with_all_deps } + + it 'sets up the socket unit file' do + is_expected.to contain_file('/etc/systemd/system/myservice.socket'). + with_ensure('file'). + with_content(%r{\[Socket\]}). + that_comes_before(['Service[myservice.socket]', 'Service[myservice.service]']) + end + + it 'sets up the socket service' do + is_expected.to contain_service('myservice.socket'). + with_ensure(true). + with_enable(true) + end + + it 'sets up the service unit file' do + is_expected.to contain_file('/etc/systemd/system/myservice.service'). + with_ensure('file'). + with_content(%r{\[Service\]}). + that_comes_before('Service[myservice.service]') + end + + it 'sets up the service service' do + is_expected.to contain_service('myservice.service'). + with_ensure(true). + with_enable(true) + end + + context 'enable => false' do + let(:params) { super().merge(enable: false) } + + it { is_expected.to compile.with_all_deps } + + it 'sets up the socket service' do + is_expected.to contain_service('myservice.socket'). + with_ensure(true). + with_enable(false) + end + + it 'sets up the service service' do + is_expected.to contain_service('myservice.service'). + with_ensure(true). + with_enable(false) + end + end + end + + context 'ensure => stopped' do + let(:params) do + { + ensure: 'stopped', + socket_content: "[Socket]\nListenStream=/run/myservice.socket\n", + service_content: "[Service]\nType=notify\n", + } + end + + it { is_expected.to compile.with_all_deps } + + it 'sets up the socket unit file' do + is_expected.to contain_file('/etc/systemd/system/myservice.socket'). + with_ensure('file'). + with_content(%r{\[Socket\]}). + that_comes_before(['Service[myservice.socket]', 'Service[myservice.service]']) + end + + it 'sets up the socket service' do + is_expected.to contain_service('myservice.socket'). + with_ensure(false). + with_enable(false) + end + + it 'sets up the service unit file' do + is_expected.to contain_file('/etc/systemd/system/myservice.service'). + with_ensure('file'). + with_content(%r{\[Service\]}). + that_comes_before('Service[myservice.service]') + end + + it 'sets up the service service' do + is_expected.to contain_service('myservice.service'). + with_ensure(false). + with_enable(false) + end + + context 'enable => true' do + let(:params) { super().merge(enable: true) } + + it { is_expected.to compile.with_all_deps } + + it 'sets up the socket service' do + is_expected.to contain_service('myservice.socket'). + with_ensure(false). + with_enable(true) + end + + it 'sets up the service service' do + is_expected.to contain_service('myservice.service'). + with_ensure(false). + with_enable(true) + end + end + end + + context 'ensure => present' do + let(:params) do + { + ensure: 'present', + socket_content: "[Socket]\nListenStream=/run/myservice.socket\n", + service_content: "[Service]\nType=notify\n", + } + end + + it { is_expected.to compile.with_all_deps } + + it 'sets up the socket unit file' do + is_expected.to contain_file('/etc/systemd/system/myservice.socket'). + with_ensure('file'). + with_content(%r{\[Socket\]}) + end + + it "doesn't set up the socket service" do + is_expected.not_to contain_service('myservice.socket') + end + + it 'sets up the service unit file' do + is_expected.to contain_file('/etc/systemd/system/myservice.service'). + with_ensure('file'). + with_content(%r{\[Service\]}) + end + + it "doesn't set up the service service" do + is_expected.not_to contain_service('myservice.service') + end + + context 'enable => true' do + let(:params) { super().merge(enable: true) } + + it { is_expected.to compile.with_all_deps } + + it 'sets up the socket service' do + is_expected.to contain_service('myservice.socket'). + without_ensure. + with_enable(true) + end + + it 'sets up the service service' do + is_expected.to contain_service('myservice.service'). + without_ensure. + with_enable(true) + end + end + end + + context 'ensure => absent' do + let(:params) do + { + ensure: 'absent', + } + end + + it { is_expected.to compile.with_all_deps } + + it 'sets up the socket unit file' do + is_expected.to contain_file('/etc/systemd/system/myservice.socket'). + with_ensure('absent'). + without_content. + that_requires('Service[myservice.socket]') + end + + it 'sets up the socket service' do + is_expected.to contain_service('myservice.socket'). + with_ensure(false). + with_enable(false) + end + + it 'sets up the service unit file' do + is_expected.to contain_file('/etc/systemd/system/myservice.service'). + with_ensure('absent'). + without_content. + that_requires('Service[myservice.service]') + end + + it 'sets up the service service' do + is_expected.to contain_service('myservice.service'). + with_ensure(false). + with_enable(false) + end + end + end + end +end