diff --git a/spec/acceptance/compose_v3_spec.rb b/spec/acceptance/compose_v3_spec.rb index a7324c7..06eda5e 100644 --- a/spec/acceptance/compose_v3_spec.rb +++ b/spec/acceptance/compose_v3_spec.rb @@ -1,157 +1,161 @@ require 'spec_helper_acceptance' if fact('osfamily') == 'windows' install_dir = '/cygdrive/c/Program Files/Docker' file_extension = '.exe' docker_args = 'docker_ee => true' tmp_path = 'C:/cygwin64/tmp' - test_container = 'nanoserver-sac2016' + if fact('os.release.major') == '2019' + test_container = 'nanoserver' + else + test_container = 'nanoserver-sac2016' + end else docker_args = if fact('os.name') == 'RedHat' "repo_opt => '--enablerepo=localmirror-extras'" elsif fact('os.name') == 'Centos' "repo_opt => '--enablerepo=localmirror-extras'" elsif fact('os.name') == 'Ubuntu' && fact('os.release.full') == '14.04' "version => '18.06.1~ce~3-0~ubuntu'" else '' end install_dir = '/usr/local/bin' file_extension = '' tmp_path = '/tmp' test_container = 'debian' end describe 'docker compose' do before(:all) do retry_on_error_matching(60, 5, %r{connection failure running}) do install_code = <<-code class { 'docker': #{docker_args} } class { 'docker::compose': version => '1.23.2', } code apply_manifest(install_code, catch_failures: true) end end context 'Creating compose v3 projects' do let(:install_pp) do <<-MANIFEST docker_compose { 'web': compose_files => ['#{tmp_path}/docker-compose-v3.yml'], ensure => present, } MANIFEST end it 'is idempotent' do idempotent_apply(default, install_pp, {}) end it 'has docker compose installed' do shell('docker-compose --help', acceptable_exit_codes: [0]) end it 'finds a docker container' do shell('docker inspect web_compose_test_1', acceptable_exit_codes: [0]) end end context 'creating compose projects with multi compose files' do before(:all) do install_pp = <<-MANIFEST docker_compose { 'web1': compose_files => ['#{tmp_path}/docker-compose-v3.yml', '#{tmp_path}/docker-compose-override-v3.yml'], ensure => present, } MANIFEST apply_manifest(install_pp, catch_failures: true) end it "should find container with #{test_container} tag" do shell("docker inspect web1_compose_test_1 | grep #{test_container}", acceptable_exit_codes: [0]) end end context 'Destroying project with multiple compose files' do let(:destroy_pp) do <<-MANIFEST docker_compose { 'web1': compose_files => ['#{tmp_path}/docker-compose-v3.yml', '#{tmp_path}/docker-compose-override-v3.yml'], ensure => absent, } MANIFEST end before(:all) do install_pp = <<-MANIFEST docker_compose { 'web1': compose_files => ['#{tmp_path}/docker-compose-v3.yml', '#{tmp_path}/docker-compose-override-v3.yml'], ensure => present, } MANIFEST apply_manifest(install_pp, catch_failures: true) end it 'is idempotent' do idempotent_apply(default, destroy_pp, {}) end it 'does not find a docker container' do shell('docker inspect web1_compose_test_1', acceptable_exit_codes: [1]) end end context 'Requesting a specific version of compose' do let(:version) do '1.21.2' end it 'is idempotent' do pp = <<-MANIFEST class { 'docker::compose': version => '#{version}', } MANIFEST idempotent_apply(default, pp, {}) end it 'has installed the requested version' do shell('docker-compose --version', acceptable_exit_codes: [0]) do |r| expect(r.stdout).to match(%r{#{version}}) end end end context 'Removing docker compose' do let(:version) do '1.21.2' end it 'is idempotent' do pp = <<-MANIFEST class { 'docker::compose': ensure => absent, version => '#{version}', } MANIFEST idempotent_apply(default, pp, {}) end it 'has removed the relevant files' do shell("test -e \"#{install_dir}/docker-compose#{file_extension}\"", acceptable_exit_codes: [1]) shell("test -e \"#{install_dir}/docker-compose-#{version}#{file_extension}\"", acceptable_exit_codes: [1]) end after(:all) do install_pp = <<-MANIFEST class { 'docker': #{docker_args}} class { 'docker::compose': } MANIFEST apply_manifest(install_pp, catch_failures: true) end end end diff --git a/spec/acceptance/docker_full_spec.rb b/spec/acceptance/docker_full_spec.rb index 13925bc..20393d9 100644 --- a/spec/acceptance/docker_full_spec.rb +++ b/spec/acceptance/docker_full_spec.rb @@ -1,958 +1,962 @@ require 'spec_helper_acceptance' if fact('kernel') == 'windows' docker_args = 'docker_ee => true' default_image = 'winamd64/hello-seattle' - default_image_tag = 'nanoserver-sac2016' + if fact('os.release.major') == '2019' + default_image_tag = 'nanoserver' + else + default_image_tag = 'nanoserver-sac2016' + end default_digest = 'sha256:dcba85354678b50608b8c40ec6d17cce063a224aa0e12b6a55dc47b67f039e75' second_image = 'winamd64/hola-mundo' default_dockerfile = 'C:/Users/Administrator/AppData/Local/Temp/Dockerfile' dockerfile_test = 'C:/Windows/Dockerfile_test.txt' # The default args are set because: # restart => 'always' - there is no service created to manage containers # net => 'nat' - docker uses bridged by default when running a container. When installing docker on windows the default network is NAT. default_docker_run_arg = "restart => 'always', net => 'nat'," default_run_command = 'ping 127.0.0.1 -t' docker_command = '"/cygdrive/c/Program Files/Docker/docker"' default_docker_exec_lr_command = 'cmd /c "ping 127.0.0.1 -t > c:\windows\temp\test_file.txt"' default_docker_exec_command = 'cmd /c "echo test > c:\windows\temp\test_file.txt"' docker_mount_path = 'C:/Users/Administrator/AppData/Local/Temp' storage_driver = 'windowsfilter' else docker_args = if fact('os.family') == 'RedHat' "repo_opt => '--enablerepo=localmirror-extras'" elsif fact('os.name') == 'Ubuntu' && fact('os.release.full') == '14.04' "version => '18.06.1~ce~3-0~ubuntu'" else '' end default_image = 'alpine' second_image = 'busybox' default_image_tag = '3.7' default_digest = 'sha256:3dcdb92d7432d56604d4545cbd324b14e647b313626d99b889d0626de158f73a' default_dockerfile = '/root/Dockerfile' dockerfile_test = "#{default_dockerfile}_test.txt" docker_command = 'docker' default_docker_run_arg = '' default_run_command = 'init' default_docker_exec_lr_command = '/bin/sh -c "touch /root/test_file.txt; while true; do echo hello world; sleep 1; done"' default_docker_exec_command = 'touch /root/test_file.txt' docker_mount_path = '/root' storage_driver = 'devicemapper' storage_driver = if fact('os.family') == 'Debian' && fact('os.release.major') =~ %r{14.04|^8$} 'aufs' elsif fact('os.family') == 'RedHat' 'devicemapper' else 'overlay2' end end describe 'the Puppet Docker module' do context 'clean up before each test' do before(:each) do retry_on_error_matching(60, 5, %r{connection failure running}) do # Stop all container using systemd shell('ls -D -1 /etc/systemd/system/docker-container* | sed \'s/\/etc\/systemd\/system\///g\' | sed \'s/\.service//g\' | while read container; do service $container stop; done') # Delete all running containers shell("#{docker_command} rm -f $(#{docker_command} ps -a -q) || true") # Delete all existing images shell("#{docker_command} rmi -f $(#{docker_command} images -q) || true") # Check to make sure no images are present shell("#{docker_command} images | wc -l") do |r| expect(r.stdout).to match(%r{^0|1$}) # rubocop:disable RSpec/ExpectInHook: end # Check to make sure no running containers are present shell("#{docker_command} ps | wc -l") do |r| expect(r.stdout).to match(%r{^0|1$}) # rubocop:disable RSpec/ExpectInHook: end end end describe 'docker class' do context 'without any parameters' do let(:pp) { "class { 'docker': #{docker_args} }" } it 'runs successfully' do apply_manifest(pp, catch_failures: true) end it 'runs idempotently' do apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' end it 'is start a docker process' do if fact('osfamily') == 'windows' shell('powershell Get-Process -Name dockerd') do |r| expect(r.stdout).to match(%r{ProcessName}) end else shell('ps aux | grep docker') do |r| expect(r.stdout).to match(%r{dockerd -H unix:\/\/\/var\/run\/docker.sock}) end end end it 'installs a working docker client' do shell("#{docker_command} ps", acceptable_exit_codes: [0]) end it 'stops a running container and remove container' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_6': image => '#{default_image}', command => '#{default_run_command}', require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS pp2 = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_6': ensure => 'absent', image => '#{default_image}', require => Docker::Image['#{default_image}'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 15 shell("#{docker_command} ps", acceptable_exit_codes: [0]) apply_manifest(pp2, catch_failures: true) apply_manifest(pp2, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 15 shell("#{docker_command} inspect container-3-6", acceptable_exit_codes: [1]) if fact('osfamily') == 'windows' shell('test -f /cygdrive/c/Users/Administrator/AppData/Local/Temp/container-3-6.service', acceptable_exit_codes: [1]) else shell('test -f /etc/systemd/system/container-3-6.service', acceptable_exit_codes: [1]) end end end context 'passing a storage driver' do let(:pp) do <<-MANIFEST class {'docker': #{docker_args}, storage_driver => "#{storage_driver}", } MANIFEST end it 'applies manifest' do apply_manifest(pp, catch_failures: true) sleep 15 end it 'results in the docker daemon being configured with the specified storage driver' do shell("#{docker_command} info -f \"{{ .Driver}}\"") do |r| expect(r.stdout).to match %r{#{storage_driver}} end end end context 'passing a TCP address to bind to' do let(:pp) do <<-MANIFEST class { 'docker': tcp_bind => 'tcp://127.0.0.1:4444', #{docker_args} } MANIFEST end it 'runs idempotently' do idempotent_apply(default, pp, {}) unless fact('selinux') == 'true' sleep 4 end it 'results in docker listening on the specified address' do if fact('osfamily') == 'windows' shell('netstat -a -b') do |r| expect(r.stdout).to match(%r{127.0.0.1:4444}) end else shell('netstat -tulpn | grep docker') do |r| expect(r.stdout).to match(%r{tcp\s+0\s+0\s+127.0.0.1:4444\s+0.0.0.0\:\*\s+LISTEN\s+\d+\/docker}) end end end end context 'bound to a particular unix socket' do let(:pp) do <<-MANIFEST class { 'docker': socket_bind => 'unix:///var/run/docker.sock', #{docker_args} } MANIFEST end it 'runs idempotently' do idempotent_apply(default, pp, {}) unless fact('selinux') == 'true' sleep 4 end it 'shows docker listening on the specified unix socket' do if fact('osfamily') != 'windows' shell('ps aux | grep docker') do |r| expect(r.stdout).to match(%r{unix:\/\/\/var\/run\/docker.sock}) end end end end context 'uninstall docker' do after(:all) do pp = <<-EOS class {'docker': #{docker_args}, ensure => 'present' } EOS apply_manifest(pp, catch_failures: true) # Wait for reboot if windows sleep 300 if fact('osfamily') == 'windows' end it 'uninstalls successfully' do pp = <<-EOS class {'docker': #{docker_args}, ensure => 'absent' } EOS apply_manifest(pp, catch_failures: true) sleep 4 shell('docker ps', acceptable_exit_codes: [1, 127]) end end end describe 'docker::image' do it 'successfullies download an image from the Docker Hub' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': ensure => present, require => Class['docker'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 shell("#{docker_command} images") do |r| expect(r.stdout).to match(%r{#{default_image}}) end end it 'successfullies download an image based on a tag from the Docker Hub' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': ensure => present, image_tag => '#{default_image_tag}', require => Class['docker'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 shell("#{docker_command} images") do |r| expect(r.stdout).to match(%r{#{default_image}\s+#{default_image_tag}}) end end it 'successfullies download an image based on a digest from the Docker Hub' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': ensure => present, image_digest => '#{default_digest}', require => Class['docker'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 shell("#{docker_command} images --digests") do |r| expect(r.stdout).to match(%r{#{default_image}.*#{default_digest}}) end end it 'creates a new image based on a Dockerfile' do run_cmd = if fact('osfamily') == 'windows' 'RUN echo test > C:\\Windows\\Temp\\Dockerfile_test.txt' else "RUN echo test > #{dockerfile_test}" end pp = <<-EOS class { 'docker': #{docker_args} } docker::image { 'alpine_with_file': docker_file => "#{default_dockerfile}", require => Class['docker'], } file { '#{default_dockerfile}': ensure => present, content => "FROM #{default_image}\n#{run_cmd}", before => Docker::Image['alpine_with_file'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 if fact('osfamily') == 'windows' shell("#{docker_command} run alpine_with_file cmd /c dir Windows\\\\Temp") do |r| expect(r.stdout).to match(%r{_test.txt}) end else shell("#{docker_command} run alpine_with_file ls #{dockerfile_test}") do |r| expect(r.stdout).to match(%r{#{dockerfile_test}}) end end end it 'creates a new image based on a tar', win_broken: true do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], ensure => present, } docker::run { 'container_2_4': image => '#{default_image}', command => '/bin/sh -c "touch /root/test_file_for_tar_test.txt; while true; do echo hello world; sleep 1; done"', require => Docker::Image['alpine'], } EOS pp2 = <<-EOS class { 'docker': #{docker_args} } docker::image { 'alpine_from_commit': docker_tar => "/root/rootfs.tar" } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 # Commit currently running container as an image container_id = shell("#{docker_command} ps | awk 'FNR == 2 {print $1}'") shell("#{docker_command} commit #{container_id.stdout.strip} alpine_from_commit") # Stop all container using systemd shell('ls -D -1 /etc/systemd/system/docker-container* | sed \'s/\/etc\/systemd\/system\///g\' | sed \'s/\.service//g\' | while read container; do service $container stop; done') # Stop all running containers shell("#{docker_command} rm -f $(docker ps -a -q) || true") # Make sure no other containers are running shell("#{docker_command} ps | wc -l") do |r| expect(r.stdout).to match(%r{^1$}) end # Export new to a tar file shell("#{docker_command} save alpine_from_commit > /root/rootfs.tar") # Remove all images shell("#{docker_command} rmi $(docker images -q) || true") # Make sure no other images are present shell("#{docker_command} images | wc -l") do |r| expect(r.stdout).to match(%r{^1$}) end apply_manifest(pp2, catch_failures: true) apply_manifest(pp2, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 shell("#{docker_command} run alpine_from_commit ls /root") do |r| expect(r.stdout).to match(%r{test_file_for_tar_test.txt}) end end it 'successfullies delete the image' do pp1 = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': ensure => present, require => Class['docker'], } EOS apply_manifest(pp1, catch_failures: true) pp2 = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': ensure => absent, } EOS apply_manifest(pp2, catch_failures: true) apply_manifest(pp2, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 shell("#{docker_command} images") do |r| expect(r.stdout).not_to match(%r{#{default_image}}) end end end describe 'docker::run' do it 'starts a container with a configurable command' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_1': image => '#{default_image}', command => '#{default_docker_exec_lr_command}', require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 container_id = shell("#{docker_command} ps | awk 'FNR == 2 {print $1}'") if fact('osfamily') == 'windows' shell("#{docker_command} exec #{container_id.stdout.strip} cmd /c dir Windows\\\\Temp") do |r| expect(r.stdout).to match(%r{test_file.txt}) end else shell("#{docker_command} exec #{container_id.stdout.strip} ls /root") do |r| expect(r.stdout).to match(%r{test_file.txt}) end end container_name = shell("#{docker_command} ps | awk 'FNR == 2 {print $NF}'") expect(container_name.stdout.strip.to_s).to match(%r{(container-3-1|container_3_1)}) end it 'starts a container with port configuration' do pp = <<-EOS class { 'docker': #{docker_args}} docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_2': image => '#{default_image}', command => '#{default_run_command}', ports => ['4444'], expose => ['5555'], require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 shell("#{docker_command} ps") do |r| expect(r.stdout).to match(%r{#{default_run_command}.+5555\/tcp\, 0\.0\.0.0\:\d+\-\>4444\/tcp}) end end it 'starts a container with the hostname set' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_3': image => '#{default_image}', command => '#{default_run_command}', hostname => 'testdomain.com', require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 container_id = shell("#{docker_command} ps | awk 'FNR == 2 {print $1}'") shell("#{docker_command} exec #{container_id.stdout.strip} hostname") do |r| expect(r.stdout).to match(%r{testdomain.com}) end end it 'starts a container while mounting local volumes' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_4': image => '#{default_image}', command => '#{default_run_command}', volumes => ["#{docker_mount_path}:#{docker_mount_path}/mnt:rw"], require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } file { '#{docker_mount_path}/test_mount.txt': ensure => present, before => Docker::Run['container_3_4'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 container_id = shell("#{docker_command} ps | awk 'FNR == 2 {print $1}'") if fact('osfamily') == 'windows' shell("#{docker_command} exec #{container_id.stdout.strip} cmd /c dir Users\\\\Administrator\\\\AppData\\\\Local\\\\Temp\\\\mnt") do |r| expect(r.stdout).to match(%r{test_mount.txt}) end else shell("#{docker_command} exec #{container_id.stdout.strip} ls /root/mnt") do |r| expect(r.stdout).to match(%r{test_mount.txt}) end end end # cpuset is not supported on Docker Windows # STDERR: C:/Program Files/Docker/docker.exe: Error response from daemon: invalid option: Windows does not support CpusetCpus. it 'starts a container with cpuset paramater set', win_broken: true do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_5_5': image => '#{default_image}', command => '#{default_run_command}', cpuset => ['0'], require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 shell('#{docker_command} inspect container_3_5_5') do |r| expect(r.stdout).to match(%r{"CpusetCpus"\: "0"}) end end # leagacy container linking was not implemented on Windows. --link is a legacy Docker feature: https://docs.docker.com/network/links/ it 'starts multiple linked containers', win_broken: true do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_5_1': image => '#{default_image}', command => '#{default_run_command}', require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 container1 = shell("#{docker_command} ps | awk 'FNR == 2 {print $NF}'") pp2 = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_5_2': image => '#{default_image}', command => '#{default_run_command}', depends => ['#{container1.stdout.strip}'], links => "#{container1.stdout.strip}:the_link", require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS apply_manifest(pp2, catch_failures: true) apply_manifest(pp2, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 container2 = shell("#{docker_command} ps | awk 'FNR == 2 {print $NF}'") container_id = shell("#{docker_command} ps | awk 'FNR == 2 {print $1}'") shell("#{docker_command} inspect -f \"{{ .HostConfig.Links }}\" #{container_id.stdout.strip}") do |r| expect(r.stdout).to match("/#{container1.stdout.strip}:/#{container2.stdout.strip}/the_link") end end it 'stops a running container' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_6': image => '#{default_image}', command => '#{default_run_command}', require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS pp2 = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_6': image => '#{default_image}', running => false, require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 shell("#{docker_command} ps | wc -l") do |r| expect(r.stdout).to match(%r{^2$}) end apply_manifest(pp2, catch_failures: true) apply_manifest(pp2, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 shell("#{docker_command} ps | wc -l") do |r| expect(r.stdout).to match(%r{^1$}) end end it 'stops a running container and remove container' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_6_1': image => '#{default_image}', command => '#{default_run_command}', require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS pp2 = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_3_6_1': ensure => 'absent', image => '#{default_image}', require => Docker::Image['#{default_image}'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 15 shell("#{docker_command} inspect container_3_6_1", acceptable_exit_codes: [0]) apply_manifest(pp2, catch_failures: true) apply_manifest(pp2, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 15 shell("#{docker_command} inspect container_3_6_1", acceptable_exit_codes: [1]) end it 'allows dependency for ordering of independent run and image' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': } docker::run { 'container_3_7_1': image => '#{default_image}', command => '#{default_run_command}', #{default_docker_run_arg} } docker::image { '#{second_image}': require => Docker::Run['container_3_7_1'], } docker::run { 'container_3_7_2': image => '#{second_image}', command => '#{default_run_command}', #{default_docker_run_arg} } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' end it 'restarts a unhealthy container' do pp5 = <<-EOS class { 'docker': #{docker_args} } docker::run { 'container_3_7_3': image => '#{default_image}', command => '#{default_run_command}', health_check_cmd => 'echo', restart_on_unhealthy => true, #{default_docker_run_arg} } EOS pp_delete = <<-EOS class { 'docker': #{docker_args} } docker::run { 'container_3_7_3': image => '#{default_image}', ensure => absent, } EOS if fact('osfamily') == 'windows' apply_manifest(pp5, catch_failures: true) elsif fact('os.release.major') =~ %r{14.04|8} apply_manifest(pp5, catch_failures: true) do |r| expect(r.stdout).to match(%r{container_3_7_3}) end else apply_manifest(pp5, catch_failures: true) do |r| expect(r.stdout).to match(%r{docker-container_3_7_3-systemd-reload}) end end apply_manifest(pp_delete, catch_failures: true) end end end describe 'docker::exec' do it 'runs a command inside an already running container' do pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': require => Class['docker'], } docker::run { 'container_4_1': image => '#{default_image}', command => '#{default_run_command}', require => Docker::Image['#{default_image}'], #{default_docker_run_arg} } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 15 container1 = shell("#{docker_command} ps | awk 'FNR == 2 {print $NF}'") pp2 = <<-EOS class { 'docker': #{docker_args} } docker::exec { 'test_command': container => '#{container1.stdout.strip}', command => '#{default_docker_exec_command}', tty => true, } EOS pp_delete = <<-EOS docker::run { 'container_4_1': image => '#{default_image}', ensure => absent, } EOS apply_manifest(pp2, catch_failures: true) # A sleep to give docker time to execute properly sleep 4 container_id = shell("#{docker_command} ps | awk 'FNR == 2 {print $1}'") if fact('osfamily') == 'windows' shell("#{docker_command} exec #{container_id.stdout.strip} cmd /c dir Windows\\\\Temp") do |r| expect(r.stdout).to match(%r{test_file.txt}) end else shell("#{docker_command} exec #{container_id.stdout.strip} ls /root") do |r| expect(r.stdout).to match(%r{test_file.txt}) end end apply_manifest(pp_delete, catch_failures: true) end it 'onlies run if notified when refreshonly is true' do container_name = 'container_4_2' pp = <<-EOS class { 'docker': #{docker_args} } docker::image { '#{default_image}': } docker::run { '#{container_name}': image => '#{default_image}', command => '#{default_run_command}', #{default_docker_run_arg} } docker::exec { 'test_command': container => '#{container_name}', command => '#{default_docker_exec_command}', refreshonly => true, } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 if fact('osfamily') == 'windows' shell("#{docker_command} exec #{container_name} cmd /c dir Windows\\\\Temp") do |r| expect(r.stdout).not_to match(%r{test_file.txt}) end else shell("#{docker_command} exec #{container_name} ls /root") do |r| expect(r.stdout).not_to match(%r{test_file.txt}) end end pp_extra = <<-EOS file { '#{default_dockerfile}_dummy_file': ensure => 'present', notify => Docker::Exec['test_command'], } EOS pp_delete = <<-EOS docker::run { '#{container_name}': image => '#{default_image}', ensure => absent, } EOS pp2 = pp + pp_extra apply_manifest(pp2, catch_failures: true) apply_manifest(pp2, catch_changes: true) unless fact('selinux') == 'true' # A sleep to give docker time to execute properly sleep 4 if fact('osfamily') == 'windows' shell("#{docker_command} exec #{container_name} cmd /c dir Windows\\\\Temp") do |r| expect(r.stdout).to match(%r{test_file.txt}) end else shell("#{docker_command} exec #{container_name} ls /root") do |r| expect(r.stdout).to match(%r{test_file.txt}) end end apply_manifest(pp_delete, catch_failures: true) end end end diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index 39ab4ce..7ba1937 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -1,185 +1,207 @@ # frozen_string_literal: true require 'beaker-rspec/spec_helper' require 'beaker-rspec/helpers/serverspec' require 'beaker/puppet_install_helper' require 'rspec/retry' begin require 'pry' rescue LoadError # rubocop:disable Lint/HandleExceptions for optional loading end def idempotent_apply(hosts, manifest, opts = {}, &block) block_on hosts, opts do |host| file_path = host.tmpfile('apply_manifest.pp') create_remote_file(host, file_path, manifest + "\n") puppet_apply_opts = { :verbose => nil, 'detailed-exitcodes' => nil } on_options = { acceptable_exit_codes: [0, 2] } on host, puppet('apply', file_path, puppet_apply_opts), on_options, &block puppet_apply_opts2 = { :verbose => nil, 'detailed-exitcodes' => nil } on_options2 = { acceptable_exit_codes: [0] } on host, puppet('apply', file_path, puppet_apply_opts2), on_options2, &block end end # 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 run_puppet_install_helper unless ENV['BEAKER_provision'] == 'no' RSpec.configure do |c| # Add exclusive filter for Windows untill all the windows functionality is implemented c.filter_run_excluding win_broken: true # Project root proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) # 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 hosts.each do |host| next unless not_controller(host) copy_module_to(host, source: proj_root, module_name: 'docker') # Due to RE-6764, running yum update renders the machine unable to install # other software. Thus this workaround. if fact_on(host, 'operatingsystem') == 'RedHat' on(host, 'mv /etc/yum.repos.d/redhat.repo /etc/yum.repos.d/internal-mirror.repo') on(host, 'rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm') end on(host, 'yum update -y -q') if fact_on(host, 'osfamily') == 'RedHat' on host, puppet('module', 'install', 'puppetlabs-stdlib', '--version', '4.24.0'), acceptable_exit_codes: [0, 1] on host, puppet('module', 'install', 'puppetlabs-apt', '--version', '4.4.1'), acceptable_exit_codes: [0, 1] on host, puppet('module', 'install', 'puppetlabs-translate', '--version', '1.0.0'), acceptable_exit_codes: [0, 1] on host, puppet('module', 'install', 'puppetlabs-powershell', '--version', '2.1.5'), acceptable_exit_codes: [0, 1] on host, puppet('module', 'install', 'puppetlabs-reboot', '--version', '2.0.0'), acceptable_exit_codes: [0, 1] # net-tools required for netstat utility being used by some tests if fact_on(host, 'osfamily') == 'RedHat' && fact_on(host, 'operatingsystemmajrelease') == '7' on(host, 'yum install -y net-tools device-mapper') end if fact_on(host, 'osfamily') == 'Debian' on(host, 'apt-get install net-tools') 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_windows_2016 = <<-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_windows_2016 = <<-EOS +version: "3" services: compose_test: image: winamd64/hello-seattle:nanoserver-sac2016 EOS if fact_on(host, 'osfamily') == '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) - 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) + if fact_on(host, 'os.release.major') == '2019' + create_remote_file(host, '/tmp/docker-compose-override-v3.yml', docker_stack_override_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_stack_override_windows_2016) + create_remote_file(host, '/tmp/docker-stack-override.yml', docker_stack_override_windows_2016) + 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 fact_on(host, 'osfamily') == 'windows' win_host = only_host_with_role(hosts, 'default') retry_on_error_matching(60, 5, %r{connection failure running}) do @windows_ip = win_host.ip end apply_manifest_on(host, "class { 'docker': docker_ee => true, extra_parameters => '\"insecure-registries\": [ \"#{@windows_ip}:5000\" ]' }") docker_path = '/cygdrive/c/Program Files/Docker' host.add_env_var('PATH', docker_path) host.add_env_var('TEMP', 'C:\Users\Administrator\AppData\Local\Temp') puts 'Waiting for box to come online' sleep 300 end end end