diff --git a/lib/puppet/parser/functions/docker_params_changed.rb b/lib/puppet/functions/docker_params_changed.rb similarity index 51% rename from lib/puppet/parser/functions/docker_params_changed.rb rename to lib/puppet/functions/docker_params_changed.rb index 325cbc7..9cb8c36 100644 --- a/lib/puppet/parser/functions/docker_params_changed.rb +++ b/lib/puppet/functions/docker_params_changed.rb @@ -1,78 +1,157 @@ # frozen_string_literal: true -require 'open3' -require 'json' +Puppet::Functions.create_function(:docker_params_changed) do + dispatch :detect_changes do + param 'Hash', :opts + return_type 'String' + end + + def run_with_powershell(cmd) + "powershell.exe -Command \"& {#{cmd}}\" " + end -module Puppet::Parser::Functions - # Checks if at least one parammeter is changed - newfunction(:docker_params_changed, type: :rvalue) do |args| - opts = args[0] || {} - return_value = [] + def remove_cidfile(cidfile, osfamily) + delete_command = if osfamily == 'windows' + run_with_powershell("del #{cidfile}") + else + "rm -f #{cidfile}" + end + _stdout, _stderr, _status = Open3.capture3(delete_command) + end + + def start_container(name, osfamily) + start_command = if osfamily == 'windows' + run_with_powershell("docker start #{name}") + else + "docker start #{name}" + end + _stdout, _stderr, _status = Open3.capture3(start_command) + end + + def stop_container(name, osfamily) + stop_command = if osfamily == 'windows' + run_with_powershell("docker stop #{name}") + else + "docker stop #{name}" + end + _stdout, _stderr, _status = Open3.capture3(stop_command) + end + + def remove_container(name, osfamily, stop_wait_time, cidfile) + stop_command = if osfamily == 'windows' + run_with_powershell("docker stop --time=#{stop_wait_time} #{name}") + else + "docker stop --time=#{stop_wait_time} #{name}" + end + _stdout, _stderr, _status = Open3.capture3(stop_command) + + remove_command = if osfamily == 'windows' + run_with_powershell("docker rm -v #{name}") + else + "docker rm -v #{name}" + end + _stdout, _stderr, _status = Open3.capture3(remove_command) + + remove_cidfile(cidfile, osfamily) + end + + def create_container(cmd, osfamily, image) + pull_command = if osfamily == 'windows' + run_with_powershell("docker pull #{image} -q") + else + "docker pull #{image} -q" + end + _stdout, _stderr, _status = Open3.capture3(pull_command) + + create_command = if osfamily == 'windows' + run_with_powershell(cmd) + else + cmd + end + _stdout, _stderr, _status = Open3.capture3(create_command) + end + + def detect_changes(opts) + require 'open3' + require 'json' + return_value = 'No changes detected' if opts['sanitised_title'] && opts['osfamily'] stdout, stderr, status = Open3.capture3("docker inspect #{opts['sanitised_title']}") if stderr.to_s == '' && status.to_s.include?('exit 0') param_changed = false inspect_hash = JSON.parse(stdout)[0] # check if the image was changed param_changed = true if opts['image'] && opts['image'] != inspect_hash['Config']['Image'] # check if something on volumes or mounts was changed(a new volume/mount was added or removed) param_changed = true if opts['volumes'].is_a?(String) && opts['volumes'].include?(':') && opts['volumes'] != inspect_hash['Mounts'].to_a[0] && opts['osfamily'] != 'windows' param_changed = true if opts['volumes'].is_a?(String) && !opts['volumes'].include?(':') && opts['volumes'] != inspect_hash['Config']['Volumes'].to_a[0] && opts['osfamily'] != 'windows' param_changed = true if opts['volumes'].is_a?(String) && opts['volumes'].scan(%r{(?=:)}).count == 2 && opts['volumes'] != inspect_hash['Mounts'].to_a[0] && opts['osfamily'] == 'windows' param_changed = if opts['volumes'].is_a?(String) && opts['volumes'].scan(%r{(?=:)}).count == 1 && opts['volumes'] != inspect_hash['Config']['Volumes'].to_a[0] && opts['osfamily'] == 'windows' true else param_changed end pp_paths = opts['volumes'].reject { |item| item.include?(':') } if opts['volumes'].is_a?(Array) && opts['osfamily'] != 'windows' pp_mounts = opts['volumes'].select { |item| item.include?(':') } if opts['volumes'].is_a?(Array) && opts['osfamily'] != 'windows' pp_paths = opts['volumes'].select { |item| item.scan(%r{(?=:)}).count == 1 } if opts['volumes'].is_a?(Array) && opts['osfamily'] == 'windows' pp_mounts = opts['volumes'].select { |item| item.scan(%r{(?=:)}).count == 2 } if opts['volumes'].is_a?(Array) && opts['osfamily'] == 'windows' inspect_paths = if inspect_hash['Config']['Volumes'] inspect_hash['Config']['Volumes'].keys else [] end param_changed = true if pp_paths != inspect_paths names = inspect_hash['Mounts'].map { |item| item.values[1] } if inspect_hash['Mounts'] pp_names = pp_mounts.map { |item| item.split(':')[0] } if pp_mounts names = names.select { |item| pp_names.include?(item) } if names && pp_names destinations = inspect_hash['Mounts'].map { |item| item.values[3] } if inspect_hash['Mounts'] pp_destinations = pp_mounts.map { |item| item.split(':')[1] } if pp_mounts && opts['osfamily'] != 'windows' pp_destinations = pp_mounts.map { |item| "#{item.split(':')[1].downcase}:#{item.split(':')[2]}" } if pp_mounts && opts['osfamily'] == 'windows' destinations = destinations.select { |item| pp_destinations.include?(item) } if destinations && pp_destinations param_changed = true if pp_names != names param_changed = true if pp_destinations != destinations param_changed = true if pp_mounts != [] && inspect_hash['Mounts'].nil? # check if something on ports was changed(some ports were added or removed) ports = inspect_hash['HostConfig']['PortBindings'].keys ports = ports.map { |item| item.split('/')[0] } pp_ports = opts['ports'].sort if opts['ports'].is_a?(Array) pp_ports = [opts['ports']] if opts['ports'].is_a?(String) param_changed = true if pp_ports && pp_ports != ports - return_value << if param_changed - 'PARAM_CHANGED' - else - 'NO_CHANGE' - end + if param_changed + remove_container(opts['sanitised_title'], opts['osfamily'], opts['stop_wait_time'], opts['cidfile']) + create_container(opts['command'], opts['osfamily'], opts['image']) + return_value = 'Param changed' + end else - return_value << 'CONTAINER_NOT_FOUND' + create_container(opts['command'], opts['osfamily'], opts['image']) unless File.exist?(opts['cidfile']) + _stdout, _stderr, status = Open3.capture3("docker inspect #{opts['sanitised_title']}") + unless status.to_s.include?('exit 0') + remove_cidfile(opts['cidfile'], opts['osfamily']) + create_container(opts['command'], opts['osfamily'], opts['image']) + end + return_value = 'No changes detected' end else - return_value << 'ARG_REQUIRED_MISSING' + return_value = 'Arg required missing' + end + + if opts['container_running'] + start_container(opts['sanitised_title'], opts['osfamily']) + else + stop_container(opts['sanitised_title'], opts['osfamily']) end - return_value.flatten.join(' ') + return_value end end diff --git a/manifests/run.pp b/manifests/run.pp index e959993..147cac8 100644 --- a/manifests/run.pp +++ b/manifests/run.pp @@ -1,722 +1,690 @@ # @summary # A define which manages a running docker container. # # @param restart # Sets a restart policy on the docker run. # Note: If set, puppet will NOT setup an init script to manage, instead # it will do a raw docker run command using a CID file to track the container # ID. # # If you want a normal named container with an init script and a restart policy # you must use the extra_parameters feature and pass it in like this: # # extra_parameters => ['--restart=always'] # # However, if your system is using sytemd this restart policy will be # ineffective because the ExecStop commands will run which will cause # docker to stop restarting it. In this case you should use the # systemd_restart option to specify the policy you want. # # This will allow the docker container to be restarted if it dies, without # puppet help. # # @param verify_digest # (optional) Make sure, that the image has not modified. Compares the digest # checksum before starting the docker image. # To get the digest of an image, run the following command: # docker image inspect <> --format='{{index .RepoDigests 0}} # # @param service_prefix # (optional) The name to prefix the startup script with and the Puppet # service resource title with. Default: 'docker-' # # @param restart_service # (optional) Whether or not to restart the service if the the generated init # script changes. Default: true # # @param restart_service_on_docker_refresh # Whether or not to restart the service if the docker service is restarted. # Only has effect if the docker_service parameter is set. # Default: true # # @param manage_service # (optional) Whether or not to create a puppet Service resource for the init # script. Disabling this may be useful if integrating with existing modules. # Default: true # # @param docker_service # (optional) If (and how) the Docker service itself is managed by Puppet # true -> Service['docker'] # false -> no Service dependency # anything else -> Service[docker_service] # Default: false # # @param health_check_cmd # (optional) Specifies the command to execute to check that the container is healthy using the docker health check functionality. # Default: undef # # @param health_check_interval # (optional) Specifies the interval that the health check command will execute in seconds. # Default: undef # # @param restart_on_unhealthy # (optional) Checks the health status of Docker container and if it is unhealthy the service will be restarted. # The health_check_cmd parameter must be set to true to use this functionality. # Default: undef # # @param net # # The docker network to attach to a container. # Can be a String or Array (if using multiple networks) # Default: bridge # # @param extra_parameters # An array of additional command line arguments to pass to the `docker run` # command. Useful for adding additional new or experimental options that the # module does not yet support. # # @param systemd_restart # (optional) If the container is to be managed by a systemd unit file set the # Restart option on the unit file. Can be any valid value for this systemd # configuration. Most commonly used are on-failure or always. # Default: on-failure # # @param custom_unless # (optional) Specify an additional unless for the Docker run command when using restart. # Default: undef # # @param after_create # (optional) Specifies the command to execute after container is created but before it is started. # Default: undef # # @param remain_after_exit # (optional) If the container is to be managed by a systemd unit file set the # RemainAfterExit option on the unit file. Can be any valid value for this systemd # configuration. # Default: Not included in unit file # # @param prepare_service_only # (optional) Prepare the service and enable it as usual but do not run it right away. # Useful when building VM images using masterless Puppet and then letting the Docker images # to be downloaded when a new VM is created. # Default: false # # @param image # # @param ensure # # @param command # # @param memory_limit # # @param cpuset # # @param ports # # @param labels # # @param expose # # @param volumes # # @param links # # @param use_name # # @param running # # @param volumes_from # # @param username # # @param hostname # # @param env # # @param env_file # # @param dns # # @param dns_search # # @param lxc_conf # # @param service_provider # # @param disable_network # # @param privileged # # @param detach # # @param extra_systemd_parameters # # @param pull_on_start # # @param after # # @param after_service # # @param depends # # @param depend_services # # @param tty # # @param socket_connect # # @param hostentries # # @param before_start # # @param before_stop # # @param after_start # # @param after_stop # # @param remove_container_on_start # # @param remove_container_on_stop # # @param remove_volume_on_start # # @param remove_volume_on_stop # # @param stop_wait_time # # @param syslog_identifier # # @param read_only # define docker::run( Optional[Pattern[/^[\S]*$/]] $image, Optional[Enum[present,absent]] $ensure = 'present', Optional[String] $verify_digest = undef, Optional[String] $command = undef, Optional[Pattern[/^[\d]*(b|k|m|g)$/]] $memory_limit = '0b', Variant[String,Array,Undef] $cpuset = [], Variant[String,Array,Undef] $ports = [], Variant[String,Array,Undef] $labels = [], Variant[String,Array,Undef] $expose = [], Variant[String,Array,Undef] $volumes = [], Variant[String,Array,Undef] $links = [], Optional[Boolean] $use_name = false, Optional[Boolean] $running = true, Optional[Variant[String,Array]] $volumes_from = [], Variant[String,Array] $net = 'bridge', Variant[String,Boolean] $username = false, Variant[String,Boolean] $hostname = false, Optional[Variant[String,Array]] $env = [], Optional[Variant[String,Array]] $env_file = [], Optional[Variant[String,Array]] $dns = [], Optional[Variant[String,Array]] $dns_search = [], Optional[Variant[String,Array]] $lxc_conf = [], Optional[String] $service_prefix = 'docker-', Optional[String] $service_provider = undef, Optional[Boolean] $restart_service = true, Optional[Boolean] $restart_service_on_docker_refresh = true, Optional[Boolean] $manage_service = true, Variant[String,Boolean] $docker_service = false, Optional[Boolean] $disable_network = false, Optional[Boolean] $privileged = false, Optional[Boolean] $detach = undef, Optional[Variant[String,Array[String]]] $extra_parameters = undef, Optional[String] $systemd_restart = 'on-failure', Optional[Variant[String,Hash]] $extra_systemd_parameters = {}, Optional[Boolean] $pull_on_start = false, Optional[Variant[String,Array]] $after = [], Optional[Variant[String,Array]] $after_service = [], Optional[Variant[String,Array]] $depends = [], Optional[Variant[String,Array]] $depend_services = ['docker.service'], Optional[Boolean] $tty = false, Optional[Variant[String,Array]] $socket_connect = [], Optional[Variant[String,Array]] $hostentries = [], Optional[String] $restart = undef, Variant[String,Boolean] $before_start = false, Variant[String,Boolean] $before_stop = false, Variant[String,Boolean] $after_start = false, Variant[String,Boolean] $after_stop = false, Optional[String] $after_create = undef, Optional[Boolean] $remove_container_on_start = true, Optional[Boolean] $remove_container_on_stop = true, Optional[Boolean] $remove_volume_on_start = false, Optional[Boolean] $remove_volume_on_stop = false, Optional[Integer] $stop_wait_time = 0, Optional[String] $syslog_identifier = undef, Optional[Boolean] $read_only = false, Optional[String] $health_check_cmd = undef, Optional[Boolean] $restart_on_unhealthy = false, Optional[Integer] $health_check_interval = undef, Optional[Variant[String,Array]] $custom_unless = [], Optional[String] $remain_after_exit = undef, Optional[Boolean] $prepare_service_only = false, ) { include docker::params if ($socket_connect != []) { $sockopts = join(any2array($socket_connect), ',') $docker_command = "${docker::params::docker_command} -H ${sockopts}" } else { $docker_command = $docker::params::docker_command } $service_name = $docker::service_name $docker_group = $docker::docker_group if $restart { assert_type(Pattern[/^(no|always|unless-stopped|on-failure)|^on-failure:[\d]+$/], $restart) } if ($remove_volume_on_start and !$remove_container_on_start) { fail(translate("In order to remove the volume on start for ${title} you need to also remove the container")) } if ($remove_volume_on_stop and !$remove_container_on_stop) { fail(translate("In order to remove the volume on stop for ${title} you need to also remove the container")) } if $use_name { notify { "docker use_name warning: ${title}": message => 'The use_name parameter is no-longer required and will be removed in a future release', withpath => true, } } if $systemd_restart { assert_type(Pattern[/^(no|always|on-success|on-failure|on-abnormal|on-abort|on-watchdog)$/], $systemd_restart) } $service_provider_real = $service_provider ? { undef => $docker::params::service_provider, default => $service_provider, } if $detach == undef { $valid_detach = $service_provider_real ? { 'systemd' => false, default => $docker::params::detach_service_in_init, } } else { $valid_detach = $detach } $extra_parameters_array = any2array($extra_parameters) $after_array = any2array($after) $depends_array = any2array($depends) $depend_services_array = any2array($depend_services) $docker_run_flags = docker_run_flags( { cpuset => any2array($cpuset), disable_network => $disable_network, dns => any2array($dns), dns_search => any2array($dns_search), env => any2array($env), env_file => any2array($env_file), expose => any2array($expose), extra_params => any2array($extra_parameters), hostentries => any2array($hostentries), hostname => $hostname, links => any2array($links), lxc_conf => any2array($lxc_conf), memory_limit => $memory_limit, net => $net, ports => any2array($ports), labels => any2array($labels), privileged => $privileged, socket_connect => any2array($socket_connect), tty => $tty, username => $username, volumes => any2array($volumes), volumes_from => any2array($volumes_from), read_only => $read_only, health_check_cmd => $health_check_cmd, restart_on_unhealthy => $restart_on_unhealthy, health_check_interval => $health_check_interval, osfamily => $facts['os']['family'], } ) $sanitised_title = docker::sanitised_name($title) if empty($depends_array) { $sanitised_depends_array = [] } else { $sanitised_depends_array = docker::sanitised_name($depends_array) } if empty($after_array) { $sanitised_after_array = [] } else { $sanitised_after_array = docker::sanitised_name($after_array) } if $facts['os']['family'] == 'windows' { $exec_environment = "PATH=${::docker_program_files_path}/Docker/;${::docker_systemroot}/System32/" $exec_timeout = 3000 $exec_path = ["${::docker_program_files_path}/Docker/"] $exec_provider = 'powershell' $cidfile = "${::docker_user_temp_path}/${service_prefix}${sanitised_title}.cid" $restart_check = "${docker_command} inspect ${sanitised_title} -f '{{ if eq \\\"unhealthy\\\" .State.Health.Status }} {{ .Name }}{{ end }}' | findstr ${sanitised_title}" # lint:ignore:140chars $container_running_check = "\$state = ${docker_command} inspect ${sanitised_title} -f \"{{ .State.Running }}\"; if (\$state -ieq \"true\") { Exit 0 } else { Exit 1 }" # lint:ignore:140chars } else { $exec_environment = 'HOME=/root' $exec_path = ['/bin', '/usr/bin'] $exec_timeout = 0 $exec_provider = undef $cidfile = "/var/run/${service_prefix}${sanitised_title}.cid" $restart_check = "${docker_command} inspect ${sanitised_title} -f '{{ if eq \"unhealthy\" .State.Health.Status }} {{ .Name }}{{ end }}' | grep ${sanitised_title}" # lint:ignore:140chars $container_running_check = "${docker_command} inspect ${sanitised_title} -f \"{{ .State.Running }}\" | grep true" # lint:ignore:140chars } if $restart_on_unhealthy { exec { "Restart unhealthy container ${title} with docker": command => "${docker_command} restart ${sanitised_title}", onlyif => $restart_check, environment => $exec_environment, path => $exec_path, provider => $exec_provider, timeout => $exec_timeout, } } if $restart { if $ensure == 'absent' { exec { "stop ${title} with docker": command => "${docker_command} stop --time=${stop_wait_time} ${sanitised_title}", onlyif => "${docker_command} inspect ${sanitised_title}", environment => $exec_environment, path => $exec_path, provider => $exec_provider, timeout => $exec_timeout, } exec { "remove ${title} with docker": command => "${docker_command} rm -v ${sanitised_title}", onlyif => "${docker_command} inspect ${sanitised_title}", environment => $exec_environment, path => $exec_path, provider => $exec_provider, timeout => $exec_timeout, } file { $cidfile: ensure => absent, } } else { $run_with_docker_command = [ "${docker_command} run -d ${docker_run_flags}", "--name ${sanitised_title} --cidfile=${cidfile}", "--restart=\"${restart}\" ${image} ${command}", ] $inspect = [ "${docker_command} inspect ${sanitised_title}", ] if $custom_unless { - $exec_unless = $custom_unless + $exec_unless = concat($custom_unless, $inspect) } else { $exec_unless = $inspect } - $detect_changes = docker_params_changed( - { - sanitised_title => $sanitised_title, - osfamily => $facts['os']['family'], - image => $image, - volumes => $volumes, - ports => $ports, + if versioncmp($facts['puppetversion'], '6') < 0 { + exec { "run ${title} with docker": + command => join($run_with_docker_command, ' '), + unless => $exec_unless, + environment => $exec_environment, + path => $exec_path, + provider => $exec_provider, + timeout => $exec_timeout, } - ) - case $detect_changes { - 'CONTAINER_NOT_FOUND': { - exec { "run ${title} with docker": - command => join($run_with_docker_command, ' '), - unless => $exec_unless, - environment => $exec_environment, - path => $exec_path, - provider => $exec_provider, - timeout => $exec_timeout, - } - } - 'PARAM_CHANGED': { + if $running == false { exec { "stop ${title} with docker": command => "${docker_command} stop --time=${stop_wait_time} ${sanitised_title}", - onlyif => "${docker_command} inspect ${sanitised_title}", + onlyif => $container_running_check, environment => $exec_environment, path => $exec_path, provider => $exec_provider, timeout => $exec_timeout, } - - exec { "remove ${title} with docker": - command => "${docker_command} rm -v ${sanitised_title}", - onlyif => "${docker_command} inspect ${sanitised_title}", - environment => $exec_environment, - path => $exec_path, - provider => $exec_provider, - timeout => $exec_timeout, - } - - file { $cidfile: - ensure => absent, - } - - exec { "run ${title} with docker": - command => join($run_with_docker_command, ' '), - unless => $exec_unless, + } else { + exec { "start ${title} with docker": + command => "${docker_command} start ${sanitised_title}", + unless => $container_running_check, environment => $exec_environment, path => $exec_path, provider => $exec_provider, timeout => $exec_timeout, } } - 'ARG_REQUIRED_MISSING': { - fail(translate('sanitised_title and osfamily are required for docker_params_changed function')) - } - default: { - # Use ONLY on debugging - DO NOT UNCOMMENT OTHERWISE - # notify { 'this case should not be executed': - # message => "detect_changes: ${detect_changes}", - # withpath => true, - # } + } else { + $docker_params_changed_args = { + sanitised_title => $sanitised_title, + osfamily => $facts['os']['family'], + command => join($run_with_docker_command, ' '), + cidfile => $cidfile, + image => $image, + volumes => $volumes, + ports => $ports, + stop_wait_time => $stop_wait_time, + container_running => $running, + # logfile_path => ($facts['os']['family'] == 'windows') ? { + # true => ::docker_user_temp_path, + # default => '/tmp', + # }, } - } - if $running == false { - exec { "stop ${title} with docker": - command => "${docker_command} stop --time=${stop_wait_time} ${sanitised_title}", - onlyif => $container_running_check, - environment => $exec_environment, - path => $exec_path, - provider => $exec_provider, - timeout => $exec_timeout, - } - } else { - exec { "start ${title} with docker": - command => "${docker_command} start ${sanitised_title}", - unless => $container_running_check, - environment => $exec_environment, - path => $exec_path, - provider => $exec_provider, - timeout => $exec_timeout, + $detect_changes = Deferred('docker_params_changed', [$docker_params_changed_args]) + + notify { 'docker_params_changed': + message => $detect_changes, } } } } else { $docker_run_inline_start = template('docker/docker-run-start.erb') $docker_run_inline_stop = template('docker/docker-run-stop.erb') case $service_provider_real { 'systemd': { $initscript = "/etc/systemd/system/${service_prefix}${sanitised_title}.service" $startscript = "/usr/local/bin/docker-run-${sanitised_title}-start.sh" $stopscript = "/usr/local/bin/docker-run-${sanitised_title}-stop.sh" $startstop_template = 'docker/usr/local/bin/docker-run.sh.epp' $init_template = 'docker/etc/systemd/system/docker-run.erb' $mode = '0644' $hasstatus = true } 'upstart': { $initscript = "/etc/init.d/${service_prefix}${sanitised_title}" $init_template = 'docker/etc/init.d/docker-run.erb' $mode = '0750' $startscript = undef $stopscript = undef $startstop_template = undef $hasstatus = true } default: { if $facts['os']['family'] != 'windows' { fail(translate('Docker needs a Debian or RedHat based system.')) } elsif $ensure == 'present' { fail(translate('Restart parameter is required for Windows')) } $hasstatus = $::docker::params::service_hasstatus } } if $syslog_identifier { $_syslog_identifier = $syslog_identifier } else { $_syslog_identifier = "${service_prefix}${sanitised_title}" } if $ensure == 'absent' { if $facts['os']['family'] == 'windows'{ exec { "stop container ${service_prefix}${sanitised_title}": command => "${docker_command} stop --time=${stop_wait_time} ${sanitised_title}", onlyif => "${docker_command} inspect ${sanitised_title}", environment => $exec_environment, path => $exec_path, provider => $exec_provider, timeout => $exec_timeout, notify => Exec["remove container ${service_prefix}${sanitised_title}"], } } else { service { "${service_prefix}${sanitised_title}": ensure => false, enable => false, hasstatus => $hasstatus, provider => $service_provider_real, notify => Exec["remove container ${service_prefix}${sanitised_title}"], } } exec { "remove container ${service_prefix}${sanitised_title}": command => "${docker_command} rm -v ${sanitised_title}", onlyif => "${docker_command} inspect ${sanitised_title}", environment => $exec_environment, path => $exec_path, refreshonly => true, provider => $exec_provider, timeout => $exec_timeout, } if $facts['os']['family'] != 'windows' { file { "/etc/systemd/system/${service_prefix}${sanitised_title}.service": ensure => absent, } if ($startscript) { file { $startscript: ensure => absent, } } if ($stopscript) { file { $stopscript: ensure => absent, } } } else { file { $cidfile: ensure => absent, } } } else { if ($startscript) { file { $startscript: ensure => file, content => epp($startstop_template, {'script' => $docker_run_inline_start}), owner => 'root', group => $docker_group, mode => '0770', } } if ($stopscript) { file { $stopscript: ensure => file, content => epp($startstop_template, {'script' => $docker_run_inline_stop}), owner => 'root', group => $docker_group, mode => '0770', } } file { $initscript: ensure => file, content => template($init_template), owner => 'root', group => $docker_group, mode => $mode, } if $manage_service { if $running == false { service { "${service_prefix}${sanitised_title}": ensure => $running, enable => false, hasstatus => $hasstatus, require => File[$initscript], } } else { # Transition help from moving from CID based container detection to # Name-based container detection. See #222 for context. # This code should be considered temporary until most people have # transitioned. - 2015-04-15 if $initscript == "/etc/init.d/${service_prefix}${sanitised_title}" { # This exec sequence will ensure the old-style CID container is stopped # before we replace the init script with the new-style. $transition_onlyif = [ "/usr/bin/test -f /var/run/docker-${sanitised_title}.cid &&", "/usr/bin/test -f /etc/init.d/${service_prefix}${sanitised_title}", ] exec { "/bin/sh /etc/init.d/${service_prefix}${sanitised_title} stop": onlyif => join($transition_onlyif, ' '), require => [], } -> file { "/var/run/${service_prefix}${sanitised_title}.cid": ensure => absent, } -> File[$initscript] } service { "${service_prefix}${sanitised_title}": ensure => $running and !$prepare_service_only, enable => true, provider => $service_provider_real, hasstatus => $hasstatus, require => File[$initscript], } } if $docker_service { if $docker_service == true { Service['docker'] -> Service["${service_prefix}${sanitised_title}"] if $restart_service_on_docker_refresh == true { Service['docker'] ~> Service["${service_prefix}${sanitised_title}"] } } else { Service[$docker_service] -> Service["${service_prefix}${sanitised_title}"] if $restart_service_on_docker_refresh == true { Service[$docker_service] ~> Service["${service_prefix}${sanitised_title}"] } } } } if $service_provider_real == 'systemd' and !$prepare_service_only { exec { "docker-${sanitised_title}-systemd-reload": path => ['/bin/', '/sbin/', '/usr/bin/', '/usr/sbin/'], command => 'systemctl daemon-reload', refreshonly => true, require => [ File[$initscript], File[$startscript], File[$stopscript], ], subscribe => [ File[$initscript], File[$startscript], File[$stopscript], ], } Exec["docker-${sanitised_title}-systemd-reload"] -> Service <| title == "${service_prefix}${sanitised_title}" |> } if $restart_service { if $startscript or $stopscript { [ File[$initscript], File[$startscript], File[$stopscript], ] ~> Service <| title == "${service_prefix}${sanitised_title}" |> } else { [ File[$initscript], ] ~> Service <| title == "${service_prefix}${sanitised_title}" |> } } else { if $startscript or $stopscript { [ File[$initscript], File[$startscript], File[$stopscript], ] -> Service <| title == "${service_prefix}${sanitised_title}" |> } else { [ File[$initscript], ] -> Service <| title == "${service_prefix}${sanitised_title}" |> } } } } } diff --git a/spec/acceptance/docker_params_changed_spec.rb b/spec/acceptance/docker_params_changed_spec.rb index 622a702..664732a 100644 --- a/spec/acceptance/docker_params_changed_spec.rb +++ b/spec/acceptance/docker_params_changed_spec.rb @@ -1,143 +1,143 @@ # frozen_string_literal: true require 'spec_helper_acceptance' if os[:family] == 'windows' os_name = run_shell('systeminfo | findstr /R /C:"OS Name"') raise 'Could not retrieve systeminfo for Windows box' if os_name.exit_code != 0 os_name = os_name.stdout.split(%r{\s}).include?('2016') ? 'win-2016' : 'win-2019' docker_args = 'docker_ee => true' docker_network = 'nat' volume_location = 'C:\\' docker_image = if os_name == 'win-2016' 'stefanscherer/nanoserver:sac2016' else 'stefanscherer/nanoserver:10.0.17763.1040' end else docker_args = '' docker_network = 'bridge' volume_location = '/opt' docker_image = 'hello-world:linux' end -describe 'docker trigger parammeters change' do +describe 'docker trigger parameters change', if: fetch_puppet_version > 5 do before(:all) do if os[:family] != 'windows' install_pp = "class { 'docker': #{docker_args}}" apply_manifest(install_pp) end run_shell("mkdir #{volume_location}/volume_1") run_shell("mkdir #{volume_location}/volume_2") end context 'when image is changed' do image_changed = if os[:family] == 'windows' if os_name == 'win-2016' 'stefanscherer/nanoserver:10.0.14393.2551' else 'stefanscherer/nanoserver:1809' end else 'hello-world:latest' end let(:pp1) do " class {'docker': #{docker_args}} docker::run {'servercore': image => '#{docker_image}', restart => 'always', net => '#{docker_network}' } " end let(:pp2) do " class {'docker': #{docker_args}} docker::run {'servercore': image => '#{image_changed}', restart => 'always', net => '#{docker_network}' } " end it 'creates servercore with first image' do - idempotent_apply(pp1) + expect(docker_run_idempotent_apply(pp1)).to be true end it 'detect image change and apply the change' do apply_manifest(pp2, catch_failures: true) run_shell('docker inspect --format="{{ .Config.Image }}" servercore') do |r| expect(r.stdout).to match(%r{#{image_changed}}) end end end context 'when volumes parameter is changed' do if os[:family] == 'windows' volumes1 = "volumes => ['volume-1:C:\\volume_1']" volumes2 = "volumes => ['volume-1:C:\\volume_1', 'volume-2:C:\\volume_2']" else volumes1 = "volumes => ['volume-1:#{volume_location}/volume_1']" volumes2 = "volumes => ['volume-1:#{volume_location}/volume_1', 'volume-2:#{volume_location}/volume_2']" end let(:pp1) do " class {'docker': #{docker_args}} docker::run {'servercore': image => '#{docker_image}', restart => 'always', net => '#{docker_network}', #{volumes1}} " end let(:pp2) do " class {'docker': #{docker_args}} docker::run {'servercore': image => '#{docker_image}', restart => 'always', net => '#{docker_network}', #{volumes2}} " end it "creates servercore with #{volumes1}" do - idempotent_apply(pp1) + expect(docker_run_idempotent_apply(pp1)).to be true end it "creates servercore with #{volumes2}" do apply_manifest(pp2, catch_failures: true) run_shell('docker inspect servercore --format="{{ json .Mounts }}"') do |r| inspect_result = JSON.parse(r.stdout) inspect_result = inspect_result.map { |item| item['Name'] }.sort expect(inspect_result).to eq(['volume-1', 'volume-2']) end end end context 'when ports parameter is changed' do ports1 = "ports => ['4444']" ports2 = "ports => ['4444', '4445']" let(:pp1) do " class {'docker': #{docker_args}} docker::run {'servercore': image => '#{docker_image}', restart => 'always', net => '#{docker_network}', #{ports1}} " end let(:pp2) do " class {'docker': #{docker_args}} docker::run {'servercore': image => '#{docker_image}', restart => 'always', net => '#{docker_network}', #{ports2}} " end it 'creates servercore with ports => ["4444"]' do - idempotent_apply(pp1) + expect(docker_run_idempotent_apply(pp1)).to be true end it 'creates servercore with ports => ["4444", "4445"]' do apply_manifest(pp2, catch_failures: true) run_shell('docker inspect servercore --format="{{ json .HostConfig.PortBindings }}"') do |r| inspect_result = JSON.parse(r.stdout) inspect_result = inspect_result.keys.map { |item| item.split('/')[0] }.sort expect(inspect_result).to eq(['4444', '4445']) end end end after(:all) do run_shell("rm -r #{volume_location}/volume_1") run_shell("rm -r #{volume_location}/volume_2") end end diff --git a/spec/acceptance/docker_spec.rb b/spec/acceptance/docker_spec.rb index cbcbd94..ba0e525 100644 --- a/spec/acceptance/docker_spec.rb +++ b/spec/acceptance/docker_spec.rb @@ -1,320 +1,329 @@ # frozen_string_literal: true require 'spec_helper_acceptance' broken = false registry_port = 5000 if os[:family] == 'windows' result = run_shell("ipconfig | findstr /i 'ipv4'") raise 'Could not retrieve ip address for Windows box' if result.exit_code != 0 ip = result.stdout.split("\n")[0].split(':')[1].strip @windows_ip = ip docker_args = "docker_ee => true, extra_parameters => '\"insecure-registries\": [ \"#{@windows_ip}:5000\" ]'" root_dir = 'C:/Users/Administrator/AppData/Local/Temp' docker_registry_image = 'stefanscherer/registry-windows' docker_network = 'nat' registry_host = @windows_ip config_file = '/cygdrive/c/Users/Administrator/.docker/config.json' server_strip = "#{registry_host}_#{registry_port}" bad_server_strip = "#{registry_host}_5001" broken = true else docker_args = if os[:name] == 'ubuntu' && os[:release][:full] == '14.04' "version => '18.06.1~ce~3-0~ubuntu'" else '' end docker_registry_image = 'registry' docker_network = 'bridge' registry_host = '127.0.0.1' server_strip = "#{registry_host}:#{registry_port}" bad_server_strip = "#{registry_host}:5001" config_file = '/root/.docker/config.json' root_dir = '/root' end describe 'docker' do package_name = 'docker-ce' service_name = 'docker' command = 'docker' + before(:all) do + install_pp = "class { 'docker': #{docker_args}}" + apply_manifest(install_pp) + end + context 'When adding system user', win_broken: broken do let(:pp) do " class { 'docker': #{docker_args}, docker_users => ['user1'] } " end it 'the docker daemon' do apply_manifest(pp, catch_failures: true) do |r| expect(r.stdout).not_to match(%r{docker-systemd-reload-before-service}) end end end context 'When prepare_service_only param is set(prepare_service_only => true)', win_broken: broken do let(:pp) do " class { 'docker': #{docker_args} } docker::run { 'servercore': image => 'hello-world:latest', prepare_service_only => true, } " end it 'creates the service without starting it' do apply_manifest(pp, catch_failures: true) end it 'not start the service' do run_shell('systemctl status docker-servercore', expect_failures: true) do |r| expect(r.stdout.include?('Main PID')).to be false end end end context 'When prepare_service_only param is not set(prepare_service_only => false)', win_broken: broken do let(:pp) do " class { 'docker': #{docker_args} } docker::run { 'servercore': image => 'hello-world:latest', } " end it 'creates the service and start it' do apply_manifest(pp, catch_failures: true) end it 'start the service' do run_shell('systemctl status docker-servercore', expect_failures: true) do |r| expect(r.stdout.include?('Main PID')).to be true end end end context 'When root_dir is set' do let(:pp) do "class { 'docker': #{docker_args}, root_dir => \"#{root_dir}\"}" end let(:shell_command) do if os[:family] == 'windows' 'cat C:/ProgramData/docker/config/daemon.json' else 'systemctl status docker' end end it 'works' do apply_manifest(pp, catch_failures: true) run_shell(shell_command) do |r| if os[:family] == 'windows' expect(r.stdout).to match(%r{\"data-root\": \"#{root_dir}\"}) else expect(r.stdout).to match(%r{--data-root #{root_dir}}) end end end end context 'with default parameters', win_broken: broken do let(:pp) do " class { 'docker': docker_users => [ 'testuser' ], #{docker_args} } docker::image { 'nginx': } docker::run { 'nginx': image => 'nginx', net => 'host', require => Docker::Image['nginx'], } docker::run { 'nginx2': image => 'nginx', restart => 'always', require => Docker::Image['nginx'], } " end it 'applies with no errors' do apply_manifest(pp, catch_failures: true) end it 'is idempotent' do - apply_manifest(pp, catch_changes: true) + if fetch_puppet_version > 5 + expect(docker_run_idempotent_apply(pp)).to be true + else + apply_manifest(pp, catch_changes: true) + end end describe package(package_name) do it { is_expected.to be_installed } end describe service(service_name) do it { is_expected.to be_enabled } it { is_expected.to be_running } end it "#{command} version" do run_shell("#{command} version", expect_failures: false) end it "#{command} images" do result = run_shell("sudo #{command} images", expect_failures: false) expect(result[:exit_code]).to eq 0 expect(result[:stdout]).to match %r{nginx} end it "#{command} inspect nginx" do run_shell("sudo #{command} inspect nginx", expect_failures: false) end it "#{command} inspect nginx2" do run_shell("sudo #{command} inspect nginx2", expect_failures: false) end it "#{command} ps --no-trunc | grep `cat /var/run/docker-nginx2.cid`" do result = run_shell("sudo #{command} ps --no-trunc | grep `cat /var/run/docker-nginx2.cid`", expect_failures: false) expect(result[:exit_code]).to eq 0 expect(result[:stdout]).to match %r{nginx -g 'daemon off;'} end it 'netstat -tlndp' do result = run_shell('netstat -tlndp') expect(result[:exit_code]).to eq 0 expect(result[:stdout]).to match %r{0\.0\.0\.0\:80} end it 'id testuser | grep docker' do result = run_shell('id testuser | grep docker') expect(result[:exit_code]).to eq 0 expect(result[:stdout]).to match %r{docker} end end context 'When asked to have the latest image of something', win_broken: broken do let(:pp) do " class { 'docker': docker_users => [ 'testuser' ] } docker::image { 'busybox': ensure => latest } " end it 'applies with no errors' do apply_manifest(pp, catch_failures: true) end end context 'When registry_mirror is set', win_broken: broken do let(:pp) do " class { 'docker': registry_mirror => 'http://testmirror.io' } " end it 'applies with no errors' do apply_manifest(pp, catch_failures: true) end it 'has a registry mirror set' do run_shell('ps -aux | grep docker') do |r| expect(r.stdout).to match(%r{--registry-mirror=http:\/\/testmirror.io}) end end end context 'When registry_mirror is array', win_broken: broken do let(:pp) do " class { 'docker': registry_mirror => ['http://testmirror1.io', 'http://testmirror2.io'] } " end it 'applies with no errors' do apply_manifest(pp, catch_failures: true) end it 'has all registry mirrors set' do run_shell('ps -aux | grep docker') do |r| expect(r.stdout).to match(%r{--registry-mirror=http:\/\/testmirror1.io}) expect(r.stdout).to match(%r{--registry-mirror=http:\/\/testmirror2.io}) end end end context 'registry' do let(:registry_address) do "#{registry_host}:#{registry_port}" end let(:registry_bad_address) do "#{registry_host}:5001" end it 'is able to run registry' do pp = <<-MANIFEST class { 'docker': #{docker_args}} docker::run { 'registry': image => '#{docker_registry_image}', pull_on_start => true, restart => 'always', net => '#{docker_network}', ports => '#{registry_port}:#{registry_port}', } MANIFEST retry_on_error_matching(60, 5, %r{connection failure running}) do apply_manifest(pp, catch_failures: true) end # avoid a race condition with the registry taking time to start # on some operating systems sleep 10 end it 'is able to login to the registry', retry: 3, retry_wait: 10, win_broken: true do pp = <<-MANIFEST docker::registry { '#{registry_address}': username => 'username', password => 'password', } MANIFEST apply_manifest(pp, catch_failures: true) run_shell("grep #{registry_address} #{config_file}", expect_failures: false) run_shell("test -e \"#{root_dir}/registry-auth-puppet_receipt_#{server_strip}_root\"", expect_failures: false) end it 'is able to logout from the registry', win_broken: true do pp = <<-MANIFEST docker::registry { '#{registry_address}': ensure=> absent, } MANIFEST apply_manifest(pp, catch_failures: true) run_shell("grep #{registry_address} #{config_file}", expect_failures: true) end it 'does not create receipt if registry login fails', win_broken: true do pp = <<-MANIFEST docker::registry { '#{registry_bad_address}': username => 'username', password => 'password', } MANIFEST apply_manifest(pp, catch_failures: false) run_shell("grep #{registry_bad_address} #{config_file}", expect_failures: true) run_shell("test -e \"#{root_dir}/registry-auth-puppet_receipt_#{bad_server_strip}_root\"", expect_failures: true) end end end diff --git a/spec/defines/run_spec.rb b/spec/defines/run_spec.rb index 81fa35c..62c7d9e 100644 --- a/spec/defines/run_spec.rb +++ b/spec/defines/run_spec.rb @@ -1,365 +1,366 @@ # frozen_string_literal: true require 'spec_helper' tests = { 'default values' => {}, 'when passing depends containers' => { 'depends' => [ 'foo', 'bar', 'foo_bar/baz', ], }, 'when passing after containers' => { 'after' => [ 'foo', 'bar', 'foo_bar/baz', ], }, 'when use_name is true' => { 'use_name' => true, }, 'when stopping the service' => { 'running' => false, }, 'when passing a cpuset' => { 'cpuset' => '3', }, 'when passing a multiple cpu cpuset' => { 'cpuset' => [ '0', '3', ], }, 'when passing a links option' => { 'links' => [ 'example:one', 'example:two', ], }, 'when passing a hostname' => { 'hostname' => 'example.com', }, 'when passing a username' => { 'username' => 'bob', }, 'when passing a port number' => { 'ports' => '4444', }, 'when passing a port to expose' => { 'expose' => '4666', }, 'when passing a label' => { 'labels' => 'key=value', }, 'when passing a hostentry' => { 'hostentries' => 'dummyhost:127.0.0.2', }, 'when connecting to shared data volumes' => { 'volumes_from' => '6446ea52fbc9', }, 'when connecting to several shared data volumes' => { 'volumes_from' => [ 'sample-linked-container-1', 'sample-linked-container-2', ], }, 'when passing several port numbers' => { 'ports' => [ '4444', '4555', ], }, 'when passing several labels' => { 'labels' => [ 'key1=value1', 'key2=value2', ], }, 'when passing several ports to expose' => { 'expose' => [ '4666', '4777', ], }, 'when passing serveral environment variables' => { 'env' => [ 'FOO=BAR', 'FOO2=BAR2', ], }, 'when passing an environment variable' => { 'env' => 'FOO=BAR', }, 'when passing serveral environment files' => { 'env_file' => [ '/etc/foo.env', '/etc/bar.env', ], }, 'when passing an environment file' => { 'env_file' => '/etc/foo.env', }, 'when passing serveral dns addresses' => { 'dns' => [ '8.8.8.8', '8.8.4.4', ], }, 'when passing a dns address' => { 'dns' => '8.8.8.8', }, 'when passing serveral sockets to connect to' => { 'socket_connect' => [ 'tcp://127.0.0.1:4567', 'tcp://127.0.0.2:4567', ], }, 'when passing a socket to connect to' => { 'socket_connect' => 'tcp://127.0.0.1:4567', }, 'when passing serveral dns search domains' => { 'dns_search' => [ 'my.domain.local', 'other-domain.de', ], }, 'when passing a dns search domain' => { 'dns_search' => 'my.domain.local', }, 'when disabling network' => { 'disable_network' => true, }, 'when running privileged' => { 'privileged' => true, }, 'when passing serveral extra parameters' => { 'extra_parameters' => ['--rm', '-w /tmp'], }, 'when passing an extra parameter' => { 'extra_parameters' => '-c 4', }, 'when passing a data volume' => { 'volumes' => '/var/log', }, 'when passing serveral data volume' => { 'volumes' => [ '/var/lib/couchdb', '/var/log', ], }, 'when pull_on_start is true' => { 'pull_on_start' => true, }, 'when pull_on_start is false' => { 'pull_on_start' => false, }, 'when before_start is set' => { 'before_start' => 'echo before_start', }, 'when before_start is not set' => { 'before_start' => false, }, 'when before_stop is set' => { 'before_stop' => 'echo before_stop', }, 'when before_stop is not set' => { 'before_stop' => false, }, 'when after_start is set' => { 'after_start' => 'echo after_start', }, 'when after_start is not set' => { 'after_start' => false, }, 'when after_stop is set' => { 'after_stop' => 'echo after_stop', }, 'when after_stop is not set' => { 'after_stop' => false, }, 'when docker_service is false' => { 'docker_service' => false, }, 'when docker_service is true' => { 'docker_service' => true, }, 'when docker_service is true and restart_service_on_docker_refresh is false' => { 'docker_service' => true, 'restart_service_on_docker_refresh' => false, }, 'when docker_service is my-docker' => { 'docker_service' => 'my-docker', }, 'when docker_service is my-docker and restart_service_on_docker_refresh is false' => { 'docker_service' => 'my-docker', 'restart_service_on_docker_refresh' => false, }, } describe 'docker::run', type: :define do on_supported_os.each do |os, os_facts| ## ## set some needed facts ## if %r{windows}.match?(os) facts = windows_facts.merge(os_facts) + facts = facts.merge({ puppetversion: Puppet.version }) os_params = { 'restart' => 'no', 'extra_parameters' => '-c 4', } docker_params = { 'docker_ee' => true, } else - facts = {}.merge(os_facts) + facts = { puppetversion: Puppet.version }.merge(os_facts) os_params = {} docker_params = {} end ## ## get defaults values from params ## defaults = get_defaults(facts) context "on #{os}" do tests.each do |title, local_params| context title do params = { 'after_create' => :undef, 'after_service' => [], 'after_start' => false, 'after_stop' => false, 'after' => [], 'before_start' => false, 'before_stop' => false, 'command' => 'command', 'cpuset' => [], 'custom_unless' => [], 'depend_services' => ['docker.service'], 'depends' => [], 'detach' => :undef, 'disable_network' => false, 'dns_search' => [], 'dns' => [], 'docker_service' => false, 'ensure' => 'present', 'env_file' => [], 'env' => [], 'expose' => [], 'extra_parameters' => :undef, 'extra_systemd_parameters' => {}, 'health_check_cmd' => :undef, 'health_check_interval' => :undef, 'hostentries' => [], 'hostname' => false, 'image' => 'base', 'labels' => [], 'links' => [], 'lxc_conf' => [], 'manage_service' => true, 'memory_limit' => '0b', 'net' => 'bridge', 'ports' => [], 'privileged' => false, 'pull_on_start' => false, 'read_only' => false, 'remain_after_exit' => :undef, 'remove_container_on_start' => true, 'remove_container_on_stop' => true, 'remove_volume_on_start' => false, 'remove_volume_on_stop' => false, 'restart_on_unhealthy' => false, 'restart_service_on_docker_refresh' => true, 'restart_service' => true, 'restart' => :undef, 'running' => true, 'service_prefix' => 'docker-', 'service_provider' => :undef, 'socket_connect' => [], 'stop_wait_time' => 0, 'syslog_identifier' => :undef, 'systemd_restart' => 'on-failure', 'tty' => false, 'use_name' => false, 'username' => false, 'volumes_from' => [], 'volumes' => [], }.merge(os_params).merge(local_params) if params['docker_service'] && params['docker_service'].to_s != 'true' docker_params['service_name'] = params['docker_service'] end let(:facts) do facts end let(:params) do params end let(:title) do params['command'] end let :pre_condition do <<-MANIFEST class { 'docker': version => "#{params['version']}", * => #{docker_params}, } MANIFEST end if params['remove_volume_on_start'] && !params['remove_container_on_start'] it { is_expected.to compile.and_raise_error("In order to remove the volume on start for #{_title} you need to also remove the container") } next end if params['remove_volume_on_stop'] && !params['remove_container_on_stop'] it { is_expected.to compile.and_raise_error("In order to remove the volume on stop for #{_title} you need to also remove the container") } next end service_provider_real = case params['service_provider'] when :undef defaults['service_provider'] else params['service_provider'] end if !params['service_provider_real'] == 'systemd' && !params['service_provider_real'] == 'upstart' if facts[:os]['family'] != 'windows' it { is_expected.to compile.and_raise_error('Docker needs a Debian or RedHat based system.') } next elsif params['ensure'] == 'present' it { is_expected.to compile.and_raise_error('Restart parameter is required for Windows') } next end end include_examples 'run', params['command'], params, facts, defaults end end end end end diff --git a/spec/helper/get_docker_params_changed.rb b/spec/helper/get_docker_params_changed.rb new file mode 100644 index 0000000..7df24db --- /dev/null +++ b/spec/helper/get_docker_params_changed.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +def get_docker_params_changed(opts) + require 'open3' + require 'json' + return_value = 'No changes detected' + + if opts['sanitised_title'] && opts['osfamily'] + stdout, stderr, status = Open3.capture3("docker inspect #{opts['sanitised_title']}") + if stderr.to_s == '' && status.to_s.include?('exit 0') + param_changed = false + inspect_hash = JSON.parse(stdout)[0] + + # check if the image was changed + param_changed = true if opts['image'] && opts['image'] != inspect_hash['Config']['Image'] + + # check if something on volumes or mounts was changed(a new volume/mount was added or removed) + param_changed = true if opts['volumes'].is_a?(String) && opts['volumes'].include?(':') && opts['volumes'] != inspect_hash['Mounts'].to_a[0] && opts['osfamily'] != 'windows' + param_changed = true if opts['volumes'].is_a?(String) && !opts['volumes'].include?(':') && opts['volumes'] != inspect_hash['Config']['Volumes'].to_a[0] && opts['osfamily'] != 'windows' + param_changed = true if opts['volumes'].is_a?(String) && opts['volumes'].scan(%r{(?=:)}).count == 2 && opts['volumes'] != inspect_hash['Mounts'].to_a[0] && opts['osfamily'] == 'windows' + param_changed = if opts['volumes'].is_a?(String) && opts['volumes'].scan(%r{(?=:)}).count == 1 && opts['volumes'] != inspect_hash['Config']['Volumes'].to_a[0] && opts['osfamily'] == 'windows' + true + else + param_changed + end + + pp_paths = opts['volumes'].reject { |item| item.include?(':') } if opts['volumes'].is_a?(Array) && opts['osfamily'] != 'windows' + pp_mounts = opts['volumes'].select { |item| item.include?(':') } if opts['volumes'].is_a?(Array) && opts['osfamily'] != 'windows' + pp_paths = opts['volumes'].select { |item| item.scan(%r{(?=:)}).count == 1 } if opts['volumes'].is_a?(Array) && opts['osfamily'] == 'windows' + pp_mounts = opts['volumes'].select { |item| item.scan(%r{(?=:)}).count == 2 } if opts['volumes'].is_a?(Array) && opts['osfamily'] == 'windows' + + inspect_paths = if inspect_hash['Config']['Volumes'] + inspect_hash['Config']['Volumes'].keys + else + [] + end + param_changed = true if pp_paths != inspect_paths + + names = inspect_hash['Mounts'].map { |item| item.values[1] } if inspect_hash['Mounts'] + pp_names = pp_mounts.map { |item| item.split(':')[0] } if pp_mounts + names = names.select { |item| pp_names.include?(item) } if names && pp_names + destinations = inspect_hash['Mounts'].map { |item| item.values[3] } if inspect_hash['Mounts'] + pp_destinations = pp_mounts.map { |item| item.split(':')[1] } if pp_mounts && opts['osfamily'] != 'windows' + pp_destinations = pp_mounts.map { |item| "#{item.split(':')[1].downcase}:#{item.split(':')[2]}" } if pp_mounts && opts['osfamily'] == 'windows' + destinations = destinations.select { |item| pp_destinations.include?(item) } if destinations && pp_destinations + + param_changed = true if pp_names != names + param_changed = true if pp_destinations != destinations + param_changed = true if pp_mounts != [] && inspect_hash['Mounts'].nil? + + # check if something on ports was changed(some ports were added or removed) + + ports = inspect_hash['HostConfig']['PortBindings'].keys + ports = ports.map { |item| item.split('/')[0] } + pp_ports = opts['ports'].sort if opts['ports'].is_a?(Array) + pp_ports = [opts['ports']] if opts['ports'].is_a?(String) + + param_changed = true if pp_ports && pp_ports != ports + + if param_changed + remove_container(opts['sanitised_title'], opts['osfamily'], opts['stop_wait_time'], opts['cidfile'], log) + create_container(opts['command'], opts['osfamily'], opts['image'], log) + return_value = 'Param changed' + end + else + create_container(opts['command'], opts['osfamily'], opts['image']) unless File.exist?(opts['cidfile']) + _stdout, _stderr, status = Open3.capture3("docker inspect #{opts['sanitised_title']}") + unless status.to_s.include?('exit 0') + remove_cidfile(opts['cidfile'], opts['osfamily']) + create_container(opts['command'], opts['osfamily'], opts['image']) + end + return_value = 'No changes detected' + end + else + return_value = 'Arg required missing' + end + + if opts['container_running'] + start_container(opts['sanitised_title'], opts['osfamily']) + else + stop_container(opts['sanitised_title'], opts['osfamily']) + end + + return_value +end + +def run_with_powershell(cmd) + "powershell.exe -Command \"& {#{cmd}}\" " +end + +def remove_cidfile(cidfile, osfamily) + delete_command = if osfamily == 'windows' + run_with_powershell("del #{cidfile}") + else + "rm -f #{cidfile}" + end + _stdout, _stderr, _status = Open3.capture3(delete_command) +end + +def start_container(name, osfamily) + start_command = if osfamily == 'windows' + run_with_powershell("docker start #{name}") + else + "docker start #{name}" + end + _stdout, _stderr, _status = Open3.capture3(start_command) +end + +def stop_container(name, osfamily) + stop_command = if osfamily == 'windows' + run_with_powershell("docker stop #{name}") + else + "docker stop #{name}" + end + _stdout, _stderr, _status = Open3.capture3(stop_command) +end + +def remove_container(name, osfamily, stop_wait_time, cidfile) + stop_command = if osfamily == 'windows' + run_with_powershell("docker stop --time=#{stop_wait_time} #{name}") + else + "docker stop --time=#{stop_wait_time} #{name}" + end + _stdout, _stderr, _status = Open3.capture3(stop_command) + + remove_command = if osfamily == 'windows' + run_with_powershell("docker rm -v #{name}") + else + "docker rm -v #{name}" + end + _stdout, _stderr, _status = Open3.capture3(remove_command) + + remove_cidfile(cidfile, osfamily) +end + +def create_container(cmd, osfamily, image) + pull_command = if osfamily == 'windows' + run_with_powershell("docker pull #{image} -q") + else + "docker pull #{image} -q" + end + _stdout, _stderr, _status = Open3.capture3(pull_command) + + create_command = if osfamily == 'windows' + run_with_powershell(cmd) + else + cmd + end + _stdout, _stderr, _status = Open3.capture3(create_command) +end diff --git a/spec/shared_examples.rb b/spec/shared_examples.rb index 44e5143..6a8998b 100644 --- a/spec/shared_examples.rb +++ b/spec/shared_examples.rb @@ -1,34 +1,35 @@ # frozen_string_literal: true require 'helper/get_defaults' require 'helper/get_docker_exec_flags' require 'helper/get_docker_plugin_enable_flags' require 'helper/get_docker_plugin_install_flags' require 'helper/get_docker_plugin_remove_flags' require 'helper/get_docker_run_flags' require 'helper/get_docker_secrets_flags' require 'helper/get_docker_service_flags' require 'helper/get_docker_stack_flags' require 'helper/get_docker_swarm_init_flags' require 'helper/get_docker_swarm_join_flags' +require 'helper/get_docker_params_changed' require 'helper/get_values_init' require 'helper/pw_hash' require 'helper/windows_facts' require 'shared_examples/compose' require 'shared_examples/config' require 'shared_examples/exec' require 'shared_examples/image' require 'shared_examples/install' require 'shared_examples/machine' require 'shared_examples/params' require 'shared_examples/plugin' require 'shared_examples/registry' require 'shared_examples/repos' require 'shared_examples/run' require 'shared_examples/secrets' require 'shared_examples/service' require 'shared_examples/services' require 'shared_examples/stack' require 'shared_examples/swarm' require 'shared_examples/system_user' diff --git a/spec/shared_examples/run.rb b/spec/shared_examples/run.rb index 530f3c6..26d2701 100644 --- a/spec/shared_examples/run.rb +++ b/spec/shared_examples/run.rb @@ -1,449 +1,472 @@ # frozen_string_literal: true shared_examples 'run' do |title, params, facts, defaults| # after = params['after'] # after_create = params['after_create'] # after_service = params['after_service'] # after_start = params['after_start'] # after_stop = params['after_stop'] # before_start = params['before_start'] # before_stop = params['before_stop'] command = params['command'] cpuset = params['cpuset'] # custom_unless = params['custom_unless'] # depend_services = params['depend_services'] # depends = params['depends'] # detach = params['detach'] disable_network = params['disable_network'] dns = params['dns'] dns_search = params['dns_search'] docker_service = params['docker_service'] ensure_value = params['ensure'] env = params['env'] env_file = params['env_file'] expose = params['expose'] extra_parameters = params['extra_parameters'] # extra_systemd_parameters = params['extra_systemd_parameters'] health_check_cmd = params['health_check_cmd'] health_check_interval = params['health_check_interval'] hostentries = params['hostentries'] hostname = params['hostname'] image = params['image'] labels = params['labels'] links = params['links'] lxc_conf = params['lxc_conf'] manage_service = params['manage_service'] memory_limit = params['memory_limit'] net = params['net'] ports = params['ports'] privileged = params['privileged'] # pull_on_start = params['pull_on_start'] read_only = params['read_only'] # remain_after_exit = params['remain_after_exit'] # remove_container_on_start = params['remove_container_on_start'] # remove_container_on_stop = params['remove_container_on_stop'] # remove_volume_on_start = params['remove_volume_on_start'] # remove_volume_on_stop = params['remove_volume_on_stop'] restart = params['restart'] restart_on_unhealthy = params['restart_on_unhealthy'] # restart_service = params['restart_service'] restart_service_on_docker_refresh = params['restart_service_on_docker_refresh'] running = params['running'] service_prefix = params['service_prefix'] service_provider = params['service_provider'] socket_connect = params['socket_connect'] stop_wait_time = params['stop_wait_time'] syslog_identifier = params['syslog_identifier'] # systemd_restart = params['systemd_restart'] tty = params['tty'] use_name = params['use_name'] username = params['username'] volumes = params['volumes'] volumes_from = params['volumes_from'] docker_group = defaults['docker_group'] if socket_connect != [] sockopts = [socket_connect].join(',') docker_command = "#{defaults['docker_command']} -H #{sockopts}" else docker_command = defaults['docker_command'] end if use_name it { is_expected.to contain_notify("docker use_name warning: #{title}").with( 'message' => 'The use_name parameter is no-longer required and will be removed in a future release', 'withpath' => true, ) } end service_provider_real = case service_provider.to_s when 'undef' defaults['service_provider'] else service_provider end docker_run_flags = get_docker_run_flags( 'cpuset' => [cpuset], 'disable_network' => disable_network, 'dns_search' => [dns_search], 'dns' => [dns], 'env_file' => [env_file], 'env' => [env], 'expose' => [expose], 'extra_params' => [extra_parameters], 'health_check_cmd' => health_check_cmd, 'health_check_interval' => health_check_interval, 'hostentries' => [hostentries], 'hostname' => hostname, 'labels' => [labels], 'links' => [links], 'lxc_conf' => [lxc_conf], 'memory_limit' => memory_limit, 'net' => net, 'osfamily' => facts[:os]['family'], 'ports' => [ports], 'privileged' => privileged, 'read_only' => read_only, 'restart_on_unhealthy' => restart_on_unhealthy, 'socket_connect' => [socket_connect], 'tty' => tty, 'username' => username, 'volumes_from' => [volumes_from], 'volumes' => [volumes], ) sanitised_title = title.gsub('[^0-9A-Za-z.\-_]', '-') if facts[:os]['family'] == 'windows' exec_environment = "PATH=#{facts['docker_program_files_path']}/Docker/;#{facts['docker_systemroot']}/System32/" exec_timeout = 3000 exec_path = ["#{facts['docker_program_files_path']}/Docker/"] exec_provider = 'powershell' cidfile = "#{facts['docker_user_temp_path']}/#{service_prefix}#{sanitised_title}.cid" restart_check = "#{docker_command} inspect #{sanitised_title} -f '{{ if eq \\\"unhealthy\\\" .State.Health.Status }} {{ .Name }}{{ end }}' | findstr #{sanitised_title}" container_running_check = "\$state = #{docker_command} inspect #{sanitised_title} -f \"{{ .State.Running }}\"; if (\$state -ieq \"true\") { Exit 0 } else { Exit 1 }" else exec_environment = 'HOME=/root' exec_path = ['/bin', '/usr/bin'] exec_timeout = 0 exec_provider = nil cidfile = "/var/run/#{service_prefix}#{sanitised_title}.cid" restart_check = "#{docker_command} inspect #{sanitised_title} -f '{{ if eq \"unhealthy\" .State.Health.Status }} {{ .Name }}{{ end }}' | grep #{sanitised_title}" container_running_check = "#{docker_command} inspect #{sanitised_title} -f \"{{ .State.Running }}\" | grep true" end if restart_on_unhealthy it { is_expected.to contain_exec("Restart unhealthy container #{title} with docker").with( 'command' => "#{docker_command} restart #{sanitised_title}", 'onlyif' => restart_check, 'environment' => exec_environment, 'path' => exec_path, 'provider' => exec_provider, 'timeout' => exec_timeout, ) } end if restart.to_s != 'undef' if ensure_value == 'absent' it { is_expected.to contain_exec("stop #{title} with docker").with( 'command' => "#{docker_command} stop --time=#{stop_wait_time} #{sanitised_title}", 'onlyif' => "#{docker_command} inspect #{sanitised_title}", 'environment' => exec_environment, 'path' => exec_path, 'provider' => exec_provider, 'timeout' => exec_timeout, ) is_expected.to contain_exec("remove #{title} with docker").with( 'command' => "#{docker_command} rm -v #{sanitised_title}", 'onlyif' => "#{docker_command} inspect #{sanitised_title}", 'environment' => exec_environment, 'path' => exec_path, 'provider' => exec_provider, 'timeout' => exec_timeout, ) is_expected.to contain_file(cidfile).with( 'ensure' => 'absent', ) } else run_with_docker_command = [ "#{docker_command} run -d #{docker_run_flags}", "--name #{sanitised_title} --cidfile=#{cidfile}", "--restart=\"#{restart}\" #{image} #{command}", ] # inspect = [ # "#{docker_command} inspect #{sanitised_title}", # ] # exec_unless = if custom_unless # custom_unless << inspect # else # inspect # end - it { - is_expected.to contain_exec("run #{title} with docker").with( - 'command' => run_with_docker_command.join(' '), - ## todo: - ## fix the following strange behavior: - ## expected that the catalogue would contain Exec[run command with docker] with unless set to [["docker inspect command"]] - ## but it is set to [["docker inspect command"], "docker inspect command"] - # 'unless' => exec_unless, - 'environment' => exec_environment, - 'path' => exec_path, - 'provider' => exec_provider, - 'timeout' => exec_timeout, - ) - } - - if !running + if facts[:puppetversion].to_i < 6 it { - is_expected.to contain_exec("stop #{title} with docker").with( - 'command' => "#{docker_command} stop --time=#{stop_wait_time} #{sanitised_title}", - 'onlyif' => container_running_check, + is_expected.to contain_exec("run #{title} with docker").with( + 'command' => run_with_docker_command.join(' '), + ## todo: + ## fix the following strange behavior: + ## expected that the catalogue would contain Exec[run command with docker] with unless set to [["docker inspect command"]] + ## but it is set to [["docker inspect command"], "docker inspect command"] + # 'unless' => exec_unless, 'environment' => exec_environment, 'path' => exec_path, 'provider' => exec_provider, 'timeout' => exec_timeout, ) } + + if !running + it { + is_expected.to contain_exec("stop #{title} with docker").with( + 'command' => "#{docker_command} stop --time=#{stop_wait_time} #{sanitised_title}", + 'onlyif' => container_running_check, + 'environment' => exec_environment, + 'path' => exec_path, + 'provider' => exec_provider, + 'timeout' => exec_timeout, + ) + } + else + it { + is_expected.to contain_exec("start #{title} with docker").with( + 'command' => "#{docker_command} start #{sanitised_title}", + 'unless' => container_running_check, + 'environment' => exec_environment, + 'path' => exec_path, + 'provider' => exec_provider, + 'timeout' => exec_timeout, + ) + } + end else + docker_params_changed_args = { + 'sanitised_title' => sanitised_title, + 'osfamily' => facts[:os]['family'], + 'command' => run_with_docker_command.join(' '), + 'cidfile' => cidfile, + 'image' => image, + 'volumes' => volumes, + 'ports' => ports, + 'stop_wait_time' => stop_wait_time, + 'container_running' => running, + 'logfile_path' => facts[:os]['family'] == 'windows' ? facts['docker_user_temp_path'] : '/tmp', + } + + detect_changes = get_docker_params_changed(docker_params_changed_args) + it { - is_expected.to contain_exec("start #{title} with docker").with( - 'command' => "#{docker_command} start #{sanitised_title}", - 'unless' => container_running_check, - 'environment' => exec_environment, - 'path' => exec_path, - 'provider' => exec_provider, - 'timeout' => exec_timeout, + is_expected.to contain_notify('docker_params_changed').with( + 'message' => detect_changes, ) } end end else case service_provider_real when 'systemd' hasstatus = true initscript = "/etc/systemd/system/#{service_prefix}#{sanitised_title}.service" mode = '0644' startscript = "/usr/local/bin/docker-run-#{sanitised_title}-start.sh" stopscript = "/usr/local/bin/docker-run-#{sanitised_title}-stop.sh" when 'upstart' hasstatus = true initscript = "/etc/init.d/#{service_prefix}#{sanitised_title}" mode = '0750' startscript = nil stopscript = nil else hasstatus = defaults['service_hasstatus'] end _syslog_identifier = if syslog_identifier syslog_identifier else "#{service_prefix}#{sanitised_title}" end if ensure_value == 'absent' if facts[:os]['family'] == 'windows' it { is_expected.to contain_exec("stop container #{service_prefix}#{sanitised_title}").with( 'command' => "#{docker_command} stop --time=#{stop_wait_time} #{sanitised_title}", 'onlyif' => "#{docker_command} inspect #{sanitised_title}", 'environment' => exec_environment, 'path' => exec_path, 'provider' => exec_provider, 'timeout' => exec_timeout, ).that_notifies( "Exec[remove container #{service_prefix}#{sanitised_title}]", ) } else it { is_expected.to contain_service("#{service_prefix}#{sanitised_title}").with( 'ensure' => false, 'enable' => false, 'hasstatus' => hasstatus, 'provider' => service_provider_real, ) } end it { is_expected.to contain_exec("remove container #{service_prefix}#{sanitised_title}").with( 'command' => "#{docker_command} rm -v #{sanitised_title}", 'onlyif' => "#{docker_command} inspect #{sanitised_title}", 'environment' => exec_environment, 'path' => exec_path, 'refreshonly' => true, 'provider' => exec_provider, 'timeout' => exec_timeout, ) } if facts[:os]['family'] != 'windows' it { is_expected.to contain_file("/etc/systemd/system/#{service_prefix}#{sanitised_title}.service").with( 'ensure' => 'absent', ) } if startscript it { is_expected.to contain_file(startscript).with( 'ensure' => 'absent', ) } end if stopscript it { is_expected.to contain_file(stopscript).with( 'ensure' => 'absent', ) } end else it { is_expected.to contain_file(cidfile).with( 'ensure' => 'absent', ) } end else if startscript it { is_expected.to contain_file(startscript).with( 'ensure' => 'file', 'owner' => 'root', 'group' => docker_group, 'mode' => '0770', ) } end if stopscript it { is_expected.to contain_file(stopscript).with( 'ensure' => 'file', 'owner' => 'root', 'group' => docker_group, 'mode' => '0770', ) } end it { is_expected.to contain_file(initscript).with( 'ensure' => 'file', 'owner' => 'root', 'group' => docker_group, 'mode' => mode, ) } if manage_service if !running it { is_expected.to contain_service("#{service_prefix}#{sanitised_title}").with( 'ensure' => running, 'enable' => false, 'hasstatus' => hasstatus, ).that_requires( "File[#{initscript}]", ) } else if initscript == "/etc/init.d/#{service_prefix}#{sanitised_title}" transition_onlyif = [ "/usr/bin/test -f /var/run/docker-#{sanitised_title}.cid &&", "/usr/bin/test -f /etc/init.d/#{service_prefix}#{sanitised_title}", ] it { is_expected.to contain_exec("/bin/sh /etc/init.d/#{service_prefix}#{sanitised_title} stop").with( 'onlyif' => transition_onlyif.join(' '), ).that_comes_before( "File[/var/run/#{service_prefix}#{sanitised_title}.cid]", ) is_expected.to contain_file("/var/run/#{service_prefix}#{sanitised_title}.cid").with( 'ensure' => 'absent', ).that_comes_before( "File[#{initscript}]", ) } end it { is_expected.to contain_service("#{service_prefix}#{sanitised_title}").with( 'ensure' => running, 'enable' => true, 'provider' => service_provider_real, 'hasstatus' => hasstatus, ).that_requires( "File[#{initscript}]", ) } if docker_service if docker_service.to_s == 'true' it { is_expected.to contain_service('docker').that_comes_before("Service[#{service_prefix}#{sanitised_title}]") } if restart_service_on_docker_refresh.to_s == 'true' it { is_expected.to contain_service('docker').that_notifies("Service[#{service_prefix}#{sanitised_title}]") } end else it { is_expected.to contain_service('docker').with('name' => docker_service).that_comes_before("Service[#{service_prefix}#{sanitised_title}]") } if restart_service_on_docker_refresh.to_s == 'true' it { is_expected.to contain_service('docker').with('name' => docker_service).that_notifies("Service[#{service_prefix}#{sanitised_title}]") } end end end end end if service_provider_real == 'systemd' it { is_expected.to contain_exec("docker-#{sanitised_title}-systemd-reload").with( 'path' => ['/bin/', '/sbin/', '/usr/bin/', '/usr/sbin/'], 'command' => 'systemctl daemon-reload', 'refreshonly' => true, ).that_requires( [ "File[#{initscript}]", "File[#{startscript}]", "File[#{stopscript}]", ], ).that_subscribes_to( [ "File[#{initscript}]", "File[#{startscript}]", "File[#{stopscript}]", ], ).that_comes_before( "Service[#{service_prefix}#{sanitised_title}]", ) } end end end end diff --git a/spec/spec_helper_acceptance_local.rb b/spec/spec_helper_acceptance_local.rb index b492e65..7bb8d91 100644 --- a/spec/spec_helper_acceptance_local.rb +++ b/spec/spec_helper_acceptance_local.rb @@ -1,195 +1,204 @@ # frozen_string_literal: true require 'puppet_litmus' require 'rspec/retry' require 'tempfile' include PuppetLitmus # This method allows a block to be passed in and if an exception is raised # that matches the 'error_matcher' matcher, the block will wait a set number # of seconds before retrying. # Params: # - max_retry_count - Max number of retries # - retry_wait_interval_secs - Number of seconds to wait before retry # - error_matcher - Matcher which the exception raised must match to allow retry # Example Usage: # retry_on_error_matching(3, 5, /OpenGPG Error/) do # apply_manifest(pp, :catch_failures => true) # end def retry_on_error_matching(max_retry_count = 3, retry_wait_interval_secs = 5, error_matcher = nil) try = 0 begin try += 1 yield rescue StandardError => e raise unless try < max_retry_count && (error_matcher.nil? || e.message =~ error_matcher) sleep retry_wait_interval_secs retry end end def create_remote_file(name, full_name, file_content) Tempfile.open name do |tempfile| File.open(tempfile.path, 'w') { |file| file.puts file_content } bolt_upload_file(tempfile.path, full_name) end end +def docker_run_idempotent_apply(pp) + apply_manifest(pp) + apply_manifest(pp).stdout.include?('Notice: No changes detected') +end + +def fetch_puppet_version + @fetch_puppet_version ||= run_shell('puppet --version').stdout.to_i +end + RSpec.configure do |c| # Add exclusive filter for Windows untill all the windows functionality is implemented c.filter_run_excluding win_broken: true # Readable test descriptions c.formatter = :documentation # show retry status in spec process c.verbose_retry = true # show exception that triggers a retry if verbose_retry is set to true c.display_try_failure_messages = true # Configure all nodes in nodeset c.before :suite do # Install module and dependencies # Due to RE-6764, running yum update renders the machine unable to install # other software. Thus this workaround. if os[:family] == 'redhat' run_shell('mv /etc/yum.repos.d/redhat.repo /etc/yum.repos.d/internal-mirror.repo', expect_failures: true) run_shell('rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm', expect_failures: true) run_shell('yum update -y -q') # run_shell('yum upgrade -y') end if os[:family] == 'debian' || os[:family] == 'ubuntu' run_shell('apt-get update -y') # run_shell('apt-get upgrade -y') run_shell('apt-get install -y lsb-release') run_shell('apt-get install -y net-tools') end run_shell('puppet module install puppetlabs-stdlib --version 4.24.0', expect_failures: true) run_shell('puppet module install puppetlabs-apt --version 4.4.1', expect_failures: true) run_shell('puppet module install puppetlabs-translate --version 1.0.0', expect_failures: true) run_shell('puppet module install puppetlabs-powershell --version 2.1.5', expect_failures: true) run_shell('puppet module install puppetlabs-reboot --version 2.0.0', expect_failures: true) # net-tools required for netstat utility being used by some tests if os[:family] == 'redhat' && os[:release].to_i == 7 run_shell('yum -y install lvm2 device-mapper device-mapper-persistent-data device-mapper-event device-mapper-libs device-mapper-event-libs') run_shell('yum install -y yum-utils net-tools') run_shell('yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo') run_shell('yum-config-manager --enable docker\*') end docker_compose_content_v3 = <<-EOS version: "3.4" x-images: &default-image alpine:3.8 services: compose_test: image: *default-image command: /bin/sh -c "while true; do echo hello world; sleep 1; done" EOS docker_compose_override_v3 = <<-EOS version: "3.4" x-images: &default-image debian:stable-slim services: compose_test: image: *default-image command: /bin/sh -c "while true; do echo hello world; sleep 1; done" EOS docker_stack_override_v3 = <<-EOS version: "3.4" x-images: &default-image debian:stable-slim services: compose_test: image: *default-image command: /bin/sh -c "while true; do echo hello world; sleep 1; done" EOS docker_compose_content_v3_windows = <<-EOS version: "3" services: compose_test: image: winamd64/hello-seattle command: cmd.exe /C "ping 8.8.8.8 -t" networks: default: external: name: nat EOS docker_compose_override_v3_windows = <<-EOS version: "3" services: compose_test: image: winamd64/hello-seattle:nanoserver command: cmd.exe /C "ping 8.8.8.8 -t" networks: default: external: name: nat EOS docker_compose_override_v3_windows2016 = <<-EOS version: "3" services: compose_test: image: winamd64/hello-seattle:nanoserver-sac2016 command: cmd.exe /C "ping 8.8.8.8 -t" networks: default: external: name: nat EOS docker_stack_content_windows = <<-EOS version: "3" services: compose_test: image: winamd64/hello-seattle command: cmd.exe /C "ping 8.8.8.8 -t" EOS docker_stack_override_windows = <<-EOS version: "3" services: compose_test: image: winamd64/hello-seattle:nanoserver EOS docker_stack_override_windows2016 = <<-EOS version: "3" services: compose_test: image: winamd64/hello-seattle:nanoserver-sac2016 EOS if os[:family] == 'windows' create_remote_file(host, '/tmp/docker-compose-v3.yml', docker_compose_content_v3_windows) create_remote_file(host, '/tmp/docker-stack.yml', docker_stack_content_windows) if %r{2019}.match?(os[:release]) create_remote_file(host, '/tmp/docker-compose-override-v3.yml', docker_compose_override_v3_windows) create_remote_file(host, '/tmp/docker-stack-override.yml', docker_stack_override_windows) else create_remote_file(host, '/tmp/docker-compose-override-v3.yml', docker_compose_override_v3_windows2016) create_remote_file(host, '/tmp/docker-stack-override.yml', docker_stack_override_windows2016) end else create_remote_file(host, '/tmp/docker-compose-v3.yml', docker_compose_content_v3) create_remote_file(host, '/tmp/docker-stack.yml', docker_compose_content_v3) create_remote_file(host, '/tmp/docker-compose-override-v3.yml', docker_compose_override_v3) create_remote_file(host, '/tmp/docker-stack-override.yml', docker_stack_override_v3) end next unless os[:family] == 'windows' result = run_shell("ipconfig | findstr /i 'ipv4'") raise 'Could not retrieve ip address for Windows box' if result.exit_code != 0 ip = result.stdout.split("\n")[0].split(':')[1].strip retry_on_error_matching(60, 5, %r{connection failure running}) do @windows_ip = ip end apply_manifest("class { 'docker': docker_ee => true, extra_parameters => '\"insecure-registries\": [ \"#{@windows_ip}:5000\" ]' }", catch_failures: true) docker_path = 'C:\\Program Files\\Docker' run_shell("set PATH \"%PATH%;C:\\Users\\Administrator\\AppData\\Local\\Temp;#{docker_path}\"") puts 'Waiting for box to come online' sleep 300 end end