diff --git a/.fixtures.yml b/.fixtures.yml index 1d455a31..b2e06ff2 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -1,4 +1,6 @@ --- fixtures: + repositories: + stdlib: https://github.com/puppetlabs/puppetlabs-stdlib symlinks: - systemd: "#{source_dir}" \ No newline at end of file + systemd: "#{source_dir}" diff --git a/.rspec b/.rspec new file mode 100644 index 00000000..4cd92bfe --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--fail-fast diff --git a/Gemfile b/Gemfile index 377d0c16..6f607a14 100644 --- a/Gemfile +++ b/Gemfile @@ -25,7 +25,8 @@ group :development, :unit_tests do end group :system_tests do - gem 'beaker', :require => false + gem 'beaker', :git => 'https://github.com/trevor-vaughan/beaker.git', :branch => 'BKR-978-2.51.0' + #gem 'beaker', :require => false gem 'beaker-rspec', '> 5', :require => false gem 'beaker_spec_helper', :require => false gem 'serverspec', :require => false diff --git a/README.md b/README.md index 51bf5cde..c5e1818e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ Let this module handle file creation and systemd reloading. Or handle file creation yourself and trigger systemd. ```puppet -include ::systemd +include ::systemd::systemctl::daemon_reload + file { '/usr/lib/systemd/system/foo.service': ensure => file, owner => 'root', @@ -32,7 +33,7 @@ file { '/usr/lib/systemd/system/foo.service': mode => '0644', source => "puppet:///modules/${module_name}/foo.service", } ~> -Exec['systemctl-daemon-reload'] +Class['systemd::systemctl::daemon_reload'] ``` ### tmpfiles @@ -48,7 +49,8 @@ Let this module handle file creation and systemd reloading Or handle file creation yourself and trigger systemd. ```puppet -include ::systemd +include ::systemd::tmpfiles + file { '/etc/tmpfiles.d/foo.conf': ensure => file, owner => 'root', @@ -56,7 +58,7 @@ file { '/etc/tmpfiles.d/foo.conf': mode => '0644', source => "puppet:///modules/${module_name}/foo.conf", } ~> -Exec['systemd-tmpfiles-create'] +Class['systemd::tmpfiles'] ``` ### service limits diff --git a/manifests/init.pp b/manifests/init.pp index e669f093..ed1908b6 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,24 +1,19 @@ -# -- Class systemd -# This module allows triggering systemd commands once for all modules +# This module allows triggering systemd commands once for all modules +# +# @api public +# +# @param service_limits +# May be passed a resource hash suitable for passing directly into the +# ``create_resources()`` function as called on ``systemd::service_limits`` +# class systemd ( - $service_limits = {} + Optional[Hash] $service_limits = undef ){ - Exec { - refreshonly => true, - path => $::path, - } - - exec { - 'systemctl-daemon-reload': - command => 'systemctl daemon-reload', - } + include '::systemd::systemctl::daemon_reload' + include '::systemd::tmpfiles' - exec { - 'systemd-tmpfiles-create': - command => 'systemd-tmpfiles --create', + if $service_limits { + create_resources('systemd::service_limits', $service_limits) } - - create_resources('systemd::service_limits', $service_limits, {}) - } diff --git a/manifests/service_limits.pp b/manifests/service_limits.pp index a9cdc25a..502df475 100644 --- a/manifests/service_limits.pp +++ b/manifests/service_limits.pp @@ -1,50 +1,86 @@ -# -- Define: systemd::service_limits -# Creates a custom config file and reloads systemd +# Adds a set of custom limits to the service +# +# @api public +# +# @see systemd.exec(5) +# +# @attr name [Pattern['^.+\.(service|socket|mount|swap)$']] +# The name of the service that you will be modifying +# +# @param $ensure +# Whether to drop a file or remove it +# +# @param path +# The path to the main systemd settings directory +# +# @param limits +# A Hash of service limits matching the settings in ``systemd.exec(5)`` +# +# * Mutually exclusive with ``$source`` +# +# @param source +# A ``File`` resource compatible ``source`` +# +# * Mutually exclusive with ``$limits`` +# +# @param restart_service +# Restart the managed service after setting the limits +# define systemd::service_limits( - $ensure = file, - $path = '/etc/systemd/system', - $limits = undef, - $source = undef, - $restart_service = true + Enum['file','absent'] $ensure = 'file', + Stdlib::Absolutepath $path = '/etc/systemd/system', + Optional[Systemd::ServiceLimits] $limits = undef, + Optional[String] $source = undef, + Boolean $restart_service = true ) { include ::systemd - if $limits { - validate_hash($limits) - $content = template('systemd/limits.erb') + if $title !~ Pattern['^.+\.(service|socket|mount|swap)$'] { + fail('$name must match Pattern["^.+\.(service|socket|mount|swap)$"]') + } + + if $limits and !empty($limits) { + $_content = template("${module_name}/limits.erb") } else { - $content = undef + $_content = undef } - if $limits and $source { + if ($limits and !empty($limits)) and $source { fail('You may not supply both limits and source parameters to systemd::service_limits') - } elsif $limits == undef and $source == undef { + } + elsif ($limits == undef or empty($limits)) and ($source == undef) { fail('You must supply either the limits or source parameter to systemd::service_limits') } - file { "${path}/${title}.d/": - ensure => 'directory', - owner => 'root', - group => 'root', - } - -> - file { "${path}/${title}.d/limits.conf": + ensure_resource('file', "${path}/${title}.d/", + { + ensure => 'directory', + owner => 'root', + group => 'root', + mode => '0644', + } + ) + + file { "${path}/${title}.d/90-limits.conf": ensure => $ensure, - content => $content, + content => $_content, source => $source, owner => 'root', group => 'root', - mode => '0444', - notify => Exec['systemctl-daemon-reload'], + mode => '0644', } + File["${path}/${title}.d/90-limits.conf"] ~> Class['systemd::systemctl::daemon_reload'] + if $restart_service { - exec { "systemctl restart ${title}": + exec { "restart ${title} because limits": + command => "systemctl restart ${title}", path => $::path, refreshonly => true, - subscribe => File["${path}/${title}.d/limits.conf"], - require => Exec['systemctl-daemon-reload'], } + + File["${path}/${title}.d/90-limits.conf"] ~> Exec["restart ${title} because limits"] + Class['systemd::systemctl::daemon_reload'] -> Exec["restart ${title} because limits"] } } diff --git a/manifests/systemctl/daemon_reload.pp b/manifests/systemctl/daemon_reload.pp new file mode 100644 index 00000000..f42efdc9 --- /dev/null +++ b/manifests/systemctl/daemon_reload.pp @@ -0,0 +1,10 @@ +# Reload the systemctl daemon +# +# @api public +class systemd::systemctl::daemon_reload { + exec { 'systemctl-daemon-reload': + command => 'systemctl daemon-reload', + refreshonly => true, + path => $::path, + } +} diff --git a/manifests/tmpfile.pp b/manifests/tmpfile.pp index c4d1a05f..8f8d4fd4 100644 --- a/manifests/tmpfile.pp +++ b/manifests/tmpfile.pp @@ -1,20 +1,49 @@ -# -- Define: systemd::tmpfile -# Creates a tmpfile and reloads systemd +# Creates a systemd tmpfile +# +# @api public +# +# @see systemd-tmpfiles(8) +# +# @attr name [String] +# The name of the tmpfile to create +# +# * May not contain ``/`` +# +# @param $ensure +# Whether to drop a file or remove it +# +# @param path +# The path to the main systemd tmpfiles directory +# +# @param content +# The literal content to write to the file +# +# * Mutually exclusive with ``$source`` +# +# @param source +# A ``File`` resource compatible ``source`` +# +# * Mutually exclusive with ``$limits`` +# define systemd::tmpfile( - $ensure = file, - $path = '/etc/tmpfiles.d', - $content = undef, - $source = undef, + Enum['file','absent'] $ensure = 'file', + Stdlib::Absolutepath $path = '/etc/tmpfiles.d', + Optional[String] $content = undef, + Optional[String] $source = undef, ) { include ::systemd + if name =~ Pattern['/'] { + fail('$name may not contain a forward slash "(/)"') + } + file { "${path}/${title}": ensure => $ensure, content => $content, source => $source, owner => 'root', group => 'root', - mode => '0444', - notify => Exec['systemd-tmpfiles-create'], + mode => '0644', + notify => Class['systemd::tmpfiles'], } -} \ No newline at end of file +} diff --git a/manifests/tmpfiles.pp b/manifests/tmpfiles.pp new file mode 100644 index 00000000..f71e0824 --- /dev/null +++ b/manifests/tmpfiles.pp @@ -0,0 +1,24 @@ +# Update the systemd temp files +# +# @api public +# +# @see systemd-tmpfiles(8) +# +# @param operations +# The operations to perform on the systemd tempfiles +# +# * All operations may be combined but you'll probably only ever want to +# use ``create`` +# +class systemd::tmpfiles ( + Array[Enum['create','clean','remove']] $operations = ['create'] +) { + + $_ops = join($operations.map |$op| { "--${op}" }, ' ') + + exec { 'systemd-tmpfiles': + command => "systemd-tmpfiles ${_ops}", + refreshonly => true, + path => $::path, + } +} diff --git a/manifests/unit_file.pp b/manifests/unit_file.pp index 94bc845b..3b9e2dd0 100644 --- a/manifests/unit_file.pp +++ b/manifests/unit_file.pp @@ -1,22 +1,60 @@ -# -- Define: systemd::unit_file -# Creates a unit file and reloads systemd +# Creates a systemd unit file +# +# @api public +# +# @see systemd.unit(5) +# +# @attr name [Pattern['^.+\.(service|socket|device|mount|automount|swap|target|path|timer|slice|scope)$']] +# The target unit file to create +# +# * Must not contain ``/`` +# +# @attr path +# The main systemd configuration path +# +# @attr content +# The full content of the unit file +# +# * Mutually exclusive with ``$source`` +# +# @attr source +# The ``File`` resource compatible ``source`` +# +# * Mutually exclusive with ``$content`` +# +# @attr target +# If set, will force the file to be a symlink to the given target +# +# * Mutually exclusive with both ``$source`` and ``$content`` +# define systemd::unit_file( - $ensure = file, - $path = '/etc/systemd/system', - $content = undef, - $source = undef, - $target = undef, + Enum['file', 'absent'] $ensure = 'file', + Stdlib::Absolutepath $path = '/etc/systemd/system', + Optional[String] $content = undef, + Optional[String] $source = undef, + Optional[Stdlib::Absolutepath] $target = undef, ) { include ::systemd + if $title !~ Pattern['^.+\.(service|socket|device|mount|automount|swap|target|path|timer|slice|scope)$'] { + fail('$name must match Pattern["^.+\.(service|socket|device|mount|automount|swap|target|path|timer|slice|scope)$"]') + } + + if $target { + $_ensure = 'link' + } + else { + $_ensure = $ensure + } + file { "${path}/${title}": - ensure => $ensure, + ensure => $_ensure, content => $content, source => $source, target => $target, owner => 'root', group => 'root', - mode => '0444', - notify => Exec['systemctl-daemon-reload'], + mode => '0644', + notify => Class['systemd::systemctl::daemon_reload'], } } diff --git a/metadata.json b/metadata.json index 08951efb..cfce7acc 100644 --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,6 @@ { "name": "camptocamp-systemd", - "version": "0.4.0", + "version": "1.0.0", "author": "camptocamp", "summary": "Puppet Systemd module", "license": "Apache-2.0", @@ -8,16 +8,19 @@ "project_page": "https://github.com/camptocamp/puppet-systemd", "issues_url": "https://github.com/camptocamp/puppet-systemd/issues", "dependencies": [ - + { + "name": "puppetlabs/stdlib", + "version_requirement": ">= 4.13.1 < 5.0.0" + } ], "requirements": [ { - "name": "pe", - "version_requirement": "3.x" + "name": "puppet", + "version_requirement": "4.x" }, { - "name": "puppet", - "version_requirement": "3.x" + "name": "pe", + "version_requirement": ">= 2016.2.0" } ], "operatingsystem_support": [ @@ -32,17 +35,12 @@ "operatingsystemrelease": [ "7" ] + }, + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "7" + ] } - ], - "puppet_version": [ - "2.7", - "3.0", - "3.1", - "3.2", - "3.3", - "3.4", - "3.5", - "3.6", - "3.7" ] } diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb new file mode 100644 index 00000000..11ea6db3 --- /dev/null +++ b/spec/classes/init_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe 'systemd' do + context 'supported operating systems' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { facts } + + it { is_expected.to compile.with_all_deps } + it { is_expected.to create_class('systemd') } + it { is_expected.to create_class('systemd::systemctl::daemon_reload') } + it { is_expected.to create_class('systemd::tmpfiles') } + end + end + end +end diff --git a/spec/classes/systemctl/daemon_reload_spec.rb b/spec/classes/systemctl/daemon_reload_spec.rb new file mode 100644 index 00000000..ec5f00eb --- /dev/null +++ b/spec/classes/systemctl/daemon_reload_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'systemd::systemctl::daemon_reload' do + context 'supported operating systems' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { facts } + + it { is_expected.to compile.with_all_deps } + it { is_expected.to create_class('systemd::systemctl::daemon_reload') } + it { is_expected.to create_exec('systemctl-daemon-reload') } + end + end + end +end diff --git a/spec/classes/tmpfiles_spec.rb b/spec/classes/tmpfiles_spec.rb new file mode 100644 index 00000000..13defae0 --- /dev/null +++ b/spec/classes/tmpfiles_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'systemd::tmpfiles' do + context 'supported operating systems' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { facts } + + it { is_expected.to compile.with_all_deps } + it { is_expected.to create_class('systemd::tmpfiles') } + it { is_expected.to contain_exec('systemd-tmpfiles').with_command('systemd-tmpfiles --create') } + end + end + end +end diff --git a/spec/defines/service_limits_spec.rb b/spec/defines/service_limits_spec.rb new file mode 100644 index 00000000..b63f7a68 --- /dev/null +++ b/spec/defines/service_limits_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'systemd::service_limits' do + context 'supported operating systems' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { facts } + + let(:title) { 'test.service' } + + let(:params) {{ + :limits => { + 'LimitCPU' => '10m' + } + }} + + it { is_expected.to compile.with_all_deps } + it { is_expected.to create_file("/etc/systemd/system/#{title}.d/90-limits.conf").with( + :ensure => 'file', + :content => /LimitCPU=10m/, + :mode => '0644' + ) } + it { is_expected.to create_exec("restart #{title} because limits").with( + :command => "systemctl restart #{title}", + :refreshonly => true + ) } + end + end + end +end diff --git a/spec/defines/tmpfile_spec.rb b/spec/defines/tmpfile_spec.rb index 4eb22acd..eb74de70 100644 --- a/spec/defines/tmpfile_spec.rb +++ b/spec/defines/tmpfile_spec.rb @@ -1,48 +1,24 @@ require 'spec_helper' describe 'systemd::tmpfile' do - - let(:facts) { { - :path => '/usr/bin', - } } - - context 'default params' do - - let(:title) { 'fancy.conf' } - - it 'creates the tmpfile' do - should contain_file('/etc/tmpfiles.d/fancy.conf').with({ - 'ensure' => 'file', - 'owner' => 'root', - 'group' => 'root', - 'mode' => '0444', - }) - end - - it 'triggers systemd daemon-reload' do - should contain_class('systemd') - should contain_file('/etc/tmpfiles.d/fancy.conf').with_notify("Exec[systemd-tmpfiles-create]") + context 'supported operating systems' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { facts } + + let(:title) { 'random_tmpfile' } + + let(:params) {{ + :content => 'random stuff' + }} + + it { is_expected.to compile.with_all_deps } + it { is_expected.to create_file("/etc/tmpfiles.d/#{title}").with( + :ensure => 'file', + :content => /#{params[:content]}/, + :mode => '0644' + ) } + end end end - - context 'with params' do - let(:title) { 'fancy.conf' } - - let(:params) { { - :ensure => 'absent', - :path => '/etc/tmpfiles.d/foo', - :content => 'some-content', - :source => 'some-source', - } } - - it 'creates the unit file' do - should contain_file('/etc/tmpfiles.d/foo/fancy.conf').with({ - 'ensure' => 'absent', - 'content' => 'some-content', - 'source' => 'some-source', - }) - end - - end - end diff --git a/spec/defines/unit_file_spec.rb b/spec/defines/unit_file_spec.rb index 88a0122c..0da1364a 100644 --- a/spec/defines/unit_file_spec.rb +++ b/spec/defines/unit_file_spec.rb @@ -1,50 +1,25 @@ require 'spec_helper' describe 'systemd::unit_file' do - - let(:facts) { { - :path => '/usr/bin', - } } - - context 'default params' do - - let(:title) { 'fancy.service' } - - it 'creates the unit file' do - should contain_file('/etc/systemd/system/fancy.service').with({ - 'ensure' => 'file', - 'owner' => 'root', - 'group' => 'root', - 'mode' => '0444', - }) - end - - it 'triggers systemd daemon-reload' do - should contain_class('systemd') - should contain_file('/etc/systemd/system/fancy.service').with_notify("Exec[systemctl-daemon-reload]") + context 'supported operating systems' do + on_supported_os.each do |os, facts| + context "on #{os}" do + let(:facts) { facts } + + let(:title) { 'test.service' } + + let(:params) {{ + :content => 'random stuff' + }} + + it { is_expected.to compile.with_all_deps } + it { is_expected.to create_file("/etc/systemd/system/#{title}").with( + :ensure => 'file', + :content => /#{params[:content]}/, + :mode => '0644' + ) } + it { is_expected.to create_file("/etc/systemd/system/#{title}").that_notifies('Class[systemd::systemctl::daemon_reload]') } + end end end - - context 'with params' do - let(:title) { 'fancy.service' } - - let(:params) { { - :ensure => 'absent', - :path => '/usr/lib/systemd/system', - :content => 'some-content', - :source => 'some-source', - :target => 'some-target', - } } - - it 'creates the unit file' do - should contain_file('/usr/lib/systemd/system/fancy.service').with({ - 'ensure' => 'absent', - 'content' => 'some-content', - 'source' => 'some-source', - 'target' => 'some-target', - }) - end - - end - end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 94d30d5c..bccb5d1b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,6 +6,18 @@ RSpec.configure do |c| c.include PuppetlabsSpec::Files + # Useless backtrace noise + backtrace_exclusion_patterns = [ + /spec_helper/, + /gems/ + ] + + if c.respond_to?(:backtrace_exclusion_patterns) + c.backtrace_exclusion_patterns = backtrace_exclusion_patterns + elsif c.respond_to?(:backtrace_clean_patterns) + c.backtrace_clean_patterns = backtrace_exclusion_patterns + end + c.before :each do # Store any environment variables away to be restored later @old_env = {} diff --git a/templates/limits.erb b/templates/limits.erb index 3caf5867..3955f7bc 100644 --- a/templates/limits.erb +++ b/templates/limits.erb @@ -1,26 +1,5 @@ -# This file is created by Puppet +# This file managed by Puppet - DO NOT EDIT [Service] -<% -[ - 'LimitCPU', - 'LimitFSIZE', - 'LimitDATA', - 'LimitSTACK', - 'LimitCORE', - 'LimitRSS', - 'LimitNOFILE', - 'LimitAS', - 'LimitNPROC', - 'LimitMEMLOCK', - 'LimitLOCKS', - 'LimitSIGPENDING', - 'LimitMSGQUEUE', - 'LimitNICE', - 'LimitRTPRIO', - 'LimitRTTIME' -].each do |d| -if @limits[d] -%> -<%= d %>=<%= @limits[d] %> -<% -end -end %> +<% @limits.keys.sort.each do |limit| -%> +<%= limit %>=<%= @limits[limit] %> +<% end -%> diff --git a/types/servicelimits.pp b/types/servicelimits.pp new file mode 100644 index 00000000..d9e3001e --- /dev/null +++ b/types/servicelimits.pp @@ -0,0 +1,21 @@ +# Matches Systemd Service Limit Struct +type Systemd::ServiceLimits = Struct[ + { + Optional['LimitCPU'] => Pattern['^\d+(s|m|h|d|w|M|y)?(:\d+(s|m|h|d|w|M|y)?)?$'], + Optional['LimitFSIZE'] => Pattern['^\d+(K|M|G|T|P|E)?:(\d+(K|M|G|T|P|E)?)?$'], + Optional['LimitDATA'] => Pattern['^\d+(K|M|G|T|P|E)?:(\d+(K|M|G|T|P|E)?)?$'], + Optional['LimitSTACK'] => Pattern['^\d+(K|M|G|T|P|E)?:(\d+(K|M|G|T|P|E)?)?$'], + Optional['LimitCORE'] => Pattern['^\d+(K|M|G|T|P|E)?:(\d+(K|M|G|T|P|E)?)?$'], + Optional['LimitRSS'] => Pattern['^\d+(K|M|G|T|P|E)?:(\d+(K|M|G|T|P|E)?)?$'], + Optional['LimitNOFILE'] => Integer[0], + Optional['LimitAS'] => Pattern['^\d+(K|M|G|T|P|E)?:(\d+(K|M|G|T|P|E)?)?$'], + Optional['LimitNPROC'] => Integer[1], + Optional['LimitMEMLOCK'] => Pattern['^\d+(K|M|G|T|P|E)?:(\d+(K|M|G|T|P|E)?)?$'], + Optional['LimitLOCKS'] => Integer[1], + Optional['LimitSIGPENDING'] => Integer[1], + Optional['LimitMSGQUEUE'] => Pattern['^\d+(K|M|G|T|P|E)?:(\d+(K|M|G|T|P|E)?)?$'], + Optional['LimitNICE'] => Integer[-20,19], + Optional['LimitRTPRIO'] => Integer[0], + Optional['LimitRTTIME'] => Pattern['^\d+(ms|s|m|h|d|w|M|y)?(:\d+(ms|s|m|h|d|w|M|y)?)?$'] + } +]