diff --git a/lib/puppet/functions/docker_params_changed.rb b/lib/puppet/functions/docker_params_changed.rb index e44ed05..7105365 100644 --- a/lib/puppet/functions/docker_params_changed.rb +++ b/lib/puppet/functions/docker_params_changed.rb @@ -1,184 +1,184 @@ # frozen_string_literal: true 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 def remove_cidfile(cidfile, osfamily, _logger = nil) delete_command = if osfamily == 'windows' run_with_powershell("del #{cidfile}") else "rm -f #{cidfile}" end _stdout, _stderr, _status = Open3.capture3(delete_command) # logger.puts("### BEGIN remove_cidfile\n\n") # logger.puts("command: #{delete_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # logger.puts("### END remove_cidfile\n\n") end def start_container(name, osfamily, _logger = nil) start_command = if osfamily == 'windows' run_with_powershell("docker start #{name}") else "docker start #{name}" end _stdout, _stderr, _status = Open3.capture3(start_command) # logger.puts("### BEGIN start_container\n\n") # logger.puts("command: #{start_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # logger.puts("### END start_container\n\n") end def stop_container(name, osfamily, _logger = nil) stop_command = if osfamily == 'windows' run_with_powershell("docker stop #{name}") else "docker stop #{name}" end _stdout, _stderr, _status = Open3.capture3(stop_command) # logger.puts("### BEGIN stop_container\n\n") # logger.puts("command: #{stop_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # logger.puts("### END stop_container\n\n") end def remove_container(name, osfamily, stop_wait_time, cidfile, logger = nil) 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) # logger.puts("### BEGIN remove_container\n\n") # logger.puts("command: #{stop_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") 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) # logger.puts("command: #{remove_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") remove_cidfile(cidfile, osfamily, logger) # logger.puts("### END remove_container\n\n") end def create_container(cmd, osfamily, image, _logger = nil) # logger.puts("### BEGIN create_container\n\n") 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) # logger.puts("command: #{pull_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") create_command = if osfamily == 'windows' run_with_powershell(cmd) else cmd end _stdout, _stderr, _status = Open3.capture3(create_command) # logger.puts("command: #{create_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # logger.puts("### END create_container\n\n") end - def detect_changes(opts) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity + def detect_changes(opts) require 'open3' require 'json' return_value = 'NO_CHANGE_DETECTED' # log = File.open("#{opts['logfile_path']}/docker_params.txt", 'a') # log.puts("### BEGIN detect_changes\n\n") if opts['sanitised_title'] && opts['osfamily'] stdout, stderr, status = Open3.capture3("docker inspect #{opts['sanitised_title']}") # log.puts("command: #{"docker inspect #{opts['sanitised_title']}"}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # log.puts("#{opts['sanitised_title']}\n\n") 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']) create_container(opts['command'], opts['osfamily'], opts['image']) return_value = 'PARAM_CHANGED' # log.puts(return_value) 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']}") # log.puts("command: #{"docker inspect #{opts['sanitised_title']}"}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") 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_CHANGE_DETECTED' # log.puts(return_value) end else return_value = 'ARG_REQUIRED_MISSING' # log.puts(return_value) end if opts['container_running'] start_container(opts['sanitised_title'], opts['osfamily']) else stop_container(opts['sanitised_title'], opts['osfamily']) end # log.puts(return_value) # log.puts("### END detect_changes\n\n") # log.close return_value end end diff --git a/spec/acceptance/docker_params_changed_spec.rb b/spec/acceptance/docker_params_changed_spec.rb index a21af05..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 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 - docker_run_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 - docker_run_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 - docker_run_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 77ac507..ba0e525 100644 --- a/spec/acceptance/docker_spec.rb +++ b/spec/acceptance/docker_spec.rb @@ -1,329 +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 if fetch_puppet_version > 5 - docker_run_idempotent_apply(pp) + 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/helper/get_docker_params_changed.rb b/spec/helper/get_docker_params_changed.rb index e040912..61a2e89 100644 --- a/spec/helper/get_docker_params_changed.rb +++ b/spec/helper/get_docker_params_changed.rb @@ -1,177 +1,177 @@ # frozen_string_literal: true -def get_docker_params_changed(opts) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity +def get_docker_params_changed(opts) require 'open3' require 'json' return_value = 'No changes detected' # log = File.open("#{opts['logfile_path']}/docker_params.txt", 'a') # log.puts("### BEGIN detect_changes\n\n") if opts['sanitised_title'] && opts['osfamily'] stdout, stderr, status = Open3.capture3("docker inspect #{opts['sanitised_title']}") # log.puts("command: #{"docker inspect #{opts['sanitised_title']}"}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # log.puts("#{opts['sanitised_title']}\n\n") 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' # log.puts(return_value) 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']}") # log.puts("command: #{"docker inspect #{opts['sanitised_title']}"}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") 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' # log.puts(return_value) end else - return_value = 'Arg required missing' + return_value = 'Arg required missing' # log.puts(return_value) end if opts['container_running'] start_container(opts['sanitised_title'], opts['osfamily']) else stop_container(opts['sanitised_title'], opts['osfamily']) end # log.puts(return_value) # log.puts("### END detect_changes\n\n") # log.close return_value end def run_with_powershell(cmd) "powershell.exe -Command \"& {#{cmd}}\" " end def remove_cidfile(cidfile, osfamily, _logger = nil) delete_command = if osfamily == 'windows' run_with_powershell("del #{cidfile}") else "rm -f #{cidfile}" end _stdout, _stderr, _status = Open3.capture3(delete_command) # logger.puts("### BEGIN remove_cidfile\n\n") # logger.puts("command: #{delete_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # logger.puts("### END remove_cidfile\n\n") end def start_container(name, osfamily, _logger = nil) start_command = if osfamily == 'windows' run_with_powershell("docker start #{name}") else "docker start #{name}" end _stdout, _stderr, _status = Open3.capture3(start_command) # logger.puts("### BEGIN start_container\n\n") # logger.puts("command: #{start_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # logger.puts("### END start_container\n\n") end def stop_container(name, osfamily, _logger = nil) stop_command = if osfamily == 'windows' run_with_powershell("docker stop #{name}") else "docker stop #{name}" end _stdout, _stderr, _status = Open3.capture3(stop_command) # logger.puts("### BEGIN stop_container\n\n") # logger.puts("command: #{stop_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # logger.puts("### END stop_container\n\n") end def remove_container(name, osfamily, stop_wait_time, cidfile, _logger = nil) 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) # logger.puts("### BEGIN remove_container\n\n") # logger.puts("command: #{stop_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") 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) # logger.puts("command: #{remove_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") remove_cidfile(cidfile, osfamily) # logger.puts("### END remove_container\n\n") end def create_container(cmd, osfamily, image, _logger = nil) # logger.puts("### BEGIN create_container\n\n") 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) # logger.puts("command: #{pull_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") create_command = if osfamily == 'windows' run_with_powershell(cmd) else cmd end _stdout, _stderr, _status = Open3.capture3(create_command) # logger.puts("command: #{create_command}\nstdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}\n\n") # logger.puts("### END create_container\n\n") end diff --git a/spec/spec_helper_acceptance_local.rb b/spec/spec_helper_acceptance_local.rb index c341735..7bb8d91 100644 --- a/spec/spec_helper_acceptance_local.rb +++ b/spec/spec_helper_acceptance_local.rb @@ -1,204 +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 =~ %r{Notice: No changes detected} + 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