diff --git a/.fixtures.yml b/.fixtures.yml index adba068..f9baf1c 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -1,12 +1,11 @@ fixtures: repositories: epel: 'https://github.com/voxpupuli/puppet-epel.git' inifile: 'https://github.com/puppetlabs/puppetlabs-inifile.git' stdlib: 'https://github.com/puppetlabs/puppetlabs-stdlib.git' - vcsrepo: 'https://github.com/puppetlabs/puppetlabs-vcsrepo.git' yumrepo_core: repo: https://github.com/puppetlabs/puppetlabs-yumrepo_core.git puppet_version: ">= 6.0.0" cron_core: repo: https://github.com/puppetlabs/puppetlabs-cron_core.git puppet_version: ">= 6.0.0" diff --git a/README.md b/README.md index dbc93d4..7a841d5 100644 --- a/README.md +++ b/README.md @@ -1,365 +1,365 @@ # Let's Encrypt [![Build Status](https://travis-ci.org/voxpupuli/puppet-letsencrypt.png?branch=master)](https://travis-ci.org/voxpupuli/puppet-letsencrypt) [![Puppet Forge](https://img.shields.io/puppetforge/v/puppet/letsencrypt.svg)](https://forge.puppetlabs.com/puppet/letsencrypt) [![Puppet Forge - downloads](https://img.shields.io/puppetforge/dt/puppet/letsencrypt.svg)](https://forge.puppetlabs.com/puppet/letsencrypt) [![Puppet Forge - endorsement](https://img.shields.io/puppetforge/e/puppet/letsencrypt.svg)](https://forge.puppetlabs.com/puppet/letsencrypt) [![Puppet Forge - scores](https://img.shields.io/puppetforge/f/puppet/letsencrypt.svg)](https://forge.puppetlabs.com/puppet/letsencrypt) [![Documentation Status](http://img.shields.io/badge/docs-puppet--strings-ff69b4.svg?style=flat)](http://voxpupuli.github.io/puppet-letsencrypt) This module installs the Let's Encrypt client from source and allows you to request certificates. ## Support This module is currently only written to work on Debian and RedHat based operating systems, although it may work on others. The supported Puppet versions are defined in the [metadata.json](metadata.json) ## Dependencies On EL (Red Hat, CentOS etc.) systems, the EPEL repository needs to be enabled for the Let's Encrypt client package. The module can integrate with [puppet/epel](https://forge.puppetlabs.com/puppet/epel) to set up the repo by setting the `configure_epel` parameter to `true` (the default for RedHat) and installing the module. ## Usage ### Setting up the Let's Encrypt client To install the Let's Encrypt client with the default configuration settings you must provide your email address to register with the Let's Encrypt servers: ```puppet class { 'letsencrypt': email => 'foo@example.com', } ``` -If using Ubuntu16.04 with `install_method` to default `package`, you can enforce upgrade of package from 0.4 to 0.7 with : +You can enforce upgrade of package to the latest available version (in your repositories): ```puppet class { 'letsencrypt': email => 'foo@example.com', package_ensure => 'latest', } ``` If using EL7 without EPEL-preconfigured, add `configure_epel`: ```puppet class { 'letsencrypt': configure_epel => true, email => 'foo@example.com', } ``` (If you manage epel some other way, disable it with `configure_epel => false`.) This will install the Let's Encrypt client and its dependencies, agree to the Terms of Service, initialize the client, and install a configuration file for the client. Alternatively, you can specify your email address in the $config hash: ```puppet class { 'letsencrypt': config => { email => 'foo@example.com', server => 'https://acme-v01.api.letsencrypt.org/directory', } } ``` During testing, you probably want to direct to the staging server instead with `server => 'https://acme-staging.api.letsencrypt.org/directory'` If you don't wish to provide your email address, you can set the `unsafe_registration` parameter to `true` (this is not recommended): ```puppet class { 'letsencrypt': unsafe_registration => true, } ``` To request a wildcard certificate, you must use the ACME v2 endpoint and use a DNS-01 challenge. See https://community.letsencrypt.org/t/acme-v2-production-environment-wildcards/55578 ```puppet class { 'letsencrypt': config => { email => 'foo@example.com', server => 'https://acme-v02.api.letsencrypt.org/directory', } } ``` ### Issuing certificates #### Standalone authenticator To request a certificate for `foo.example.com` using the `certonly` installer and the `standalone` authenticator: ```puppet letsencrypt::certonly { 'foo.example.com': } ``` #### Apache authenticator To request a certificate for `foo.example.com` and `bar.example.com` with the `certonly` installer and the `apache` authenticator: ```puppet letsencrypt::certonly { 'foo': domains => ['foo.example.com', 'bar.example.com'], plugin => 'apache', } ``` #### Webroot plugin To request a certificate using the `webroot` plugin, the paths to the webroots for all domains must be given through `webroot_paths`. If `domains` and `webroot_paths` are not the same length, the last `webroot_paths` element will be used for all subsequent domains. ```puppet letsencrypt::certonly { 'foo': domains => ['foo.example.com', 'bar.example.com'], plugin => 'webroot', webroot_paths => ['/var/www/foo', '/var/www/bar'], } ``` #### dns-rfc2136 plugin To request a certificate using the `dns-rfc2136` plugin, you will at a minimum need to pass `server`, `key_name` and `key_secret` to the class `letsencrypt::plugin::dns_rfc2136`. Ideally the key secret should be encrypted, eg. with eyaml if using Hiera. It's also recommended to only enable access to the specific DNS records needed by the Let's Encrypt client. Plugin documentation and it's parameters can be found here: https://certbot-dns-rfc2136.readthedocs.io Parameter defaults: - `key_algorithm` HMAC-SHA512 - `port` 53 - `propagation_seconds` 10 (the plugin defaults to 60) Example: ```puppet class { 'letsencrypt::plugin::dns_rfc2136': server => '192.0.2.1', key_name => 'certbot', key_secret => '[...]==', } letsencrypt::certonly { 'foo': domains => ['foo.example.com', 'bar.example.com'], plugin => 'dns-rfc2136', } ``` #### Additional arguments If you need to pass a command line flag to the `letsencrypt-auto` command that is not supported natively by this module, you can use the `additional_args` parameter to pass those arguments: ```puppet letsencrypt::certonly { 'foo': domains => ['foo.example.com', 'bar.example.com'], plugin => 'apache', additional_args => ['--foo bar', '--baz quuz'], } ``` ### Renewing certificates There are two ways to automatically renew certificates with cron using this module. #### cron using certbot renew All installed certificates will be renewed using `certbot renew` using their original settings, including any not managed by Puppet. * `renew_cron_ensure` manages the cron resource. Set to `present` to enable. Default: `absent` * `renew_cron_minute` sets minute(s) to run the cron job. Default: Seeded random minute * `renew_cron_hour` sets hour(s) to run the cron job. Default: Seeded random hour * `renew_cron_monthday` sets month day(s) to run the cron job. Default: Every day ```puppet class { 'letsencrypt': config => { email => 'foo@example.com', server => 'https://acme-v01.api.letsencrypt.org/directory', }, renew_cron_ensure => 'present', } ``` With Hiera, at 6 AM (roughly) every other day: ```yaml --- letsencrypt::renew_cron_ensure: 'present' letsencrypt::renew_cron_minute: 0 letsencrypt::renew_cron_hour: 6 letsencrypt::renew_cron_monthday: '1-31/2' ``` #### cron using certbot certonly Only specific certificates will be renewed using `certbot certonly`. * `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_hour => [0,12], cron_minute => '30', cron_before_command => 'service nginx stop', cron_success_command => '/bin/systemctl reload nginx.service', suppress_cron_output => true, } ``` #### Deprovisioning If a domain needs to be removed for any reason this can be done by setting `ensure` to 'absent', this will remove the certificates for this domain from the server. If `manage_cron` is set to true, the certificate renewal cronjob and shell scripts for the domain will also be removed. ```puppet letsencrypt::certonly { 'foo': ensure => 'absent', domains => ['foo.example.com', 'bar.example.com'], manage_cron => true, } ``` ## Hooks Certbot supports hooks since certbot v0.5.0, however this module uses the newer `--deploy-hook` replacing the deprecated `--renew-hook`. Because of this the minimum version you will need to manage hooks with this module is v0.17.0. All hook command parameters support both string and array. **Note on certbot hook behavior:** Hooks created by `letsencrypt::certonly` will be configured in the renewal config file of the certificate by certbot (stored in CONFIG_DIR/renewal/), which means all hooks created this way are used when running `certbot renew` without hook arguments. This allows you to easily create individual hooks for each certificate with just one cron job for renewal. HOWEVER, when running `certbot renew` with any of the hook arguments (setting any of the `letsencrypt::renew_*_hook_commands` parameters), hooks of the corresponding types in all renewal configs will be ignored by certbot. It's recommended to keep these two ways of using hooks mutually exclusive to avoid confusion. Cron jobs created by `letsencrypt::certonly` are unaffected as they renew certificates directly using `certbot certonly`. ### certbot certonly Hooks created with `letsencrypt::certonly` will behave the following way: * `pre` hooks will be run before each certificate is attempted issued or renewed, even if the action fails. * `post` hooks will be run after each certificate is attempted issued or renewed, even if the action fails. * `deploy` hooks will be run after successfully issuing or renewing each certificate. It will not be run if no action is taken or if the action fails. ```puppet letsencrypt::certonly { 'foo': domains => ['foo.example.com', 'bar.example.com'], pre_hook_commands => ['...'], post_hook_commands => ['...'], deploy_hook_commands => ['...'], } ``` ### certbot renew Hooks passed to `certbot renew` will behave the following way: * `pre` hook will be run once total before any certificates are attempted issued or renewed. It will not be run if no actions are taken. Overrides all pre hooks created by `letsencrypt::certonly`. * `post` hook will be run once total after all certificates are issued or renewed. It will not be run if no actions are taken. Overrides all post hooks created by `letsencrypt::certonly`. * `deploy` hook will be run once for each successfully issued or renewed certificate. It will not be run otherwise. Overrides all deploy hooks created by `letsencrypt::certonly`. ```puppet class { 'letsencrypt': config => { email => 'foo@example.com', server => 'https://acme-v01.api.letsencrypt.org/directory', }, renew_pre_hook_commands: [...], renew_post_hook_commands: [...], renew_deploy_hook_commands: [...], } ``` With Hiera: ```yaml --- letsencrypt::renew_pre_hook_commands: - '...' letsencrypt::renew_post_hook_commands: - '...' letsencrypt::renew_deploy_hook_commands: - '...' ``` ## Facts Facts about your live certificates are available through facter. You can query the list of live certificates from puppet using `$::letsencrypt_directory` in your puppet code, hiera data or from the command line. ``` facter -p letsencrypt_directory { legacyfiles.ijc.org => "/etc/letsencrypt/live/legacyfiles.ijc.org", static.ijc.org => "/etc/letsencrypt/live/static.ijc.org", ijc.org => "/etc/letsencrypt/live/ijc.org", new.ijc.org => "/etc/letsencrypt/live/new.ijc.org", www.ijc.org => "/etc/letsencrypt/live/ijc.org", training.ijc.org => "/etc/letsencrypt/live/training.ijc.org" } ``` ## Puppet Functions This module profiles a custom puppet function `letsencrypt::letsencrypt_lookup` which allows you to load information about your certificates into puppet. This returns the same information as in the facts but for a particular domain. It accepts a single argument for your domain or wildcard domain. ## Development 1. Fork it 2. Create a feature branch 3. Write a failing test 4. Write the code to make that test pass 5. Refactor the code 6. Submit a pull request We politely request (demand) tests for all new features. Pull requests that contain new features without a test will not be considered. If you need help, just ask! diff --git a/manifests/certonly.pp b/manifests/certonly.pp index b639fc9..2b46b84 100644 --- a/manifests/certonly.pp +++ b/manifests/certonly.pp @@ -1,219 +1,218 @@ # @summary Request a certificate using the `certonly` installer # # This type can be used to request a certificate using the `certonly` installer. # # @param ensure # Intended state of the resource # Will remove certificates for specified domains if set to 'absent'. Will # also remove cronjobs and renewal scripts if `manage_cron` is set to 'true'. # @param domains # An array of domains to include in the CSR. # @param custom_plugin Whether to use a custom plugin in additional_args and disable -a flag. # @param plugin The authenticator plugin to use when requesting the certificate. # @param webroot_paths # An array of webroot paths for the domains in `domains`. # Required if using `plugin => 'webroot'`. If `domains` and # `webroot_paths` are not the same length, the last `webroot_paths` # element will be used for all subsequent domains. # @param letsencrypt_command Command to run letsencrypt -# @param additional_args An array of additional command line arguments to pass to the `letsencrypt-auto` command. -# @param environment An optional array of environment variables (in addition to VENV_PATH). +# @param additional_args An array of additional command line arguments to pass to the `letsencrypt` command. +# @param environment An optional array of environment variables # @param key_size Size for the RSA public key # @param manage_cron # Indicating whether or not to schedule cron job for renewal. # Runs daily but only renews if near expiration, e.g. within 10 days. # @param suppress_cron_output Redirect cron output to devnull # @param cron_before_command Representation of a command that should be run before renewal command # @param cron_success_command Representation of a command that should be run if the renewal command succeeds. # @param cron_hour # Optional hour(s) that the renewal command should execute. # e.g. '[0,12]' execute at midnight and midday. Default - seeded random hour. # @param cron_minute # Optional minute(s) that the renewal command should execute. # e.g. 0 or '00' or [0,30]. Default - seeded random minute. # @param cron_monthday # Optional string, integer or array of monthday(s) the renewal command should # run. E.g. '2-30/2' to run on even days. Default: Every day. # @param config_dir The path to the configuration directory. # @param pre_hook_commands Array of commands to run in a shell before attempting to obtain/renew the certificate. # @param post_hook_commands Array of command(s) to run in a shell after attempting to obtain/renew the certificate. # @param deploy_hook_commands # Array of command(s) to run in a shell once if the certificate is successfully issued. # Two environmental variables are supplied by certbot: # - $RENEWED_LINEAGE: Points to the live directory with the cert files and key. # Example: /etc/letsencrypt/live/example.com # - $RENEWED_DOMAINS: A space-delimited list of renewed certificate domains. # Example: "example.com www.example.com" # define letsencrypt::certonly ( Enum['present','absent'] $ensure = 'present', Array[String[1]] $domains = [$title], String[1] $cert_name = $title, Boolean $custom_plugin = false, Letsencrypt::Plugin $plugin = 'standalone', Array[Stdlib::Unixpath] $webroot_paths = [], String[1] $letsencrypt_command = $letsencrypt::command, Integer[2048] $key_size = $letsencrypt::key_size, Array[String[1]] $additional_args = [], Array[String[1]] $environment = [], Boolean $manage_cron = false, Boolean $suppress_cron_output = false, 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, Variant[String[1], Array[String[1]]] $pre_hook_commands = [], Variant[String[1], Array[String[1]]] $post_hook_commands = [], Variant[String[1], Array[String[1]]] $deploy_hook_commands = [], ) { if $plugin == 'webroot' and empty($webroot_paths) { fail("The 'webroot_paths' parameter must be specified when using the 'webroot' plugin") } # Wildcard-less title for use in file paths $title_nowc = regsubst($title, '^\*\.', '') if $ensure == 'present' { if ($custom_plugin) { $default_args = "--text --agree-tos --non-interactive certonly --rsa-key-size ${key_size}" } else { $default_args = "--text --agree-tos --non-interactive certonly --rsa-key-size ${key_size} -a ${plugin}" } } else { $default_args = '--text --agree-tos --non-interactive delete' } case $plugin { 'webroot': { $_plugin_args = zip($domains, $webroot_paths).map |$domain| { if $domain[1] { "--webroot-path ${domain[1]} -d '${domain[0]}'" } else { "-d '${domain[0]}'" } } $plugin_args = ["--cert-name '${cert_name}'"] + $_plugin_args } 'dns-rfc2136': { require letsencrypt::plugin::dns_rfc2136 $_domains = join($domains, '\' -d \'') $plugin_args = [ "--cert-name '${cert_name}' -d", "'${_domains}'", "--dns-rfc2136-credentials ${letsencrypt::plugin::dns_rfc2136::config_dir}/dns-rfc2136.ini", "--dns-rfc2136-propagation-seconds ${letsencrypt::plugin::dns_rfc2136::propagation_seconds}", ] } 'dns-route53': { require letsencrypt::plugin::dns_route53 $_domains = join($domains, '\' -d \'') $plugin_args = [ "--cert-name '${cert_name}' -d '${_domains}'", "--dns-route53-propagation-seconds ${letsencrypt::plugin::dns_route53::propagation_seconds}", ] } default: { if $ensure == 'present' { $_domains = join($domains, '\' -d \'') $plugin_args = "--cert-name '${cert_name}' -d '${_domains}'" } else { $plugin_args = "--cert-name '${cert_name}'" } } } $hook_args = ['pre', 'post', 'deploy'].map | String $type | { $commands = getvar("${type}_hook_commands") if (!empty($commands)) { $hook_file = "${config_dir}/renewal-hooks-puppet/${title_nowc}-${type}.sh" letsencrypt::hook { "${title}-${type}": type => $type, hook_file => $hook_file, commands => $commands, before => Exec["letsencrypt certonly ${title}"], } "--${type}-hook \"${hook_file}\"" } else { undef } } # certbot uses --cert-name to generate the file path $live_path_certname = regsubst($cert_name, '^\*\.', '') $live_path = "${config_dir}/live/${live_path_certname}/cert.pem" $_command = flatten([ $letsencrypt_command, $default_args, $plugin_args, $hook_args, $additional_args, ]).filter | $arg | { $arg =~ NotUndef and $arg != [] } $command = join($_command, ' ') - $execution_environment = ["VENV_PATH=${letsencrypt::venv_path}",] + $environment $verify_domains = join(unique($domains), '\' \'') if $ensure == 'present' { $exec_ensure = { 'unless' => "/usr/local/sbin/letsencrypt-domain-validation ${live_path} '${verify_domains}'" } } else { $exec_ensure = { 'onlyif' => "/usr/local/sbin/letsencrypt-domain-validation ${live_path} '${verify_domains}'" } } exec { "letsencrypt certonly ${title}": command => $command, * => $exec_ensure, path => $facts['path'], - environment => $execution_environment, + environment => $environment, provider => 'shell', require => [ Class['letsencrypt'], File['/usr/local/sbin/letsencrypt-domain-validation'], ], } if $manage_cron { $maincommand = join(["${letsencrypt_command} --keep-until-expiring"] + $_command[1,-1], ' ') $cron_script_ensure = $ensure ? { 'present' => 'file', default => 'absent' } $cron_ensure = $ensure if $suppress_cron_output { $croncommand = "${maincommand} > /dev/null 2>&1" } else { $croncommand = $maincommand } if $cron_before_command { $renewcommand = "(${cron_before_command}) && ${croncommand}" } else { $renewcommand = $croncommand } if $cron_success_command { $cron_cmd = "${renewcommand} && (${cron_success_command})" } else { $cron_cmd = $renewcommand } file { "${letsencrypt::cron_scripts_path}/renew-${title}.sh": ensure => $cron_script_ensure, mode => '0755', owner => 'root', group => $letsencrypt::cron_owner_group, content => template('letsencrypt/renew-script.sh.erb'), } cron { "letsencrypt renew cron ${title}": ensure => $cron_ensure, command => "\"${letsencrypt::cron_scripts_path}/renew-${title}.sh\"", user => root, hour => $cron_hour, minute => $cron_minute, monthday => $cron_monthday, } } } diff --git a/manifests/init.pp b/manifests/init.pp index d72707b..261e8f7 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,131 +1,113 @@ # @summary Install and configure Certbot, the LetsEncrypt client # # Install and configure Certbot, the LetsEncrypt client # # @example # class { 'letsencrypt' : # email => 'letsregister@example.com', # config => { # 'server' => 'https://acme-staging-v02.api.letsencrypt.org/directory', # }, # } # # @param email # The email address to use to register with Let's Encrypt. This takes # precedence over an 'email' setting defined in $config. -# @param path The path to the letsencrypt installation. -# @param venv_path virtualenv path for vcs-installed Certbot -# @param environment An optional array of environment variables (in addition to VENV_PATH) -# @param repo A Git URL to install the Let's encrypt client from. -# @param version The Git ref (tag, sha, branch) to check out when installing the client with the `vcs` method. +# @param environment An optional array of environment variables # @param package_name Name of package and command to use when installing the client with the `package` method. # @param package_ensure The value passed to `ensure` when installing the client with the `package` method. # @param package_command Path or name for letsencrypt executable when installing the client with the `package` method. # @param config_file The path to the configuration file for the letsencrypt cli. # @param config A hash representation of the letsencrypt configuration file. # @param cron_scripts_path The path for renewal scripts called by cron # @param cron_owner_group Group owner of cron renew scripts. # @param manage_config A feature flag to toggle the management of the letsencrypt configuration file. # @param manage_install A feature flag to toggle the management of the letsencrypt client installation. # @param manage_dependencies A feature flag to toggle the management of the letsencrypt dependencies. # @param configure_epel A feature flag to include the 'epel' class and depend on it for package installation. -# @param install_method Method to install the letsencrypt client, either package or vcs. # @param agree_tos A flag to agree to the Let's Encrypt Terms of Service. # @param unsafe_registration A flag to allow using the 'register-unsafely-without-email' flag. # @param config_dir The path to the configuration directory. # @param key_size Size for the RSA public key # @param renew_pre_hook_commands Array of commands to run in a shell before obtaining/renewing any certificates. # @param renew_post_hook_commands Array of commands to run in a shell after attempting to obtain/renew certificates. # @param renew_deploy_hook_commands # Array of commands to run in a shell once for each successfully issued/renewed # certificate. Two environmental variables are supplied by certbot: # - $RENEWED_LINEAGE: Points to the live directory with the cert files and key. # Example: /etc/letsencrypt/live/example.com # - $RENEWED_DOMAINS: A space-delimited list of renewed certificate domains. # Example: "example.com www.example.com" # @param renew_additional_args Array of additional command line arguments to pass to 'certbot renew'. # @param renew_cron_ensure Intended state of the cron resource running certbot renew. # @param renew_cron_hour # Optional string, integer or array of hour(s) the renewal command should run. # E.g. '[0,12]' to execute at midnight and midday. # hour. # @param renew_cron_minute # Optional string, integer or array of minute(s) the renewal command should # run. E.g. 0 or '00' or [0,30]. # @param renew_cron_monthday # Optional string, integer or array of monthday(s) the renewal command should # run. E.g. '2-30/2' to run on even days. # class letsencrypt ( Boolean $configure_epel, Optional[String] $email = undef, - String $path = '/opt/letsencrypt', - $venv_path = '/opt/letsencrypt/.venv', Array $environment = [], - String $repo = 'https://github.com/certbot/certbot.git', - String $version = 'v0.39.0', String $package_name = 'certbot', $package_ensure = 'installed', String $package_command = 'certbot', Stdlib::Unixpath $config_dir = '/etc/letsencrypt', String $config_file = "${config_dir}/cli.ini", Hash $config = { 'server' => 'https://acme-v02.api.letsencrypt.org/directory' }, String $cron_scripts_path = "${facts['puppet_vardir']}/letsencrypt", String $cron_owner_group = 'root', Boolean $manage_config = true, Boolean $manage_install = true, Boolean $manage_dependencies = true, - Enum['package', 'vcs'] $install_method = 'package', Boolean $agree_tos = true, Boolean $unsafe_registration = false, Integer[2048] $key_size = 4096, # $renew_* should only be used in letsencrypt::renew (blame rspec) $renew_pre_hook_commands = [], $renew_post_hook_commands = [], $renew_deploy_hook_commands = [], $renew_additional_args = [], $renew_cron_ensure = 'absent', $renew_cron_hour = fqdn_rand(24), $renew_cron_minute = fqdn_rand(60, fqdn_rand_string(10)), $renew_cron_monthday = '*', ) { if $manage_install { contain letsencrypt::install # lint:ignore:relative_classname_inclusion Class['letsencrypt::install'] ~> Exec['initialize letsencrypt'] Class['letsencrypt::install'] -> Class['letsencrypt::renew'] } - $command = $install_method ? { - 'package' => $package_command, - 'vcs' => "${venv_path}/bin/letsencrypt", - } - - $command_init = $install_method ? { - 'package' => $package_command, - 'vcs' => "${path}/letsencrypt-auto", - } + $command = $package_command if $manage_config { contain letsencrypt::config # lint:ignore:relative_classname_inclusion Class['letsencrypt::config'] -> Exec['initialize letsencrypt'] } contain letsencrypt::renew # TODO: do we need this command when installing from package? exec { 'initialize letsencrypt': - command => "${command_init} -h", + command => "${package_command} -h", path => $facts['path'], - environment => concat(["VENV_PATH=${venv_path}"], $environment), + environment => $environment, refreshonly => true, } # Used in letsencrypt::certonly Exec["letsencrypt certonly ${title}"] file { '/usr/local/sbin/letsencrypt-domain-validation': ensure => file, owner => 'root', group => 'root', mode => '0500', source => "puppet:///modules/${module_name}/domain-validation.sh", } } diff --git a/manifests/install.pp b/manifests/install.pp index 0ba3313..c46f33c 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -1,48 +1,21 @@ # @summary Installs the Let's Encrypt client. # -# @param manage_install A feature flag to toggle the management of the letsencrypt client installation. -# @param manage_dependencies A feature flag to toggle the management of the letsencrypt dependencies. # @param configure_epel A feature flag to include the 'epel' class and depend on it for package installation. -# @param install_method Method to install the letsencrypt client -# @param path The path to the letsencrypt installation. -# @param repo A Git URL to install the Let's encrypt client from. -# @param version The Git ref (tag, sha, branch) to check out when installing the client with the `vcs` method. # @param package_ensure The value passed to `ensure` when installing the client with the `package` method. # @param package_name Name of package to use when installing the client with the `package` method. # class letsencrypt::install ( - Boolean $manage_install = $letsencrypt::manage_install, - Boolean $manage_dependencies = $letsencrypt::manage_dependencies, Boolean $configure_epel = $letsencrypt::configure_epel, - Enum['package', 'vcs'] $install_method = $letsencrypt::install_method, String $package_name = $letsencrypt::package_name, String $package_ensure = $letsencrypt::package_ensure, - String $path = $letsencrypt::path, - String $repo = $letsencrypt::repo, - String $version = $letsencrypt::version, ) { - if $install_method == 'vcs' { - if $manage_dependencies { - $dependencies = ['python', 'git'] - ensure_packages($dependencies) - Package[$dependencies] -> Vcsrepo[$path] - } - - vcsrepo { $path: - ensure => present, - provider => git, - source => $repo, - revision => $version, - } - } else { - package { 'letsencrypt': - ensure => $package_ensure, - name => $package_name, - } + package { 'letsencrypt': + ensure => $package_ensure, + name => $package_name, + } - if $configure_epel { - include epel - Class['epel'] -> Package['letsencrypt'] - } + if $configure_epel { + include epel + Class['epel'] -> Package['letsencrypt'] } } diff --git a/metadata.json b/metadata.json index ffdcb60..09d6a3b 100644 --- a/metadata.json +++ b/metadata.json @@ -1,87 +1,83 @@ { "name": "puppet-letsencrypt", "version": "6.0.1-rc0", "author": "Vox Pupuli", "summary": "Manages lets-encrypt and certbot + related certs", "license": "Apache-2.0", "source": "https://github.com/voxpupuli/puppet-letsencrypt", "project_page": "https://github.com/voxpupuli/puppet-letsencrypt", "issues_url": "https://github.com/voxpupuli/puppet-letsencrypt/issues", "tags": [ "letsencrypt", "let's encrypt", "certbot", "acme" ], "operatingsystem_support": [ { "operatingsystem": "CentOS", "operatingsystemrelease": [ "7" ] }, { "operatingsystem": "RedHat", "operatingsystemrelease": [ "7" ] }, { "operatingsystem": "Fedora", "operatingsystemrelease": [ "32" ] }, { "operatingsystem": "Ubuntu", "operatingsystemrelease": [ "16.04", "18.04" ] }, { "operatingsystem": "Debian", "operatingsystemrelease": [ "9", "10" ] }, { "operatingsystem": "OpenBSD", "operatingsystemrelease": [ "6.2" ] }, { "operatingsystem": "FreeBSD", "operatingsystemrelease": [ "11", "12" ] } ], "requirements": [ { "name": "puppet", "version_requirement": ">= 6.1.0 < 8.0.0" } ], "dependencies": [ { "name": "puppetlabs/stdlib", "version_requirement": ">= 4.13.1 < 7.0.0" }, { "name": "puppetlabs/inifile", "version_requirement": ">= 2.0.0 < 5.0.0" }, - { - "name": "puppetlabs/vcsrepo", - "version_requirement": ">= 2.0.0 < 4.0.0" - }, { "name": "puppet/epel", "version_requirement": ">= 3.0.1 < 4.0.0" } ] } diff --git a/spec/acceptance/letsencrypt_spec.rb b/spec/acceptance/letsencrypt_spec.rb index 7c30e7b..38c0d04 100644 --- a/spec/acceptance/letsencrypt_spec.rb +++ b/spec/acceptance/letsencrypt_spec.rb @@ -1,65 +1,30 @@ require 'spec_helper_acceptance' describe 'letsencrypt' do context 'with defaults values' do pp = %( class { 'letsencrypt' : email => 'letsregister@example.com', config => { 'server' => 'https://acme-staging-v02.api.letsencrypt.org/directory', }, } ) it 'installs letsencrypt without error' do apply_manifest(pp, catch_failures: true) end it 'installs letsencrypt idempotently' do apply_manifest(pp, catch_changes: true) end describe file('/etc/letsencrypt/cli.ini') do it { is_expected.to be_file } it { is_expected.to be_owned_by 'root' } it { is_expected.to be_grouped_into 'root' } it { is_expected.to be_mode 644 } its(:content) { is_expected.to match %r{server = https://acme-staging-v02.api.letsencrypt.org/directory} } its(:content) { is_expected.to match %r{email = letsregister@example.com} } end end - - context 'with install_method => vcs' do - pp = %( - class { 'letsencrypt' : - install_method => 'vcs', - email => 'letsregister@example.com', - config => { - 'server' => 'https://acme-staging-v02.api.letsencrypt.org/directory', - }, - } - ) - - it 'installs letsencrypt without error' do - apply_manifest(pp, catch_failures: true) - end - it 'installs letsencrypt idempotently' do - apply_manifest(pp, catch_changes: true) - end - - describe file('/etc/letsencrypt/cli.ini') do - it { is_expected.to be_file } - it { is_expected.to be_owned_by 'root' } - it { is_expected.to be_grouped_into 'root' } - it { is_expected.to be_mode 644 } - its(:content) { is_expected.to match %r{server = https://acme-staging-v02.api.letsencrypt.org/directory} } - its(:content) { is_expected.to match %r{email = letsregister@example.com} } - end - - describe file('/opt/letsencrypt/.venv/bin/certbot') do - it { is_expected.to be_file } - it { is_expected.to be_owned_by 'root' } - it { is_expected.to be_grouped_into 'root' } - it { is_expected.to be_mode 755 } - end - end end diff --git a/spec/classes/letsencrypt_install_spec.rb b/spec/classes/letsencrypt_install_spec.rb index 46a807c..7510585 100644 --- a/spec/classes/letsencrypt_install_spec.rb +++ b/spec/classes/letsencrypt_install_spec.rb @@ -1,103 +1,48 @@ require 'spec_helper' describe 'letsencrypt::install' do on_supported_os.each do |os, facts| let(:params) { default_params.merge(additional_params) } let(:default_params) do { configure_epel: false, package_ensure: 'installed', - manage_install: true, - manage_dependencies: true, - path: '/opt/letsencrypt', - repo: 'https://github.com/certbot/certbot.git', - version: 'v0.30.2', package_name: 'letsencrypt' } end let(:additional_params) { {} } context "on #{os} based operating systems" do let :facts do facts end - describe 'with install_method => package' do - let(:additional_params) { { install_method: 'package' } } + it { is_expected.to compile.with_all_deps } - it { is_expected.to compile } - - it 'contains the correct resources' do - is_expected.not_to contain_vcsrepo('/opt/letsencrypt') - is_expected.not_to contain_package('python') - is_expected.not_to contain_package('git') - - is_expected.to contain_package('letsencrypt').with_ensure('installed') - end - - describe 'with package_ensure => 0.3.0-1.el7' do - let(:additional_params) { { install_method: 'package', package_ensure: '0.3.0-1.el7' } } - - it { is_expected.to compile } - it { is_expected.to contain_package('letsencrypt').with_ensure('0.3.0-1.el7') } - end - - case facts[:osfamily] - when 'RedHat' - describe 'with configure_epel => true' do - let(:additional_params) { { install_method: 'package', configure_epel: true } } - - it { is_expected.to compile } - - it 'contains the correct resources' do - is_expected.to contain_class('epel') - is_expected.to contain_package('letsencrypt').that_requires('Class[epel]') - end - end - end + it 'contains the correct resources' do + is_expected.to contain_package('letsencrypt').with_ensure('installed') end - describe 'with install_method => vcs' do - let(:additional_params) { { install_method: 'vcs' } } - - it { is_expected.to compile } + describe 'with package_ensure => 0.3.0-1.el7' do + let(:additional_params) { { package_ensure: '0.3.0-1.el7' } } - it 'contains the correct resources' do - is_expected.to contain_vcsrepo('/opt/letsencrypt').with(source: 'https://github.com/certbot/certbot.git', - revision: 'v0.30.2') - is_expected.to contain_package('python') - is_expected.to contain_package('git') - - is_expected.not_to contain_package('letsencrypt') - end - - describe 'with custom path' do - let(:additional_params) { { install_method: 'vcs', path: '/usr/lib/letsencrypt' } } - - it { is_expected.to contain_vcsrepo('/usr/lib/letsencrypt') } - end - - describe 'with custom repo' do - let(:additional_params) { { install_method: 'vcs', repo: 'git://foo.com/letsencrypt.git' } } - - it { is_expected.to contain_vcsrepo('/opt/letsencrypt').with_source('git://foo.com/letsencrypt.git') } - end - - describe 'with custom version' do - let(:additional_params) { { install_method: 'vcs', version: 'foo' } } + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_package('letsencrypt').with_ensure('0.3.0-1.el7') } + end - it { is_expected.to contain_vcsrepo('/opt/letsencrypt').with_revision('foo') } - end + case facts[:osfamily] + when 'RedHat' + describe 'with configure_epel => true' do + let(:additional_params) { { configure_epel: true } } - describe 'with manage_dependencies set to false' do - let(:additional_params) { { install_method: 'vcs', manage_dependencies: false } } + it { is_expected.to compile.with_all_deps } - it 'does not contain the dependencies' do - is_expected.not_to contain_package('git') - is_expected.not_to contain_package('python') + it 'contains the correct resources' do + is_expected.to contain_class('epel') + is_expected.to contain_package('letsencrypt').that_requires('Class[epel]') end end end end end end diff --git a/spec/classes/letsencrypt_spec.rb b/spec/classes/letsencrypt_spec.rb index 1a5e615..1b59164 100644 --- a/spec/classes/letsencrypt_spec.rb +++ b/spec/classes/letsencrypt_spec.rb @@ -1,298 +1,255 @@ require 'spec_helper' describe 'letsencrypt' do on_supported_os.each do |os, facts| context "on #{os}" do let :facts do facts end context 'when specifying an email address with the email parameter' do let(:params) { additional_params.merge(default_params) } let(:default_params) { { email: 'foo@example.com' } } let(:additional_params) { {} } describe 'with defaults' do it { is_expected.to compile } epel = facts[:osfamily] == 'RedHat' && facts[:operatingsystem] != 'Fedora' it 'contains File[/usr/local/sbin/letsencrypt-domain-validation]' do is_expected.to contain_file('/usr/local/sbin/letsencrypt-domain-validation'). with_ensure('file'). with_owner('root'). with_group('root'). with_mode('0500'). with_source('puppet:///modules/letsencrypt/domain-validation.sh') end it 'contains the correct resources' do is_expected.to contain_class('letsencrypt::install'). - with(configure_epel: epel, - manage_install: true, - manage_dependencies: true, - repo: 'https://github.com/certbot/certbot.git', - version: 'v0.39.0'). + with(configure_epel: epel). that_notifies('Exec[initialize letsencrypt]'). that_comes_before('Class[letsencrypt::renew]') - is_expected.to contain_exec('initialize letsencrypt') + is_expected.to contain_exec('initialize letsencrypt').with_command('certbot -h') is_expected.to contain_class('letsencrypt::config').that_comes_before('Exec[initialize letsencrypt]') is_expected.to contain_class('letsencrypt::renew'). with(pre_hook_commands: [], post_hook_commands: [], deploy_hook_commands: [], additional_args: [], cron_ensure: 'absent', cron_monthday: ['*']) is_expected.to contain_cron('letsencrypt-renew').with_ensure('absent') if facts[:osfamily] == 'FreeBSD' is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini email foo@example.com') is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini server https://acme-v02.api.letsencrypt.org/directory') is_expected.to contain_file('letsencrypt-renewal-hooks-puppet'). with(ensure: 'directory', path: '/usr/local/etc/letsencrypt/renewal-hooks-puppet', owner: 'root', group: 'root', mode: '0755', recurse: true, purge: true) else is_expected.to contain_ini_setting('/etc/letsencrypt/cli.ini email foo@example.com') is_expected.to contain_ini_setting('/etc/letsencrypt/cli.ini server https://acme-v02.api.letsencrypt.org/directory') is_expected.to contain_file('letsencrypt-renewal-hooks-puppet').with_path('/etc/letsencrypt/renewal-hooks-puppet') end if facts[:osfamily] == 'RedHat' if epel is_expected.to contain_class('epel').that_comes_before('Package[letsencrypt]') else is_expected.not_to contain_class('epel') end - is_expected.to contain_class('letsencrypt::install').with(install_method: 'package').with(package_name: 'certbot') + is_expected.to contain_class('letsencrypt::install').with(package_name: 'certbot') is_expected.to contain_class('letsencrypt').with(package_command: 'certbot') is_expected.to contain_package('letsencrypt').with(name: 'certbot') is_expected.to contain_file('/etc/letsencrypt').with(ensure: 'directory') elsif facts[:osfamily] == 'Debian' - is_expected.to contain_class('letsencrypt::install').with(install_method: 'package').with(package_name: 'certbot') + is_expected.to contain_class('letsencrypt::install').with(package_name: 'certbot') is_expected.to contain_file('/etc/letsencrypt').with(ensure: 'directory') elsif facts[:operatingsystem] == 'Gentoo' - is_expected.to contain_class('letsencrypt::install').with(install_method: 'package').with(package_name: 'app-crypt/certbot') + is_expected.to contain_class('letsencrypt::install').with(package_name: 'app-crypt/certbot') is_expected.to contain_class('letsencrypt').with(package_command: 'certbot') is_expected.to contain_package('letsencrypt').with(name: 'app-crypt/certbot') is_expected.to contain_file('/etc/letsencrypt').with(ensure: 'directory') elsif facts[:operatingsystem] == 'OpenBSD' - is_expected.to contain_class('letsencrypt::install').with(install_method: 'package').with(package_name: 'certbot') + is_expected.to contain_class('letsencrypt::install').with(package_name: 'certbot') is_expected.to contain_class('letsencrypt').with(package_command: 'certbot') is_expected.to contain_package('letsencrypt').with(name: 'certbot') is_expected.to contain_file('/etc/letsencrypt').with(ensure: 'directory') elsif facts[:operatingsystem] == 'FreeBSD' - is_expected.to contain_class('letsencrypt::install').with(install_method: 'package').with(package_name: 'py27-certbot') + is_expected.to contain_class('letsencrypt::install').with(package_name: 'py27-certbot') is_expected.to contain_class('letsencrypt').with(package_command: 'certbot') is_expected.to contain_package('letsencrypt').with(name: 'py27-certbot') is_expected.to contain_file('/usr/local/etc/letsencrypt').with(ensure: 'directory') else - is_expected.to contain_class('letsencrypt::install').with(install_method: 'vcs') + is_expected.to contain_class('letsencrypt::install') is_expected.to contain_file('/etc/letsencrypt').with(ensure: 'directory') end end end # describe 'with defaults' - describe 'with custom path' do - let(:additional_params) { { path: '/usr/lib/letsencrypt', install_method: 'vcs' } } - - it { is_expected.to contain_class('letsencrypt::install').with_path('/usr/lib/letsencrypt') } - it { is_expected.to contain_exec('initialize letsencrypt').with_command('/usr/lib/letsencrypt/letsencrypt-auto -h') } - end - describe 'with custom environment variables' do let(:additional_params) { { environment: ['FOO=bar', 'FIZZ=buzz'] } } - it { is_expected.to contain_exec('initialize letsencrypt').with_environment(['VENV_PATH=/opt/letsencrypt/.venv', 'FOO=bar', 'FIZZ=buzz']) } - end - - describe 'with custom repo' do - let(:additional_params) { { repo: 'git://foo.com/letsencrypt.git' } } - - it { is_expected.to contain_class('letsencrypt::install').with_repo('git://foo.com/letsencrypt.git') } - end - - describe 'with custom version' do - let(:additional_params) { { version: 'foo' } } - - it { is_expected.to contain_class('letsencrypt::install').with_path('/opt/letsencrypt').with_version('foo') } + it { is_expected.to contain_exec('initialize letsencrypt').with_environment(['FOO=bar', 'FIZZ=buzz']) } end describe 'with custom package_ensure' do let(:additional_params) { { package_ensure: '0.3.0-1.el7' } } it { is_expected.to contain_class('letsencrypt::install').with_package_ensure('0.3.0-1.el7') } end describe 'with custom config file' do let(:additional_params) { { config_file: '/etc/letsencrypt/custom_config.ini' } } it { is_expected.to contain_ini_setting('/etc/letsencrypt/custom_config.ini server https://acme-v02.api.letsencrypt.org/directory') } end describe 'with custom config' do let(:additional_params) { { config: { 'foo' => 'bar' } } } case facts[:operatingsystem] when 'FreeBSD' it { is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini foo bar') } else it { is_expected.to contain_ini_setting('/etc/letsencrypt/cli.ini foo bar') } end end describe 'with manage_config set to false' do let(:additional_params) { { manage_config: false } } it { is_expected.not_to contain_class('letsencrypt::config') } end describe 'with manage_install set to false' do let(:additional_params) { { manage_install: false } } it { is_expected.not_to contain_class('letsencrypt::install') } end - describe 'with install_method => package' do - let(:additional_params) { { install_method: 'package', package_command: 'letsencrypt' } } - - it { is_expected.to contain_class('letsencrypt::install').with_install_method('package') } - it { is_expected.to contain_exec('initialize letsencrypt').with_command('letsencrypt -h') } - end - - describe 'with install_method => vcs' do - let(:additional_params) { { install_method: 'vcs' } } - - it { is_expected.to contain_class('letsencrypt::install').with_install_method('vcs') } - it { is_expected.to contain_exec('initialize letsencrypt').with_command('/opt/letsencrypt/letsencrypt-auto -h') } - end - describe 'with custom config directory' do let(:additional_params) { { config_dir: '/foo/bar/baz' } } it { is_expected.to contain_file('/foo/bar/baz').with(ensure: 'directory') } end context 'when not agreeing to the TOS' do let(:params) { { agree_tos: false } } it { is_expected.to raise_error Puppet::Error, %r{You must agree to the Let's Encrypt Terms of Service} } end context 'with renew' do describe 'pre hook' do let(:additional_params) { { config_dir: '/etc/letsencrypt', renew_pre_hook_commands: ['FooBar'] } } it { is_expected.to contain_letsencrypt__hook('renew-pre').with_hook_file('/etc/letsencrypt/renewal-hooks-puppet/renew-pre.sh') } end describe 'post hook' do let(:additional_params) { { config_dir: '/etc/letsencrypt', renew_post_hook_commands: ['FooBar'] } } it { is_expected.to contain_letsencrypt__hook('renew-post').with_hook_file('/etc/letsencrypt/renewal-hooks-puppet/renew-post.sh') } end describe 'deploy hook' do let(:additional_params) { { config_dir: '/etc/letsencrypt', renew_deploy_hook_commands: ['FooBar'] } } it { is_expected.to contain_letsencrypt__hook('renew-deploy').with_hook_file('/etc/letsencrypt/renewal-hooks-puppet/renew-deploy.sh') } end describe 'renew_cron_ensure' do let(:additional_params) do - { install_method: 'package', - package_command: 'certbot', - renew_cron_ensure: 'present', + { renew_cron_ensure: 'present', renew_cron_hour: 0, renew_cron_minute: 0 } end it do is_expected.to contain_cron('letsencrypt-renew'). with(ensure: 'present', command: 'certbot renew -q', hour: 0, minute: 0, monthday: '*') end end describe 'renew_cron_ensure and renew_cron_monthday' do let(:additional_params) { { renew_cron_ensure: 'present', renew_cron_monthday: [1, 15] } } it { is_expected.to contain_cron('letsencrypt-renew').with_ensure('present').with_monthday([1, 15]) } end describe 'renew_cron_ensure and hooks' do let(:additional_params) do { config_dir: '/etc/letsencrypt', - install_method: 'package', - package_command: 'certbot', renew_cron_ensure: 'present', renew_pre_hook_commands: ['PreBar'], renew_post_hook_commands: ['PostBar'], renew_deploy_hook_commands: ['DeployBar'] } end it do is_expected.to contain_cron('letsencrypt-renew'). with(ensure: 'present', command: 'certbot renew -q --pre-hook "/etc/letsencrypt/renewal-hooks-puppet/renew-pre.sh" --post-hook "/etc/letsencrypt/renewal-hooks-puppet/renew-post.sh" --deploy-hook "/etc/letsencrypt/renewal-hooks-puppet/renew-deploy.sh"') end end describe 'renew_cron_ensure and additional args' do let(:additional_params) do - { install_method: 'package', - package_command: 'certbot', - renew_cron_ensure: 'present', + { renew_cron_ensure: 'present', renew_additional_args: ['AdditionalBar'] } end it do is_expected.to contain_cron('letsencrypt-renew'). with(ensure: 'present', command: 'certbot renew -q AdditionalBar') end end end # context 'with renew' end # context 'when specifying an email address with the email parameter' context 'when specifying an email in $config' do let(:params) { { config: { 'email' => 'foo@example.com' } } } it { is_expected.to compile.with_all_deps } case facts[:operatingsystem] when 'FreeBSD' it { is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini email foo@example.com') } else it { is_expected.to contain_ini_setting('/etc/letsencrypt/cli.ini email foo@example.com') } end end context 'when not specifying the email parameter or an email key in $config' do context 'with unsafe_registration set to false' do it { is_expected.to raise_error Puppet::Error, %r{Please specify an email address} } end context 'with unsafe_registration set to true' do let(:params) { { unsafe_registration: true } } case facts[:operatingsystem] when 'FreeBSD' it { is_expected.not_to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini email foo@example.com') } it { is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini register-unsafely-without-email true') } else it { is_expected.not_to contain_ini_setting('/etc/letsencrypt/cli.ini email foo@example.com') } it { is_expected.to contain_ini_setting('/etc/letsencrypt/cli.ini register-unsafely-without-email true') } end end end end end end diff --git a/spec/defines/letsencrypt_certonly_spec.rb b/spec/defines/letsencrypt_certonly_spec.rb index 63028b7..b4e61bc 100644 --- a/spec/defines/letsencrypt_certonly_spec.rb +++ b/spec/defines/letsencrypt_certonly_spec.rb @@ -1,461 +1,461 @@ require 'spec_helper' describe 'letsencrypt::certonly' do on_supported_os.each do |os, facts| context "on #{os} based operating systems" do let :facts do facts end let(:pre_condition) { "class { letsencrypt: email => 'foo@example.com', package_command => 'letsencrypt' }" } # FreeBSD uses a different filesystem path pathprefix = facts[:kernel] == 'FreeBSD' ? '/usr/local' : '' context 'with a single domain' do let(:title) { 'foo.example.com' } it { is_expected.to compile.with_all_deps } it { is_expected.to contain_class('Letsencrypt::Install') } it { is_expected.to contain_class('Letsencrypt::Config') } if facts[:osfamily] == 'FreeBSD' it { is_expected.to contain_file('/usr/local/etc/letsencrypt') } it { is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini email foo@example.com') } it { is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini server https://acme-v02.api.letsencrypt.org/directory') } else it { is_expected.to contain_file('/etc/letsencrypt') } it { is_expected.to contain_package('letsencrypt') } it { is_expected.to contain_ini_setting('/etc/letsencrypt/cli.ini email foo@example.com') } it { is_expected.to contain_ini_setting('/etc/letsencrypt/cli.ini server https://acme-v02.api.letsencrypt.org/directory') } end it { is_expected.to contain_exec('initialize letsencrypt') } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com') } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_unless "/usr/local/sbin/letsencrypt-domain-validation #{pathprefix}/etc/letsencrypt/live/foo.example.com/cert.pem 'foo.example.com'" } end context 'with ensure absent' do let(:title) { 'foo.example.com' } let(:params) { { ensure: 'absent' } } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com') } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive delete --cert-name 'foo.example.com'" } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_onlyif "/usr/local/sbin/letsencrypt-domain-validation #{pathprefix}/etc/letsencrypt/live/foo.example.com/cert.pem 'foo.example.com'" } end context 'with multiple domains' do let(:title) { 'foo' } let(:params) { { domains: ['foo.example.com', 'bar.example.com', '*.example.com'] } } it { is_expected.to compile.with_all_deps } it { is_expected.to contain_exec('letsencrypt certonly foo').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo' -d 'foo.example.com' -d 'bar.example.com' -d '*.example.com'" } end context 'with custom cert-name' do let(:title) { 'foo' } let(:params) { { cert_name: 'bar.example.com' } } it { is_expected.to compile.with_all_deps } it { is_expected.to contain_exec('letsencrypt certonly foo').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'bar.example.com' -d 'foo'" } end context 'with custom command' do let(:title) { 'foo.example.com' } let(:params) { { letsencrypt_command: '/usr/lib/letsencrypt/letsencrypt-auto' } } it { is_expected.to compile.with_all_deps } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command '/usr/lib/letsencrypt/letsencrypt-auto --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name \'foo.example.com\' -d \'foo.example.com\'' } end context 'with webroot plugin' do let(:title) { 'foo.example.com' } let(:params) do { plugin: 'webroot', webroot_paths: ['/var/www/foo'] } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a webroot --cert-name 'foo.example.com' --webroot-path /var/www/foo -d 'foo.example.com'" } end context 'with webroot plugin and multiple domains' do let(:title) { 'foo' } let(:params) do { domains: ['foo.example.com', 'bar.example.com'], plugin: 'webroot', webroot_paths: ['/var/www/foo', '/var/www/bar'] } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_exec('letsencrypt certonly foo').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a webroot --cert-name 'foo' --webroot-path /var/www/foo -d 'foo.example.com' --webroot-path /var/www/bar -d 'bar.example.com'" } end context 'with webroot plugin, one webroot, and multiple domains' do let(:title) { 'foo' } let(:params) do { domains: ['foo.example.com', 'bar.example.com'], plugin: 'webroot', webroot_paths: ['/var/www/foo'] } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_exec('letsencrypt certonly foo').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a webroot --cert-name 'foo' --webroot-path /var/www/foo -d 'foo.example.com' -d 'bar.example.com'" } end context 'with webroot plugin and no webroot_paths' do let(:title) { 'foo.example.com' } let(:params) { { plugin: 'webroot' } } it { is_expected.not_to compile.with_all_deps } it { is_expected.to raise_error Puppet::Error, %r{'webroot_paths' parameter must be specified} } end context 'with dns-rfc2136 plugin' do let(:title) { 'foo.example.com' } let(:params) { { plugin: 'dns-rfc2136', letsencrypt_command: 'letsencrypt' } } let(:pre_condition) do <<-PUPPET class { 'letsencrypt': email => 'foo@example.com', config_dir => '/etc/letsencrypt', } class { 'letsencrypt::plugin::dns_rfc2136': server => '192.0.2.1', key_name => 'certbot', key_secret => 'secret', package_name => 'irrelevant', } PUPPET end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_class('letsencrypt::plugin::dns_rfc2136') } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a dns-rfc2136 --cert-name 'foo.example.com' -d 'foo.example.com' --dns-rfc2136-credentials /etc/letsencrypt/dns-rfc2136.ini --dns-rfc2136-propagation-seconds 10" } end context 'with dns-route53 plugin' do let(:title) { 'foo.example.com' } let(:params) { { plugin: 'dns-route53', letsencrypt_command: 'letsencrypt' } } let(:pre_condition) do <<-PUPPET class { 'letsencrypt': email => 'foo@example.com', config_dir => '/etc/letsencrypt', } class { 'letsencrypt::plugin::dns_route53': package_name => 'irrelevant', } PUPPET end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_class('letsencrypt::plugin::dns_route53') } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a dns-route53 --cert-name 'foo.example.com' -d 'foo.example.com' --dns-route53-propagation-seconds 10" } end context 'with custom plugin' do let(:title) { 'foo.example.com' } let(:params) { { plugin: 'apache' } } it { is_expected.to compile.with_all_deps } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a apache --cert-name 'foo.example.com' -d 'foo.example.com'" } end context 'with custom plugin and manage_cron' do let(:title) { 'foo.example.com' } let(:params) do { plugin: 'apache', manage_cron: true } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_command('"/var/lib/puppet/letsencrypt/renew-foo.example.com.sh"').with_ensure('present') } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a apache --cert-name 'foo.example.com' -d 'foo.example.com'\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a apache --cert-name 'foo.example.com' -d 'foo.example.com'\n") } end context 'with hook' do context 'pre' do let(:title) { 'foo.example.com' } let(:params) { { config_dir: '/etc/letsencrypt', pre_hook_commands: ['FooBar'] } } it do is_expected.to compile.with_all_deps is_expected.to contain_letsencrypt__hook('foo.example.com-pre').with_hook_file('/etc/letsencrypt/renewal-hooks-puppet/foo.example.com-pre.sh') end end context 'pre with wildcard domain' do let(:title) { '*.example.com' } let(:params) { { config_dir: '/etc/letsencrypt', pre_hook_commands: ['FooBar'] } } it do is_expected.to compile.with_all_deps is_expected.to contain_letsencrypt__hook('*.example.com-pre').with_hook_file('/etc/letsencrypt/renewal-hooks-puppet/example.com-pre.sh') end end context 'post' do let(:title) { 'foo.example.com' } let(:params) { { config_dir: '/etc/letsencrypt', post_hook_commands: ['FooBar'] } } it do is_expected.to compile.with_all_deps is_expected.to contain_letsencrypt__hook('foo.example.com-post').with_hook_file('/etc/letsencrypt/renewal-hooks-puppet/foo.example.com-post.sh') end end context 'deploy' do let(:title) { 'foo.example.com' } let(:params) { { config_dir: '/etc/letsencrypt', deploy_hook_commands: ['FooBar'] } } it do is_expected.to compile.with_all_deps is_expected.to contain_letsencrypt__hook('foo.example.com-deploy').with_hook_file('/etc/letsencrypt/renewal-hooks-puppet/foo.example.com-deploy.sh') end end end # context 'with hook' 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 compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_hour(13).with_ensure('present') } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --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.not_to compile.with_all_deps } 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 compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_hour('00').with_ensure('present') } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --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 compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_hour([1, 13]).with_ensure('present') } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --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 compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_minute(15).with_ensure('present') } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --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.not_to compile.with_all_deps } 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 compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_minute('15').with_ensure('present') } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --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 compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_minute([0, 30]).with_ensure('present') } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n") } end context 'with manage_cron and ensure absent' do let(:title) { 'foo.example.com' } let(:params) do { ensure: 'absent', manage_cron: true } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_ensure('absent') } it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('absent') } end context 'with custom puppet_vardir path and manage_cron' do let :facts do super().merge(puppet_vardir: '/tmp/custom_vardir') end let(:title) { 'foo.example.com' } let(:params) do { plugin: 'apache', manage_cron: true } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_file('/tmp/custom_vardir/letsencrypt').with_ensure('directory') } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_command '"/tmp/custom_vardir/letsencrypt/renew-foo.example.com.sh"' } - it { is_expected.to contain_file('/tmp/custom_vardir/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a apache --cert-name 'foo.example.com' -d 'foo.example.com'\n") } + it { is_expected.to contain_file('/tmp/custom_vardir/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a apache --cert-name 'foo.example.com' -d 'foo.example.com'\n") } end context 'with custom plugin and manage cron and cron_success_command' do let(:title) { 'foo.example.com' } let(:params) do { plugin: 'apache', manage_cron: true, cron_before_command: 'echo before', cron_success_command: 'echo success' } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_command '"/var/lib/puppet/letsencrypt/renew-foo.example.com.sh"' } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\n(echo before) && letsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a apache --cert-name 'foo.example.com' -d 'foo.example.com' && (echo success)\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\n(echo before) && letsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a apache --cert-name 'foo.example.com' -d 'foo.example.com' && (echo success)\n") } end context 'without plugin' do let(:title) { 'foo.example.com' } let(:params) { { custom_plugin: true } } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 --cert-name 'foo.example.com' -d 'foo.example.com'" } end context 'with invalid plugin' do let(:title) { 'foo.example.com' } let(:params) { { plugin: 'bad' } } it { is_expected.not_to compile.with_all_deps } it { is_expected.to raise_error Puppet::Error } end context 'when specifying additional arguments' do let(:title) { 'foo.example.com' } let(:params) { { additional_args: ['--foo bar', '--baz quux'] } } it { is_expected.to compile.with_all_deps } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com' --foo bar --baz quux" } end describe 'when specifying custom environment variables' do let(:title) { 'foo.example.com' } let(:params) { { environment: ['FOO=bar', 'FIZZ=buzz'] } } it { is_expected.to compile.with_all_deps } - it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_environment(['VENV_PATH=/opt/letsencrypt/.venv', 'FOO=bar', 'FIZZ=buzz']) } + it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_environment(['FOO=bar', 'FIZZ=buzz']) } end context 'with custom environment variables and manage_cron' do let(:title) { 'foo.example.com' } let(:params) { { environment: ['FOO=bar', 'FIZZ=buzz'], manage_cron: true } } it { is_expected.to compile.with_all_deps } - 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\nexport FOO=bar\nexport FIZZ=buzz\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n" } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_content "#!/bin/sh\nexport FOO=bar\nexport FIZZ=buzz\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n" } end context 'with manage cron and suppress_cron_output' do\ let(:title) { 'foo.example.com' } let(:params) do { manage_cron: true, suppress_cron_output: true } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with_command('"/var/lib/puppet/letsencrypt/renew-foo.example.com.sh"').with_ensure('present') } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com' > /dev/null 2>&1\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com' > /dev/null 2>&1\n") } end context 'with manage cron and custom day of month' do let(:title) { 'foo.example.com' } let(:params) do { manage_cron: true, cron_monthday: [1, 15] } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_cron('letsencrypt renew cron foo.example.com').with(monthday: [1, 15]).with_ensure('present') } - it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nexport VENV_PATH=/opt/letsencrypt/.venv\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n") } + it { is_expected.to contain_file('/var/lib/puppet/letsencrypt/renew-foo.example.com.sh').with_ensure('file').with_content("#!/bin/sh\nletsencrypt --keep-until-expiring --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a standalone --cert-name 'foo.example.com' -d 'foo.example.com'\n") } end context 'with custom config_dir' do let(:title) { 'foo.example.com' } let(:pre_condition) { "class { letsencrypt: email => 'foo@example.com', config_dir => '/foo/bar/baz', package_command => 'letsencrypt'}" } it { is_expected.to compile.with_all_deps } it { is_expected.to contain_file('/foo/bar/baz').with_ensure('directory') } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_unless '/usr/local/sbin/letsencrypt-domain-validation /foo/bar/baz/live/foo.example.com/cert.pem \'foo.example.com\'' } end context 'on FreeBSD', if: facts[:os]['name'] == 'FreeBSD' do let(:title) { 'foo.example.com' } let(:pre_condition) { "class { letsencrypt: email => 'foo@example.com'}" } it { is_expected.to compile.with_all_deps } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command %r{^certbot} } it { is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini email foo@example.com') } it { is_expected.to contain_ini_setting('/usr/local/etc/letsencrypt/cli.ini server https://acme-v02.api.letsencrypt.org/directory') } it { is_expected.to contain_file('/usr/local/etc/letsencrypt').with_ensure('directory') } it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_unless '/usr/local/sbin/letsencrypt-domain-validation /usr/local/etc/letsencrypt/live/foo.example.com/cert.pem \'foo.example.com\'' } end end end end diff --git a/templates/renew-script.sh.erb b/templates/renew-script.sh.erb index 8ff4b80..a77cde5 100644 --- a/templates/renew-script.sh.erb +++ b/templates/renew-script.sh.erb @@ -1,5 +1,5 @@ #!/bin/sh -<%- @execution_environment.each do |environment| -%> +<%- @environment.each do |environment| -%> export <%= environment %> <%- end -%> <%= @cron_cmd %>