diff --git a/manifests/socket_service.pp b/manifests/socket_service.pp new file mode 100644 index 00000000..a772453f --- /dev/null +++ b/manifests/socket_service.pp @@ -0,0 +1,61 @@ +# @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 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, + } + $real_enable = pick_default($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 or $real_enable { + # 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..4863c0da --- /dev/null +++ b/spec/defines/socket_service_spec.rb @@ -0,0 +1,187 @@ +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