From c5d966acba80f42cf36c23f1f1d8c83182c05a8c Mon Sep 17 00:00:00 2001 From: Brett Maton Date: Wed, 5 Dec 2018 06:39:36 +0000 Subject: [PATCH] Enable user to define cron time attributes instead of defaulting to random time --- README.md | 20 ++-- manifests/certonly.pp | 10 +- spec/defines/letsencrypt_certonly_spec.rb | 106 +++++++++++++++++++++- 3 files changed, 124 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 72734084..f215355e 100644 --- a/README.md +++ b/README.md @@ -129,17 +129,21 @@ letsencrypt::certonly { 'foo': } ``` -To automatically renew a certificate, you can pass the `manage_cron` parameter. -You can optionally add a shell command to be run on success using the `cron_success_command` parameter. -You can optionally add a shell command to be run on before using the `cron_before_command` parameter. -You can optionally specify one or multiple days of the month when to run the cron using the `cron_monthday` parameter (default is every day). -You can disable output (and resulting emails) generated by the cron command using the `suppress_cron_output` parameter. +* `manage_cron` can be used to automatically renew the certificate +* `cron_success_command` can be used to run a shell command on a successful renewal +* `cron_before_command` can be used to run a shell command before a renewal +* `cron_monthday` can be used to specify one or multiple days of the month to run the cron job (defaults to every day) +* `cron_hour` can be used to specify hour(s) to run the cron job (defaults to a seeded random hour) +* `cron_minute` can be used to specify minute(s) to run the cron job (defaults to a seeded random minute) +* `suppress_cron_output` can be used to disable output (and resulting emails) generated by the cron command ```puppet letsencrypt::certonly { 'foo': - domains => ['foo.example.com', 'bar.example.com'], - manage_cron => true, - cron_before_command => 'service nginx stop', + domains => ['foo.example.com', 'bar.example.com'], + manage_cron => true, + cron_hour => [0,12], + cron_minute => '30', + cron_before_command => 'service nginx stop', cron_success_command => '/bin/systemctl reload nginx.service', suppress_cron_output => true, } diff --git a/manifests/certonly.pp b/manifests/certonly.pp index ab571bf8..11737665 100644 --- a/manifests/certonly.pp +++ b/manifests/certonly.pp @@ -31,6 +31,12 @@ # [*cron_success_command*] # String representation of a command that should be run if the renewal command # succeeds. +# [*cron_hour*] +# Optional string, integer or array, hour(s) that the renewal command should execute. +# e.g. '[0,12]' execute at midnight and midday. Default - seeded random hour. +# [*cron_minute*] +# Optional string, integer or array, minute(s) that the renewal command should execute. +# e.g. 0 or '00' or [0,30]. Default - seeded random minute. # define letsencrypt::certonly ( Array[String[1]] $domains = [$title], @@ -45,6 +51,8 @@ Optional[String[1]] $cron_before_command = undef, Optional[String[1]] $cron_success_command = undef, Array[Variant[Integer[0, 59], String[1]]] $cron_monthday = ['*'], + Variant[Integer[0,23], String, Array] $cron_hour = fqdn_rand(24, $title), + Variant[Integer[0,59], String, Array] $cron_minute = fqdn_rand(60, fqdn_rand_string(10, $title)), Stdlib::Unixpath $config_dir = $letsencrypt::config_dir, ) { @@ -109,8 +117,6 @@ } else { $cron_cmd = $renewcommand } - $cron_hour = fqdn_rand(24, $title) # 0 - 23, seed is title plus fqdn - $cron_minute = fqdn_rand(60, fqdn_rand_string(10,$title)) # 0 - 59, seed is title plus fqdn file { "${::letsencrypt::cron_scripts_path}/renew-${title}.sh": ensure => 'file', mode => '0755', diff --git a/spec/defines/letsencrypt_certonly_spec.rb b/spec/defines/letsencrypt_certonly_spec.rb index e78709be..a7d0563e 100644 --- a/spec/defines/letsencrypt_certonly_spec.rb +++ b/spec/defines/letsencrypt_certonly_spec.rb @@ -74,7 +74,7 @@ it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command 'letsencrypt --text --agree-tos --non-interactive certonly -a apache --cert-name foo.example.com -d foo.example.com' } end - context 'with custom plugin and manage cron' do + context 'with custom plugin and manage_cron' do let(:title) { 'foo.example.com' } let(:params) do { @@ -87,6 +87,108 @@ it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_content "#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --text --agree-tos --non-interactive certonly -a apache --keep-until-expiring --cert-name foo.example.com -d foo.example.com\n" } end + context 'with manage_cron and defined cron_hour (integer)' do + let(:title) { 'foo.example.com' } + let(:params) do + { + cron_hour: 13, + manage_cron: true + } + end + + it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_hour 13 } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_content "#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --text --agree-tos --non-interactive certonly -a standalone --keep-until-expiring --cert-name foo.example.com -d foo.example.com\n" } + end + + context 'with manage_cron and out of range defined cron_hour (integer)' do + let(:title) { 'foo.example.com' } + let(:params) do + { + cron_hour: 24, + manage_cron: true + } + end + + it { is_expected.to raise_error Puppet::Error } + end + + context 'with manage_cron and defined cron_hour (string)' do + let(:title) { 'foo.example.com' } + let(:params) do + { + cron_hour: '00', + manage_cron: true + } + end + + it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_hour '00' } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_content "#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --text --agree-tos --non-interactive certonly -a standalone --keep-until-expiring --cert-name foo.example.com -d foo.example.com\n" } + end + + context 'with manage_cron and defined cron_hour (array)' do + let(:title) { 'foo.example.com' } + let(:params) do + { + cron_hour: [1, 13], + manage_cron: true + } + end + + it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_hour [1, 13] } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_content "#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --text --agree-tos --non-interactive certonly -a standalone --keep-until-expiring --cert-name foo.example.com -d foo.example.com\n" } + end + + context 'with manage_cron and defined cron_minute (integer)' do + let(:title) { 'foo.example.com' } + let(:params) do + { + cron_minute: 15, + manage_cron: true + } + end + + it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_minute 15 } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_content "#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --text --agree-tos --non-interactive certonly -a standalone --keep-until-expiring --cert-name foo.example.com -d foo.example.com\n" } + end + + context 'with manage_cron and out of range defined cron_hour (integer)' do + let(:title) { 'foo.example.com' } + let(:params) do + { + cron_hour: 66, + manage_cron: true + } + end + + it { is_expected.to raise_error Puppet::Error } + end + + context 'with manage_cron and defined cron_minute (string)' do + let(:title) { 'foo.example.com' } + let(:params) do + { + cron_minute: '15', + manage_cron: true + } + end + + it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_minute '15' } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_content "#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --text --agree-tos --non-interactive certonly -a standalone --keep-until-expiring --cert-name foo.example.com -d foo.example.com\n" } + end + + context 'with manage_cron and defined cron_minute (array)' do + let(:title) { 'foo.example.com' } + let(:params) do + { + cron_minute: [0, 30], + manage_cron: true + } + end + + it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_minute [0, 30] } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_content "#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --text --agree-tos --non-interactive certonly -a standalone --keep-until-expiring --cert-name foo.example.com -d foo.example.com\n" } + end + context 'with custom puppet_vardir path and manage_cron' do let(:facts) { { osfamily: osfamily, operatingsystem: osfamily, operatingsystemrelease: osversion, operatingsystemmajrelease: osversion.split('.').first, path: '/usr/bin', puppet_vardir: '/tmp/custom_vardir' } } let(:title) { 'foo.example.com' } @@ -142,7 +244,7 @@ it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_environment(['VENV_PATH=/opt/letsencrypt/.venv', 'FOO=bar', 'FIZZ=buzz']) } end - context 'with custom environment variables and manage cron' do + context 'with custom environment variables and manage_cron' do let(:title) { 'foo.example.com' } let(:params) { { environment: ['FOO=bar', 'FIZZ=buzz'], manage_cron: true } }