Skip to content

Commit

Permalink
Add a socket_service definition
Browse files Browse the repository at this point in the history
For systemd socket activated services it's needed to have both the
.socket and .service unit defintions in place when starting the service.
Prior to 97dd16f the systemctl
daemon-reload took care of it, but now this must be done explicitly.

This new defined type makes it easy and reduces the burden on modules
that define the unit/service pair.
  • Loading branch information
ekohl committed Jan 26, 2023
1 parent 40a0f77 commit 29473c6
Show file tree
Hide file tree
Showing 3 changed files with 337 additions and 0 deletions.
57 changes: 57 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1333,6 +1334,62 @@ Restart the managed service after setting the limits

Default value: `true`

### <a name="systemd--socket_service"></a>`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)

##### <a name="-systemd--socket_service--name"></a>`name`

Data type: `Pattern['^[^/]+$']`

The target unit file to create

##### <a name="-systemd--socket_service--ensure"></a>`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'`

##### <a name="-systemd--socket_service--socket_content"></a>`socket_content`

Data type: `Optional[String[1]]`

The content for the socket unit file. Required if ensure isn't absent.

Default value: `undef`

##### <a name="-systemd--socket_service--service_content"></a>`service_content`

Data type: `Optional[String[1]]`

The content for the service unit file. Required if ensure isn't absent.

Default value: `undef`

##### <a name="-systemd--socket_service--enable"></a>`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`

### <a name="systemd--timer"></a>`systemd::timer`

Create a timer and optionally a service unit to execute with the timer unit
Expand Down
69 changes: 69 additions & 0 deletions manifests/socket_service.pp
Original file line number Diff line number Diff line change
@@ -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"]
}
}
211 changes: 211 additions & 0 deletions spec/defines/socket_service_spec.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 29473c6

Please sign in to comment.