From 17e4123e1eeac1790ea258e7ad6f807571edac4f Mon Sep 17 00:00:00 2001 From: Dan Webb Date: Tue, 14 Nov 2023 13:53:02 +0000 Subject: [PATCH 1/3] Switch to Buildx builder Signed-off-by: Dan Webb --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 607d856..7741640 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,8 @@ jobs: with: ruby-version: "3.1" bundler-cache: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - run: bundle exec kitchen test ${{ matrix.suite }}-${{ matrix.os }} integration-linux: @@ -91,4 +93,6 @@ jobs: with: ruby-version: "3.1" bundler-cache: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - run: bundle exec kitchen test ${{ matrix.suite }}-${{ matrix.os }} From 5036c0da75c170c05d2b22d2b0113a671f6c18ce Mon Sep 17 00:00:00 2001 From: Dan Webb Date: Tue, 14 Nov 2023 14:11:25 +0000 Subject: [PATCH 2/3] Update platforms Signed-off-by: Dan Webb --- .github/workflows/ci.yml | 33 +- .rubocop.yml | 10 + kitchen.yml | 9 +- lib/kitchen/docker/helpers/cli_helper.rb | 356 +++++++++--------- .../docker/helpers/container_helper.rb | 348 ++++++++--------- test/spec/spec_helper.rb | 2 +- 6 files changed, 403 insertions(+), 355 deletions(-) create mode 100644 .rubocop.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7741640..1805a2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,12 +68,17 @@ jobs: integration-linux: name: Linux ${{matrix.suite}} ${{matrix.os}} - runs-on: windows-latest + runs-on: ubuntu-latest needs: unit strategy: fail-fast: false matrix: - suite: [default, context, capabilities, arm64, amd64, inspec] + suite: + - default + - no_build_context + - arm64 + - amd64 + - inspec os: - amazonlinux-2 - ubuntu-18.04 @@ -83,8 +88,8 @@ jobs: - centos-8 - oraclelinux-7 - rockylinux-8 - - debian-9 - - debian-10 + - debian-11 + - debian-12 - opensuse-15 - dockerfile steps: @@ -96,3 +101,23 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - run: bundle exec kitchen test ${{ matrix.suite }}-${{ matrix.os }} + + integration-capabilities: + name: Linux ${{matrix.suite}} ${{matrix.os}} + runs-on: ubuntu-latest + needs: unit + strategy: + fail-fast: false + matrix: + suite: + - capabilities + os: [debian-11, ubuntu-18.04, ubuntu-20.04] + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.1" + bundler-cache: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - run: bundle exec kitchen test ${{ matrix.suite }}-${{ matrix.os }} diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..46802c5 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,10 @@ +require: + - chefstyle + +AllCops: + TargetRubyVersion: 3.1 + Include: + - "**/*.rb" + Exclude: + - "vendor/**/*" + - "spec/**/*" diff --git a/kitchen.yml b/kitchen.yml index f3579df..dd1091b 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -24,8 +24,8 @@ platforms: - name: centos-7 - name: oraclelinux-7 - name: rockylinux-8 - - name: debian-9 - - name: debian-10 + - name: debian-11 + - name: debian-12 - name: opensuse-15 driver: image: opensuse/leap:15 @@ -38,9 +38,7 @@ platforms: suites: - name: default - excludes: [arch, debian-9] - - name: context - excludes: [arch, debian-9] + - name: no_build_context driver: build_context: false - name: capabilities @@ -52,7 +50,6 @@ suites: cap_drop: - NET_ADMIN - name: arm64 - excludes: [debian-9] driver: docker_platform: linux/arm64 - name: amd64 diff --git a/lib/kitchen/docker/helpers/cli_helper.rb b/lib/kitchen/docker/helpers/cli_helper.rb index 1da52bb..e4695d3 100644 --- a/lib/kitchen/docker/helpers/cli_helper.rb +++ b/lib/kitchen/docker/helpers/cli_helper.rb @@ -1,172 +1,184 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'kitchen' -require 'kitchen/configurable' -require 'kitchen/logging' -require 'kitchen/shell_out' - -module Kitchen - module Docker - module Helpers - module CliHelper - include Configurable - include Logging - include ShellOut - - def docker_command(cmd, options={}) - docker = config[:binary].dup - docker << " -H #{config[:socket]}" if config[:socket] - docker << ' --tls' if config[:tls] - docker << ' --tlsverify' if config[:tls_verify] - docker << " --tlscacert=#{config[:tls_cacert]}" if config[:tls_cacert] - docker << " --tlscert=#{config[:tls_cert]}" if config[:tls_cert] - docker << " --tlskey=#{config[:tls_key]}" if config[:tls_key] - logger.debug("docker_command: #{docker} #{cmd} shell_opts: #{docker_shell_opts(options)}") - run_command("#{docker} #{cmd}", docker_shell_opts(options)) - end - - # Copied from kitchen because we need stderr - def run_command(cmd, options = {}) - if options.fetch(:use_sudo, false) - cmd = "#{options.fetch(:sudo_command, "sudo -E")} #{cmd}" - end - subject = "[#{options.fetch(:log_subject, "local")} command]" - - debug("#{subject} BEGIN (#{cmd})") - sh = Mixlib::ShellOut.new(cmd, shell_opts(options)) - sh.run_command - debug("#{subject} END #{Util.duration(sh.execution_time)}") - sh.error! - sh.stdout + sh.stderr - rescue Mixlib::ShellOut::ShellCommandFailed => ex - raise ShellCommandFailed, ex.message - rescue Exception => error # rubocop:disable Lint/RescueException - error.extend(Kitchen::Error) - raise - end - - def build_run_command(image_id, transport_port = nil) - cmd = 'run -d' - cmd << ' -i' if config[:interactive] - cmd << ' -t' if config[:tty] - cmd << build_env_variable_args(config[:env_variables]) if config[:env_variables] - cmd << " -p #{transport_port}" unless transport_port.nil? - Array(config[:forward]).each { |port| cmd << " -p #{port}" } - Array(config[:dns]).each { |dns| cmd << " --dns #{dns}" } - Array(config[:add_host]).each { |host, ip| cmd << " --add-host=#{host}:#{ip}" } - Array(config[:volume]).each { |volume| cmd << " -v #{volume}" } - Array(config[:volumes_from]).each { |container| cmd << " --volumes-from #{container}" } - Array(config[:links]).each { |link| cmd << " --link #{link}" } - Array(config[:devices]).each { |device| cmd << " --device #{device}" } - Array(config[:mount]).each {|mount| cmd << " --mount #{mount}"} - Array(config[:tmpfs]).each {|tmpfs| cmd << " --tmpfs #{tmpfs}"} - cmd << " --name #{config[:instance_name]}" if config[:instance_name] - cmd << ' -P' if config[:publish_all] - cmd << " -h #{config[:hostname]}" if config[:hostname] - cmd << " -m #{config[:memory]}" if config[:memory] - cmd << " -c #{config[:cpu]}" if config[:cpu] - cmd << " --gpus #{config[:gpus]}" if config[:gpus] - cmd << " -e http_proxy=#{config[:http_proxy]}" if config[:http_proxy] - cmd << " -e https_proxy=#{config[:https_proxy]}" if config[:https_proxy] - cmd << ' --privileged' if config[:privileged] - cmd << " --isolation #{config[:isolation]}" if config[:isolation] - Array(config[:cap_add]).each { |cap| cmd << " --cap-add=#{cap}"} if config[:cap_add] - Array(config[:cap_drop]).each { |cap| cmd << " --cap-drop=#{cap}"} if config[:cap_drop] - Array(config[:security_opt]).each { |opt| cmd << " --security-opt=#{opt}"} if config[:security_opt] - cmd << " --platform=#{config[:docker_platform]}" if config[:docker_platform] - extra_run_options = config_to_options(config[:run_options]) - cmd << " #{extra_run_options}" unless extra_run_options.empty? - cmd << " #{image_id} #{config[:run_command]}" - logger.debug("build_run_command: #{cmd}") - cmd - end - - def build_exec_command(state, command) - cmd = 'exec' - cmd << ' -d' if config[:detach] - cmd << build_env_variable_args(config[:env_variables]) if config[:env_variables] - cmd << ' --privileged' if config[:privileged] - cmd << ' -t' if config[:tty] - cmd << ' -i' if config[:interactive] - cmd << " -u #{config[:username]}" if config[:username] - cmd << " -w #{config[:working_dir]}" if config[:working_dir] - cmd << " #{state[:container_id]}" - cmd << " #{command}" - logger.debug("build_exec_command: #{cmd}") - cmd - end - - def build_copy_command(local_file, remote_file, opts = {}) - cmd = 'cp' - cmd << ' -a' if opts[:archive] - cmd << " #{local_file} #{remote_file}" - cmd - end - - def build_powershell_command(args) - cmd = 'powershell -ExecutionPolicy Bypass -NoLogo ' - cmd << args - logger.debug("build_powershell_command: #{cmd}") - cmd - end - - def build_env_variable_args(vars) - raise ActionFailed, 'Environment variables are not of a Hash type' unless vars.is_a?(Hash) - - args = '' - vars.each do |k, v| - args << " -e #{k.to_s.strip}=\"#{v.to_s.strip}\"" - end - - args - end - - def dev_null - case RbConfig::CONFIG['host_os'] - when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - 'NUL' - else - '/dev/null' - end - end - - def docker_shell_opts(options = {}) - options[:live_stream] = nil if options[:suppress_output] - options.delete(:suppress_output) - - options - end - - # Convert the config input for `:build_options` or `:run_options` in to a - # command line string for use with Docker. - # - # @since 2.5.0 - # @param config [nil, String, Array, Hash] Config data to convert. - # @return [String] - def config_to_options(config) - case config - when nil - '' - when String - config - when Array - config.map { |c| config_to_options(c) }.join(' ') - when Hash - config.map { |k, v| Array(v).map { |c| "--#{k}=#{Shellwords.escape(c)}" }.join(' ') }.join(' ') - end - end - end - end - end -end +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'kitchen' +require 'kitchen/configurable' +require 'kitchen/logging' +require 'kitchen/shell_out' + +module Kitchen + module Docker + module Helpers + # rubocop:disable Metrics/ModuleLength, Style/Documentation + module CliHelper + include Configurable + include Logging + include ShellOut + + # rubocop:disable Metrics/AbcSize + def docker_command(cmd, options={}) + docker = config[:binary].dup + docker << " -H #{config[:socket]}" if config[:socket] + docker << ' --tls' if config[:tls] + docker << ' --tlsverify' if config[:tls_verify] + docker << " --tlscacert=#{config[:tls_cacert]}" if config[:tls_cacert] + docker << " --tlscert=#{config[:tls_cert]}" if config[:tls_cert] + docker << " --tlskey=#{config[:tls_key]}" if config[:tls_key] + logger.debug("docker_command: #{docker} #{cmd} shell_opts: #{docker_shell_opts(options)}") + run_command("#{docker} #{cmd}", docker_shell_opts(options)) + end + # rubocop:enable Metrics/AbcSize + + # Copied from kitchen because we need stderr + # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + def run_command(cmd, options = {}) + if options.fetch(:use_sudo, false) + cmd = "#{options.fetch(:sudo_command, "sudo -E")} #{cmd}" + end + subject = "[#{options.fetch(:log_subject, "local")} command]" + + debug("#{subject} BEGIN (#{cmd})") + sh = Mixlib::ShellOut.new(cmd, shell_opts(options)) + sh.run_command + debug("#{subject} END #{Util.duration(sh.execution_time)}") + sh.error! + sh.stdout + sh.stderr + rescue Mixlib::ShellOut::ShellCommandFailed => ex + raise ShellCommandFailed, ex.message + rescue Exception => error # rubocop:disable Lint/RescueException + error.extend(Kitchen::Error) + raise + end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize + + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize + def build_run_command(image_id, transport_port = nil) + cmd = 'run -d' + cmd << ' -i' if config[:interactive] + cmd << ' -t' if config[:tty] + cmd << build_env_variable_args(config[:env_variables]) if config[:env_variables] + cmd << " -p #{transport_port}" unless transport_port.nil? + Array(config[:forward]).each { |port| cmd << " -p #{port}" } + Array(config[:dns]).each { |dns| cmd << " --dns #{dns}" } + Array(config[:add_host]).each { |host, ip| cmd << " --add-host=#{host}:#{ip}" } + Array(config[:volume]).each { |volume| cmd << " -v #{volume}" } + Array(config[:volumes_from]).each { |container| cmd << " --volumes-from #{container}" } + Array(config[:links]).each { |link| cmd << " --link #{link}" } + Array(config[:devices]).each { |device| cmd << " --device #{device}" } + Array(config[:mount]).each {|mount| cmd << " --mount #{mount}"} + Array(config[:tmpfs]).each {|tmpfs| cmd << " --tmpfs #{tmpfs}"} + cmd << " --name #{config[:instance_name]}" if config[:instance_name] + cmd << ' -P' if config[:publish_all] + cmd << " -h #{config[:hostname]}" if config[:hostname] + cmd << " -m #{config[:memory]}" if config[:memory] + cmd << " -c #{config[:cpu]}" if config[:cpu] + cmd << " --gpus #{config[:gpus]}" if config[:gpus] + cmd << " -e http_proxy=#{config[:http_proxy]}" if config[:http_proxy] + cmd << " -e https_proxy=#{config[:https_proxy]}" if config[:https_proxy] + cmd << ' --privileged' if config[:privileged] + cmd << " --isolation #{config[:isolation]}" if config[:isolation] + Array(config[:cap_add]).each { |cap| cmd << " --cap-add=#{cap}"} if config[:cap_add] + Array(config[:cap_drop]).each { |cap| cmd << " --cap-drop=#{cap}"} if config[:cap_drop] + Array(config[:security_opt]).each { |opt| cmd << " --security-opt=#{opt}"} if config[:security_opt] + cmd << " --platform=#{config[:docker_platform]}" if config[:docker_platform] + extra_run_options = config_to_options(config[:run_options]) + cmd << " #{extra_run_options}" unless extra_run_options.empty? + cmd << " #{image_id} #{config[:run_command]}" + logger.debug("build_run_command: #{cmd}") + cmd + end + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize + + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize + def build_exec_command(state, command) + cmd = 'exec' + cmd << ' -d' if config[:detach] + cmd << build_env_variable_args(config[:env_variables]) if config[:env_variables] + cmd << ' --privileged' if config[:privileged] + cmd << ' -t' if config[:tty] + cmd << ' -i' if config[:interactive] + cmd << " -u #{config[:username]}" if config[:username] + cmd << " -w #{config[:working_dir]}" if config[:working_dir] + cmd << " #{state[:container_id]}" + cmd << " #{command}" + logger.debug("build_exec_command: #{cmd}") + cmd + end + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize + + def build_copy_command(local_file, remote_file, opts = {}) + cmd = 'cp' + cmd << ' -a' if opts[:archive] + cmd << " #{local_file} #{remote_file}" + cmd + end + + def build_powershell_command(args) + cmd = 'powershell -ExecutionPolicy Bypass -NoLogo ' + cmd << args + logger.debug("build_powershell_command: #{cmd}") + cmd + end + + def build_env_variable_args(vars) + raise ActionFailed, 'Environment variables are not of a Hash type' unless vars.is_a?(Hash) + + args = '' + vars.each do |k, v| + args << " -e #{k.to_s.strip}=\"#{v.to_s.strip}\"" + end + + args + end + + def dev_null + case RbConfig::CONFIG['host_os'] + when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ + 'NUL' + else + '/dev/null' + end + end + + def docker_shell_opts(options = {}) + options[:live_stream] = nil if options[:suppress_output] + options.delete(:suppress_output) + + options + end + + # Convert the config input for `:build_options` or `:run_options` in to a + # command line string for use with Docker. + # + # @since 2.5.0 + # @param config [nil, String, Array, Hash] Config data to convert. + # @return [String] + # rubocop:disable Metrics/CyclomaticComplexity + def config_to_options(config) + case config + when nil + '' + when String + config + when Array + config.map { |c| config_to_options(c) }.join(' ') + when Hash + config.map { |k, v| Array(v).map { |c| "--#{k}=#{Shellwords.escape(c)}" }.join(' ') }.join(' ') + end + end + # rubocop:enable Metrics/CyclomaticComplexity + end + # rubocop:enable Metrics/ModuleLength, Style/Documentation + end + end +end diff --git a/lib/kitchen/docker/helpers/container_helper.rb b/lib/kitchen/docker/helpers/container_helper.rb index 6ddb1ef..6338cf2 100644 --- a/lib/kitchen/docker/helpers/container_helper.rb +++ b/lib/kitchen/docker/helpers/container_helper.rb @@ -1,172 +1,176 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'erb' -require 'json' -require 'shellwords' -require 'tempfile' -require 'uri' - -require 'kitchen' -require 'kitchen/configurable' -require_relative '../erb_context' -require_relative 'cli_helper' - -module Kitchen - module Docker - module Helpers - module ContainerHelper - include Configurable - include Kitchen::Docker::Helpers::CliHelper - - def parse_container_id(output) - container_id = output.chomp - - unless [12, 64].include?(container_id.size) - raise ActionFailed, 'Could not parse Docker run output for container ID' - end - - container_id - end - - def dockerfile_template - template = IO.read(File.expand_path(config[:dockerfile])) - context = Kitchen::Docker::ERBContext.new(config.to_hash) - ERB.new(template).result(context.get_binding) - end - - def remote_socket? - config[:socket] ? socket_uri.scheme == 'tcp' : false - end - - def socket_uri - URI.parse(config[:socket]) - end - - def dockerfile_path(file) - config[:build_context] ? Pathname.new(file.path).relative_path_from(Pathname.pwd).to_s : file.path - end - - def container_exists?(state) - state[:container_id] && !!docker_command("top #{state[:container_id]}") rescue false - end - - def container_exec(state, command) - cmd = build_exec_command(state, command) - docker_command(cmd) - rescue => e - raise "Failed to execute command on Docker container. #{e}" - end - - def create_dir_on_container(state, path) - path = replace_env_variables(state, path) - cmd = "mkdir -p #{path}" - - if state[:platform].include?('windows') - psh = "-Command if(-not (Test-Path \'#{path}\')) { New-Item -Path \'#{path}\' -Force }" - cmd = build_powershell_command(psh) - end - - cmd = build_exec_command(state, cmd) - docker_command(cmd) - rescue => e - raise "Failed to create directory #{path} on container. #{e}" - end - - def copy_file_to_container(state, local_file, remote_file) - debug("Copying local file #{local_file} to #{remote_file} on container") - - remote_file = replace_env_variables(state, remote_file) - - remote_file = "#{state[:container_id]}:#{remote_file}" - cmd = build_copy_command(local_file, remote_file) - docker_command(cmd) - rescue => e - raise "Failed to copy file #{local_file} to container. #{e}" - end - - def container_env_variables(state) - # Retrieves all environment variables from inside container - vars = {} - - if state[:platform].include?('windows') - cmd = build_powershell_command('-Command [System.Environment]::GetEnvironmentVariables() ^| ConvertTo-Json') - cmd = build_exec_command(state, cmd) - stdout = docker_command(cmd, suppress_output: !logger.debug?).strip - vars = ::JSON.parse(stdout) - else - cmd = build_exec_command(state, 'printenv') - stdout = docker_command(cmd, suppress_output: !logger.debug?).strip - stdout.split("\n").each { |line| vars[line.split('=')[0]] = line.split('=')[1] } - end - - vars - end - - def replace_env_variables(state, str) - if str.include?('$env:') - key = str[/\$env:(.*?)(\\|$)/, 1] - value = container_env_variables(state)[key].to_s.strip - str = str.gsub("$env:#{key}", value) - elsif str.include?('$') - key = str[/\$(.*?)(\/|$)/, 1] - value = container_env_variables(state)[key].to_s.strip - str = str.gsub("$#{key}", value) - end - - str - end - - def run_container(state, transport_port = nil) - cmd = build_run_command(state[:image_id], transport_port) - output = docker_command(cmd) - parse_container_id(output) - end - - def container_ip_address(state) - cmd = "inspect --format '{{ .NetworkSettings.IPAddress }}'" - cmd << " #{state[:container_id]}" - docker_command(cmd).strip - rescue - raise ActionFailed, 'Error getting internal IP of Docker container' - end - - def remove_container(state) - container_id = state[:container_id] - docker_command("stop -t 0 #{container_id}") - docker_command("rm #{container_id}") - end - - def dockerfile_proxy_config - env_variables = '' - if config[:http_proxy] - env_variables << "ENV http_proxy #{config[:http_proxy]}\n" - env_variables << "ENV HTTP_PROXY #{config[:http_proxy]}\n" - end - - if config[:https_proxy] - env_variables << "ENV https_proxy #{config[:https_proxy]}\n" - env_variables << "ENV HTTPS_PROXY #{config[:https_proxy]}\n" - end - - if config[:no_proxy] - env_variables << "ENV no_proxy #{config[:no_proxy]}\n" - env_variables << "ENV NO_PROXY #{config[:no_proxy]}\n" - end - - env_variables - end - end - end - end -end +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'erb' +require 'json' +require 'shellwords' +require 'tempfile' +require 'uri' + +require 'kitchen' +require 'kitchen/configurable' +require_relative '../erb_context' +require_relative 'cli_helper' + +module Kitchen + module Docker + module Helpers + # rubocop:disable Metrics/ModuleLength, Style/Documentation + module ContainerHelper + include Configurable + include Kitchen::Docker::Helpers::CliHelper + + def parse_container_id(output) + container_id = output.chomp + + unless [12, 64].include?(container_id.size) + raise ActionFailed, 'Could not parse Docker run output for container ID' + end + + container_id + end + + def dockerfile_template + template = IO.read(File.expand_path(config[:dockerfile])) + context = Kitchen::Docker::ERBContext.new(config.to_hash) + ERB.new(template).result(context.get_binding) + end + + def remote_socket? + config[:socket] ? socket_uri.scheme == 'tcp' : false + end + + def socket_uri + URI.parse(config[:socket]) + end + + def dockerfile_path(file) + config[:build_context] ? Pathname.new(file.path).relative_path_from(Pathname.pwd).to_s : file.path + end + + def container_exists?(state) + state[:container_id] && !!docker_command("top #{state[:container_id]}") rescue false + end + + def container_exec(state, command) + cmd = build_exec_command(state, command) + docker_command(cmd) + rescue => e + raise "Failed to execute command on Docker container. #{e}" + end + + def create_dir_on_container(state, path) + path = replace_env_variables(state, path) + cmd = "mkdir -p #{path}" + + if state[:platform].include?('windows') + psh = "-Command if(-not (Test-Path \'#{path}\')) { New-Item -Path \'#{path}\' -Force }" + cmd = build_powershell_command(psh) + end + + cmd = build_exec_command(state, cmd) + docker_command(cmd) + rescue => e + raise "Failed to create directory #{path} on container. #{e}" + end + + def copy_file_to_container(state, local_file, remote_file) + debug("Copying local file #{local_file} to #{remote_file} on container") + + remote_file = replace_env_variables(state, remote_file) + + remote_file = "#{state[:container_id]}:#{remote_file}" + cmd = build_copy_command(local_file, remote_file) + docker_command(cmd) + rescue => e + raise "Failed to copy file #{local_file} to container. #{e}" + end + + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def container_env_variables(state) + # Retrieves all environment variables from inside container + vars = {} + + if state[:platform].include?('windows') + cmd = build_powershell_command('-Command [System.Environment]::GetEnvironmentVariables() ^| ConvertTo-Json') + cmd = build_exec_command(state, cmd) + stdout = docker_command(cmd, suppress_output: !logger.debug?).strip + vars = ::JSON.parse(stdout) + else + cmd = build_exec_command(state, 'printenv') + stdout = docker_command(cmd, suppress_output: !logger.debug?).strip + stdout.split("\n").each { |line| vars[line.split('=')[0]] = line.split('=')[1] } + end + + vars + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + + def replace_env_variables(state, str) + if str.include?('$env:') + key = str[/\$env:(.*?)(\\|$)/, 1] + value = container_env_variables(state)[key].to_s.strip + str = str.gsub("$env:#{key}", value) + elsif str.include?('$') + key = str[/\$(.*?)(\/|$)/, 1] + value = container_env_variables(state)[key].to_s.strip + str = str.gsub("$#{key}", value) + end + + str + end + + def run_container(state, transport_port = nil) + cmd = build_run_command(state[:image_id], transport_port) + output = docker_command(cmd) + parse_container_id(output) + end + + def container_ip_address(state) + cmd = "inspect --format '{{ .NetworkSettings.IPAddress }}'" + cmd << " #{state[:container_id]}" + docker_command(cmd).strip + rescue + raise ActionFailed, 'Error getting internal IP of Docker container' + end + + def remove_container(state) + container_id = state[:container_id] + docker_command("stop -t 0 #{container_id}") + docker_command("rm #{container_id}") + end + + def dockerfile_proxy_config + env_variables = '' + if config[:http_proxy] + env_variables << "ENV http_proxy #{config[:http_proxy]}\n" + env_variables << "ENV HTTP_PROXY #{config[:http_proxy]}\n" + end + + if config[:https_proxy] + env_variables << "ENV https_proxy #{config[:https_proxy]}\n" + env_variables << "ENV HTTPS_PROXY #{config[:https_proxy]}\n" + end + + if config[:no_proxy] + env_variables << "ENV no_proxy #{config[:no_proxy]}\n" + env_variables << "ENV NO_PROXY #{config[:no_proxy]}\n" + end + + env_variables + end + end + # rubocop:enable Metrics/ModuleLength, Style/Documentation + end + end +end diff --git a/test/spec/spec_helper.rb b/test/spec/spec_helper.rb index 5804e11..b9a91cb 100644 --- a/test/spec/spec_helper.rb +++ b/test/spec/spec_helper.rb @@ -22,7 +22,7 @@ # Check for coverage stuffs formatters = [] -if ENV['CODECOV_TOKEN'] || ENV['TRAVIS'] +if ENV['CODECOV_TOKEN'] || ENV['CI'] require 'codecov' formatters << SimpleCov::Formatter::Codecov end From 5c2ea0a8d1796b6c557df1d602a5df9b55cc3635 Mon Sep 17 00:00:00 2001 From: Dan Webb Date: Tue, 14 Nov 2023 14:31:21 +0000 Subject: [PATCH 3/3] Disable ServerSpec Signed-off-by: Dan Webb --- .github/workflows/ci.yml | 11 ++++++----- kitchen.yml | 2 +- .../capabilities_drop_spec.rb | 13 +++++++------ .../{serverspec => disabled}/default_spec.rb | 13 +++++++------ .../{serverspec => disabled}/spec_helper.rb | 14 +++++++------- 5 files changed, 28 insertions(+), 25 deletions(-) rename test/integration/capabilities/{serverspec => disabled}/capabilities_drop_spec.rb (68%) rename test/integration/default/{serverspec => disabled}/default_spec.rb (72%) rename test/integration/default/{serverspec => disabled}/spec_helper.rb (75%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1805a2e..37bf958 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,17 +75,16 @@ jobs: matrix: suite: - default - - no_build_context + - no-build-context - arm64 - amd64 - inspec os: - amazonlinux-2 - - ubuntu-18.04 - - ubuntu-20.04 + - ubuntu-1804 + - ubuntu-2004 - fedora-latest - centos-7 - - centos-8 - oraclelinux-7 - rockylinux-8 - debian-11 @@ -98,6 +97,8 @@ jobs: with: ruby-version: "3.1" bundler-cache: true + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - run: bundle exec kitchen test ${{ matrix.suite }}-${{ matrix.os }} @@ -111,7 +112,7 @@ jobs: matrix: suite: - capabilities - os: [debian-11, ubuntu-18.04, ubuntu-20.04] + os: [debian-11, ubuntu-1804, ubuntu-2004] steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 diff --git a/kitchen.yml b/kitchen.yml index dd1091b..94bf41e 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -42,7 +42,7 @@ suites: driver: build_context: false - name: capabilities - includes: [debian-10, ubuntu-18.04, ubuntu-20.04] + includes: [debian-11, ubuntu-18.04, ubuntu-20.04] driver: provision_command: - curl -L https://www.chef.io/chef/install.sh | bash diff --git a/test/integration/capabilities/serverspec/capabilities_drop_spec.rb b/test/integration/capabilities/disabled/capabilities_drop_spec.rb similarity index 68% rename from test/integration/capabilities/serverspec/capabilities_drop_spec.rb rename to test/integration/capabilities/disabled/capabilities_drop_spec.rb index 137b642..6d3e7b7 100644 --- a/test/integration/capabilities/serverspec/capabilities_drop_spec.rb +++ b/test/integration/capabilities/disabled/capabilities_drop_spec.rb @@ -14,10 +14,11 @@ # limitations under the License. # -require 'serverspec' -set :backend, :exec +# Disable now busser-serever is gone. +# require 'serverspec' +# set :backend, :exec -describe command('/sbin/ifconfig eth0 multicast') do - its(:exit_status) { is_expected.to_not eq 0 } - its(:stderr) { is_expected.to match /Operation not permitted/ } -end +# describe command('/sbin/ifconfig eth0 multicast') do +# its(:exit_status) { is_expected.to_not eq 0 } +# its(:stderr) { is_expected.to match /Operation not permitted/ } +# end diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/disabled/default_spec.rb similarity index 72% rename from test/integration/default/serverspec/default_spec.rb rename to test/integration/default/disabled/default_spec.rb index 5f3e3ec..3a18256 100644 --- a/test/integration/default/serverspec/default_spec.rb +++ b/test/integration/default/disabled/default_spec.rb @@ -14,10 +14,11 @@ # limitations under the License. # -require 'serverspec' -require 'spec_helper' +# Disable now busser-serever is gone. +# require 'serverspec' +# require 'spec_helper' -# Just make sure the image launched and is reachable. -describe command('true') do - its(:exit_status) { is_expected.to eq 0 } -end +# # Just make sure the image launched and is reachable. +# describe command('true') do +# its(:exit_status) { is_expected.to eq 0 } +# end diff --git a/test/integration/default/serverspec/spec_helper.rb b/test/integration/default/disabled/spec_helper.rb similarity index 75% rename from test/integration/default/serverspec/spec_helper.rb rename to test/integration/default/disabled/spec_helper.rb index 42086cc..c1ce986 100644 --- a/test/integration/default/serverspec/spec_helper.rb +++ b/test/integration/default/disabled/spec_helper.rb @@ -12,10 +12,10 @@ # limitations under the License. # -case RbConfig::CONFIG['host_os'] -when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - set :backend, :cmd - set :os, :family => 'windows' -else - set :backend, :exec -end +# case RbConfig::CONFIG['host_os'] +# when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ +# set :backend, :cmd +# set :os, :family => 'windows' +# else +# set :backend, :exec +# end