diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 452ebb34..75a4e3d6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,7 @@ version: 2 updates: -- package-ecosystem: bundler - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 + - package-ecosystem: bundler + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..ab007b87 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,92 @@ +--- +name: Lint & Unit + +"on": + pull_request: + push: + branches: + - main + +jobs: + yamllint: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Lint YAML + uses: koozz/yamllint-action@main + + chefstyle: + runs-on: ubuntu-latest + strategy: + matrix: + ruby: ["2.7"] + name: Chefstyle on Ruby ${{ matrix.ruby }} + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - uses: r7kamura/rubocop-problem-matchers-action@v1 + - run: bundle exec chefstyle + + unit: + name: Unit test on Ruby ${{ matrix.ruby }} + needs: [yamllint, chefstyle] + runs-on: ubuntu-latest + strategy: + matrix: + ruby: ["2.7", "3.0", "3.1"] + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - run: bundle exec rake spec + + integration-linux: + name: Integration test on Linux + runs-on: ubuntu-latest + needs: unit + strategy: + matrix: + ruby: ["2.7", "3.0", "3.1"] + fail-fast: false + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - run: bundle exec rake spec + + integration-windows: + name: Integration test on Windows + runs-on: windows-latest + needs: unit + strategy: + fail-fast: false + matrix: + suite: [default, context, capabilities, arm64, amd64, inspec] + os: + - amazonlinux-2 + - ubuntu-18.04 + - ubuntu-20.04 + - fedora-latest + - centos-7 + - centos-8 + - oraclelinux-7 + - rockylinux-8 + - debian-9 + - debian-10 + - opensuse-15 + - dockerfile + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.1" + bundler-cache: true + - run: bundle exec kitchen test ${{ matrix.suite }}-${{ matrix.os }} diff --git a/.kitchen.yml b/.kitchen.yml deleted file mode 100644 index ab46fa87..00000000 --- a/.kitchen.yml +++ /dev/null @@ -1,65 +0,0 @@ -<% # Make sure the local copy of the driver is loaded %> -<% lib = File.expand_path('../lib', __FILE__) %> -<% $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) %> ---- -driver: - name: docker - provision_command: curl -L https://www.chef.io/chef/install.sh | bash - -transport: - name: docker - -provisioner: - name: dummy - -platforms: -- name: amazonlinux-2 -- name: ubuntu-18.04 -- name: ubuntu-20.04 -- name: fedora-latest - driver: - provision_command: - - yum install libxcrypt-compat -y - - curl -L https://www.chef.io/chef/install.sh | bash -- name: centos-7 -- name: oraclelinux-7 -- name: rockylinux-8 -- name: debian-9 -- name: debian-10 -- name: opensuse-15 - driver: - image: opensuse/leap:15 -- name: dockerfile - driver: - username: dockerfile - password: dockerfile - dockerfile: test/Dockerfile - run_command: /sbin/init - -suites: -- name: default - excludes: [arch, debian-9] -- name: context - excludes: [arch, debian-9] - driver: - build_context: false -- name: capabilities - includes: [debian-10,ubuntu-18.04,ubuntu-20.04] - driver: - provision_command: - - curl -L https://www.chef.io/chef/install.sh | bash - - apt-get install -y net-tools - cap_drop: - - NET_ADMIN -- name: arm64 - excludes: [debian-9] - driver: - docker_platform: linux/arm64 -- name: amd64 - driver: - docker_platform: linux/amd64 -- name: inspec - driver: - provision_command: true - verifier: - name: inspec diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000..94ff29cc --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.1.1 diff --git a/.tailor b/.tailor deleted file mode 100644 index b7a10780..00000000 --- a/.tailor +++ /dev/null @@ -1,4 +0,0 @@ -Tailor.config do |config| - config.formatters "text" - config.file_set 'lib/**/*.rb' -end diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 065c8340..00000000 --- a/.travis.yml +++ /dev/null @@ -1,57 +0,0 @@ -matrix: - include: - - os: linux - rvm: 2.4.9 - dist: xenial - language: ruby - cache: bundler - script: - - bundle exec docker version - - bundle exec kitchen --version - - bundle exec rake spec - - bundle exec kitchen test -d always - - os: linux - rvm: 2.5.7 - dist: xenial - language: ruby - cache: bundler - script: - - bundle exec docker version - - bundle exec kitchen --version - - bundle exec rake spec - - bundle exec kitchen test -d always - - os: linux - rvm: 2.6.5 - dist: xenial - language: ruby - cache: bundler - script: - - bundle exec docker version - - bundle exec kitchen --version - - bundle exec rake spec - - bundle exec kitchen test -d always - - os: windows - language: bash - install: - - choco uninstall ruby - - choco install ruby --version=2.6.5.1 - - export PATH=$(echo "$PATH" | sed -e 's/:\/c\/tools\/ruby27\/bin//') - - export PATH=$PATH:/c/tools/ruby26/bin - - choco install mingw - - choco install msys2 - - ridk.cmd exec pacman -S --noconfirm --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-libxslt - script: - - if [[ $(tasklist | grep "gpg-agent") ]]; then taskkill -IM "gpg-agent.exe" -F; else echo "Process gpg-agent not found. Skipping."; fi - - powershell -ExecutionPolicy Bypass -NoLogo -File docker.ps1 - - export KITCHEN_YAML=.kitchen.windows.yml - - ruby -v - - gem install bundler - - bundle config build.nokogiri --use-system-libraries - - bundle install - - bundle exec docker version - - bundle exec kitchen --version - - bundle exec rake spec - - bundle exec kitchen test -d always - -services: -- docker diff --git a/.yamllint b/.yamllint new file mode 100644 index 00000000..1b5cea09 --- /dev/null +++ b/.yamllint @@ -0,0 +1,13 @@ +--- +extends: default +rules: + line-length: + max: 256 + level: warning + document-start: disable + braces: + forbid: false + min-spaces-inside: 0 + max-spaces-inside: 1 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 diff --git a/Gemfile b/Gemfile index fa75df15..ffadf56c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,17 @@ -source 'https://rubygems.org' +source "https://rubygems.org" gemspec + +group :integration do + gem "bundler" + gem "rake" + gem "countloc" + gem "rspec", "~> 3.4" + gem "rspec-its", "~> 1.3" + gem "fuubar", "~> 2.5" + gem "simplecov", "~> 0.22" + gem "codecov", "~> 0.0", ">= 0.2.0" + gem "kitchen-inspec", "~> 2.6" + gem "train", ">= 2.1", "< 4.0" # validate 4.x when it's released + gem "chefstyle" +end diff --git a/Rakefile b/Rakefile index d956b96c..41c2ae49 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,4 @@ require "bundler/gem_tasks" -require 'cane/rake_task' -require 'tailor/rake_task' - -desc "Run cane to check quality metrics" -Cane::RakeTask.new do |cane| - cane.canefile = './.cane' -end - -Tailor::RakeTask.new desc "Display LOC stats" task :stats do @@ -16,9 +7,9 @@ task :stats do end desc "Run all quality tasks" -task :quality => [:cane, :tailor, :stats] +task quality: %i{stats} -task :default => [:quality] +task default: [:quality] # begin # require 'kitchen/rake_tasks' @@ -28,15 +19,15 @@ task :default => [:quality] # end # Create the spec task. -require 'rspec/core/rake_task' +require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec, :tag) do |t, args| t.rspec_opts = [].tap do |a| - a << '--color' - a << "--format #{ENV['CI'] ? 'documentation' : 'Fuubar'}" - a << '--backtrace' if ENV['VERBOSE'] || ENV['DEBUG'] - a << "--seed #{ENV['SEED']}" if ENV['SEED'] + a << "--color" + a << "--format #{ENV["CI"] ? "documentation" : "Fuubar"}" + a << "--backtrace" if ENV["VERBOSE"] || ENV["DEBUG"] + a << "--seed #{ENV["SEED"]}" if ENV["SEED"] a << "--tag #{args[:tag]}" if args[:tag] a << "--default-path test" - a << '-I test/spec' - end.join(' ') + a << "-I test/spec" + end.join(" ") end diff --git a/kitchen-docker.gemspec b/kitchen-docker.gemspec index 08474a9b..6ab7638b 100644 --- a/kitchen-docker.gemspec +++ b/kitchen-docker.gemspec @@ -1,39 +1,21 @@ -lib = File.expand_path('../lib', __FILE__) +require "English" +lib = File.expand_path("lib", __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'kitchen/docker/docker_version' +require "kitchen/docker/docker_version" Gem::Specification.new do |spec| - spec.name = 'kitchen-docker' + spec.name = "kitchen-docker" spec.version = Kitchen::Docker::DOCKER_VERSION - spec.authors = ['Sean Porter'] - spec.email = ['portertech@gmail.com'] + spec.authors = ["Sean Porter"] + spec.email = ["portertech@gmail.com"] spec.description = %q{A Docker Driver for Test Kitchen} spec.summary = spec.description - spec.homepage = 'https://github.com/test-kitchen/kitchen-docker' - spec.license = 'Apache 2.0' + spec.homepage = "https://github.com/test-kitchen/kitchen-docker" + spec.license = "Apache 2.0" - spec.files = `git ls-files`.split($/) + spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) - spec.require_paths = ['lib'] + spec.require_paths = ["lib"] - spec.add_dependency 'test-kitchen', '>= 1.0.0' - - spec.add_development_dependency 'bundler' - spec.add_development_dependency 'rake' - - # Style checker gems. - spec.add_development_dependency 'cane' - spec.add_development_dependency 'tailor' - spec.add_development_dependency 'countloc' - - # Unit testing gems. - spec.add_development_dependency 'rspec', '~> 3.2' - spec.add_development_dependency 'rspec-its', '~> 1.2' - spec.add_development_dependency 'fuubar', '~> 2.0' - spec.add_development_dependency 'simplecov', '~> 0.9' - spec.add_development_dependency 'codecov', '~> 0.0', '>= 0.0.2' - - # Integration testing gems. - spec.add_development_dependency 'kitchen-inspec', '~> 2.0' - spec.add_development_dependency 'train', '>= 2.1', '< 4.0' # validate 4.x when it's released + spec.add_dependency "test-kitchen", ">= 1.0.0" end diff --git a/.kitchen.windows.yml b/kitchen.windows.yml similarity index 50% rename from .kitchen.windows.yml rename to kitchen.windows.yml index b58b915d..0da1a9b1 100644 --- a/.kitchen.windows.yml +++ b/kitchen.windows.yml @@ -1,10 +1,7 @@ -<% # Make sure the local copy of the driver is loaded %> -<% lib = File.expand_path('../lib', __FILE__) %> -<% $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) %> --- driver: name: docker - provision_command: + provision_command: - powershell -ExecutionPolicy Bypass -NoLogo -Command . { iwr -useb https://omnitruck.chef.io/install.ps1 } ^| iex; install - powershell -Command $path=$env:Path + ';c:\opscode\chef\embedded\bin'; Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\' -Name Path -Value $path @@ -16,18 +13,18 @@ provisioner: name: dummy platforms: -- name: windows - driver_config: - image: mcr.microsoft.com/windows/servercore:1809 - platform: windows + - name: windows + driver_config: + image: mcr.microsoft.com/windows/servercore:1809 + platform: windows suites: -- name: default -- name: context - driver: - build_context: false -- name: inspec - driver: - provision_command: echo 1 - verifier: - name: inspec + - name: default + - name: context + driver: + build_context: false + - name: inspec + driver: + provision_command: echo 1 + verifier: + name: inspec diff --git a/kitchen.yml b/kitchen.yml new file mode 100644 index 00000000..11749c75 --- /dev/null +++ b/kitchen.yml @@ -0,0 +1,63 @@ +--- +driver: + name: docker + provision_command: curl -L https://www.chef.io/chef/install.sh | bash + +transport: + name: docker + +provisioner: + name: dummy + +platforms: + - name: amazonlinux-2 + - name: ubuntu-18.04 + - name: ubuntu-20.04 + - name: fedora-latest + driver: + provision_command: + - yum install libxcrypt-compat -y + - curl -L https://www.chef.io/chef/install.sh | bash + - name: centos-7 + - name: centos-8 + - name: oraclelinux-7 + - name: rockylinux-8 + - name: debian-9 + - name: debian-10 + - name: opensuse-15 + driver: + image: opensuse/leap:15 + - name: dockerfile + driver: + username: dockerfile + password: dockerfile + dockerfile: test/Dockerfile + run_command: /sbin/init + +suites: + - name: default + excludes: [arch] + - name: context + excludes: [arch] + driver: + build_context: false + - name: capabilities + includes: [debian-9, debian-10, ubuntu-18.04, ubuntu-20.04] + driver: + provision_command: + - curl -L https://www.chef.io/chef/install.sh | bash + - apt-get install -y net-tools + cap_drop: + - NET_ADMIN + - name: arm64 + excludes: [debian-9] + driver: + docker_platform: linux/arm64 + - name: amd64 + driver: + docker_platform: linux/amd64 + - name: inspec + driver: + provision_command: true + verifier: + name: inspec diff --git a/lib/docker/version.rb b/lib/docker/version.rb index 70fd25ef..1b4cb1c2 100644 --- a/lib/docker/version.rb +++ b/lib/docker/version.rb @@ -12,13 +12,13 @@ # limitations under the License. begin - require 'docker' + require "docker" # Override API_VERSION constant in docker-api gem to use version 1.24 of the Docker API # This override is for the docker-api gem to communicate to the Docker engine on Windows module Docker - VERSION = '0.0.0' - API_VERSION = '1.24' + VERSION = "0.0.0".freeze + API_VERSION = "1.24".freeze end rescue LoadError => e logger.debug("[Docker] docker-api gem not found for InSpec verifier. #{e}") diff --git a/lib/kitchen/docker/container.rb b/lib/kitchen/docker/container.rb index 2595dfed..af78ba37 100644 --- a/lib/kitchen/docker/container.rb +++ b/lib/kitchen/docker/container.rb @@ -11,10 +11,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -require_relative 'helpers/cli_helper' -require_relative 'helpers/container_helper' -require_relative 'helpers/file_helper' -require_relative 'helpers/image_helper' +require_relative "helpers/cli_helper" +require_relative "helpers/container_helper" +require_relative "helpers/file_helper" +require_relative "helpers/image_helper" module Kitchen module Docker @@ -33,7 +33,7 @@ def create(state) info("Container ID #{state[:container_id]} already exists.") elsif !container_exists?(state) && state[:container_id] raise ActionFailed, "Container ID #{state[:container_id]} was found in the kitchen state data, "\ - 'but the container does not exist.' + "but the container does not exist." end state[:username] = @config[:username] @@ -49,7 +49,7 @@ def destroy(state) end def hostname(state) - hostname = 'localhost' + hostname = "localhost" if remote_socket? hostname = socket_uri.host diff --git a/lib/kitchen/docker/container/linux.rb b/lib/kitchen/docker/container/linux.rb index e2650b87..a95f1308 100644 --- a/lib/kitchen/docker/container/linux.rb +++ b/lib/kitchen/docker/container/linux.rb @@ -11,13 +11,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'base64' -require 'openssl' -require 'securerandom' -require 'shellwords' +require "base64" unless defined?(Base64) +require "openssl" unless defined?(OpenSSL) +require "securerandom" unless defined?(SecureRandom) +require "shellwords" unless defined?(Shellwords) -require_relative '../container' -require_relative '../helpers/dockerfile_helper' +require_relative "../container" +require_relative "../helpers/dockerfile_helper" module Kitchen module Docker @@ -34,7 +34,7 @@ def initialize(config) def create(state) super - debug('Creating Linux container') + debug("Creating Linux container") generate_keys state[:ssh_key] = @config[:private_key] @@ -58,7 +58,7 @@ def execute(command) debug("Uploading temp file #{temp_file} to #{remote_path} on container") upload(temp_file, remote_path) - debug('Deleting temp file from local filesystem') + debug("Deleting temp file from local filesystem") ::File.delete(temp_file) # Replace any environment variables used in the path and execute script file @@ -76,13 +76,13 @@ def generate_keys MUTEX_FOR_SSH_KEYS.synchronize do if !File.exist?(@config[:public_key]) || !File.exist?(@config[:private_key]) private_key = OpenSSL::PKey::RSA.new(2048) - blobbed_key = Base64.encode64(private_key.to_blob).gsub("\n", '') + blobbed_key = Base64.encode64(private_key.to_blob).gsub("\n", "") public_key = "ssh-rsa #{blobbed_key} kitchen_docker_key" - File.open(@config[:private_key], 'w') do |file| + File.open(@config[:private_key], "w") do |file| file.write(private_key) file.chmod(0600) end - File.open(@config[:public_key], 'w') do |file| + File.open(@config[:public_key], "w") do |file| file.write(public_key) file.chmod(0600) end @@ -91,7 +91,7 @@ def generate_keys end def parse_container_ssh_port(output) - _host, port = output.split(':') + _host, port = output.split(":") port.to_i rescue => e raise ActionFailed, "Could not parse Docker port output for container SSH port. #{e}" @@ -113,10 +113,10 @@ def dockerfile platform = dockerfile_platform username = @config[:username] public_key = IO.read(@config[:public_key]).strip - homedir = username == 'root' ? '/root' : "/home/#{username}" + homedir = username == "root" ? "/root" : "/home/#{username}" base = dockerfile_base_linux(username, homedir) - custom = '' + custom = "" Array(@config[:provision_command]).each do |cmd| custom << "RUN #{cmd}\n" end @@ -124,10 +124,10 @@ def dockerfile ssh_key = "RUN echo #{Shellwords.escape(public_key)} >> #{homedir}/.ssh/authorized_keys" # Empty string to ensure the file ends with a newline. - output = [from, dockerfile_proxy_config, platform, base, custom, ssh_key, ''].join("\n") - debug('--- Start Dockerfile ---') + output = [from, dockerfile_proxy_config, platform, base, custom, ssh_key, ""].join("\n") + debug("--- Start Dockerfile ---") debug(output.strip) - debug('--- End Dockerfile ---') + debug("--- End Dockerfile ---") output end end diff --git a/lib/kitchen/docker/container/windows.rb b/lib/kitchen/docker/container/windows.rb index 168dca70..f56c6539 100644 --- a/lib/kitchen/docker/container/windows.rb +++ b/lib/kitchen/docker/container/windows.rb @@ -11,9 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'securerandom' +require "securerandom" unless defined?(SecureRandom) -require_relative '../container' +require_relative "../container" module Kitchen module Docker @@ -26,7 +26,7 @@ def initialize(config) def create(state) super - debug('Creating Windows container') + debug("Creating Windows container") state[:username] = @config[:username] state[:image_id] = build_image(state, dockerfile) unless state[:image_id] state[:container_id] = run_container(state) unless state[:container_id] @@ -35,19 +35,19 @@ def create(state) def execute(command) # Create temp script file and upload files to container - debug('Executing command on Windows container') + debug("Executing command on Windows container") filename = "docker-#{::SecureRandom.uuid}.ps1" temp_file = ".\\.kitchen\\temp\\#{filename}" create_temp_file(temp_file, command) - remote_path = @config[:temp_dir].tr('/', '\\') + remote_path = @config[:temp_dir].tr("/", "\\") debug("Creating directory #{remote_path} on container") create_dir_on_container(@config, remote_path) debug("Uploading temp file #{temp_file} to #{remote_path} on container") upload(temp_file, remote_path) - debug('Deleting temp file from local filesystem') + debug("Deleting temp file from local filesystem") ::File.delete(temp_file) # Replace any environment variables used in the path and execute script file @@ -63,20 +63,20 @@ def execute(command) protected def dockerfile - raise ActionFailed, "Unknown platform '#{@config[:platform]}'" unless @config[:platform] == 'windows' + raise ActionFailed, "Unknown platform '#{@config[:platform]}'" unless @config[:platform] == "windows" return dockerfile_template if @config[:dockerfile] from = "FROM #{@config[:image]}" - custom = '' + custom = "" Array(@config[:provision_command]).each do |cmd| custom << "RUN #{cmd}\n" end - output = [from, dockerfile_proxy_config, custom, ''].join("\n") - debug('--- Start Dockerfile ---') + output = [from, dockerfile_proxy_config, custom, ""].join("\n") + debug("--- Start Dockerfile ---") debug(output.strip) - debug('--- End Dockerfile ---') + debug("--- End Dockerfile ---") output end end diff --git a/lib/kitchen/docker/docker_version.rb b/lib/kitchen/docker/docker_version.rb index a7b835d2..27c60a27 100644 --- a/lib/kitchen/docker/docker_version.rb +++ b/lib/kitchen/docker/docker_version.rb @@ -1,5 +1,5 @@ # -# Copyright (C) 2014, Sean Porter +# Copyright:: (C) 2014, Sean Porter # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ module Kitchen module Docker - # Version string for Docker Kitchen driver - DOCKER_VERSION = "2.15.0" + DOCKER_VERSION = "2.15.0".freeze end end diff --git a/lib/kitchen/docker/erb_context.rb b/lib/kitchen/docker/erb_context.rb index 7780e47c..a0e3a5d0 100644 --- a/lib/kitchen/docker/erb_context.rb +++ b/lib/kitchen/docker/erb_context.rb @@ -1,5 +1,5 @@ # -# Copyright (C) 2014, Sean Porter +# Copyright:: (C) 2014, Sean Porter # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'erb' +require "erb" unless defined?(Erb) module Kitchen module Docker class ERBContext - def initialize(config={}) + def initialize(config = {}) config.each do |key, value| - instance_variable_set('@' + key.to_s, value) + instance_variable_set("@" + key.to_s, value) end end diff --git a/lib/kitchen/docker/helpers/cli_helper.rb b/lib/kitchen/docker/helpers/cli_helper.rb index 1da52bb7..de8056de 100644 --- a/lib/kitchen/docker/helpers/cli_helper.rb +++ b/lib/kitchen/docker/helpers/cli_helper.rb @@ -1,172 +1,172 @@ -# -# 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 + 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 diff --git a/lib/kitchen/docker/helpers/container_helper.rb b/lib/kitchen/docker/helpers/container_helper.rb index 6ddb1ef8..6f8a3062 100644 --- a/lib/kitchen/docker/helpers/container_helper.rb +++ b/lib/kitchen/docker/helpers/container_helper.rb @@ -1,172 +1,174 @@ -# -# 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" unless defined?(Erb) +require "json" unless defined?(JSON) +require "shellwords" unless defined?(Shellwords) +require "tempfile" unless defined?(Tempfile) +require "uri" unless defined?(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[%r{\$(.*?)(/|$)}, 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 diff --git a/lib/kitchen/docker/helpers/dockerfile_helper.rb b/lib/kitchen/docker/helpers/dockerfile_helper.rb index 6e290415..8b19ff2a 100644 --- a/lib/kitchen/docker/helpers/dockerfile_helper.rb +++ b/lib/kitchen/docker/helpers/dockerfile_helper.rb @@ -11,38 +11,38 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'kitchen' -require 'kitchen/configurable' +require "kitchen" +require "kitchen/configurable" module Kitchen module Docker module Helpers - module DockerfileHelper + module DockerfileHelper include Configurable def dockerfile_platform case config[:platform] - when 'arch' + when "arch" arch_platform - when 'debian', 'ubuntu' + when "debian", "ubuntu" debian_platform - when 'fedora' + when "fedora" fedora_platform - when 'gentoo' + when "gentoo" gentoo_platform - when 'gentoo-paludis' + when "gentoo-paludis" gentoo_paludis_platform - when 'opensuse/tumbleweed', 'opensuse/leap', 'opensuse', 'sles' + when "opensuse/tumbleweed", "opensuse/leap", "opensuse", "sles" opensuse_platform - when 'rhel', 'centos', 'oraclelinux', 'amazonlinux' + when "rhel", "centos", "oraclelinux", "amazonlinux" rhel_platform - when 'centosstream' + when "centosstream" centosstream_platform - when 'almalinux' + when "almalinux" almalinux_platform - when 'rockylinux' + when "rockylinux" rockylinux_platform - when 'photon' + when "photon" photonos_platform else raise ActionFailed, "Unknown platform '#{config[:platform]}'" @@ -183,4 +183,4 @@ def dockerfile_base_linux(username, homedir) end end end -end +end \ No newline at end of file diff --git a/lib/kitchen/docker/helpers/file_helper.rb b/lib/kitchen/docker/helpers/file_helper.rb index 6dc8291d..d07a011e 100644 --- a/lib/kitchen/docker/helpers/file_helper.rb +++ b/lib/kitchen/docker/helpers/file_helper.rb @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'fileutils' +require "fileutils" unless defined?(FileUtils) module Kitchen module Docker @@ -19,14 +19,14 @@ module Helpers module FileHelper def create_temp_file(file, contents) debug("[Docker] Creating temp file #{file}") - debug('[Docker] --- Start Temp File Contents ---') + debug("[Docker] --- Start Temp File Contents ---") debug(contents) - debug('[Docker] --- End Temp File Contents ---') + debug("[Docker] --- End Temp File Contents ---") begin path = ::File.dirname(file) ::FileUtils.mkdir_p(path) unless ::Dir.exist?(path) - file = ::File.open(file, 'w') + file = ::File.open(file, "w") file.write(contents) rescue IOError => e raise "Failed to write temp file. Error Details: #{e}" diff --git a/lib/kitchen/docker/helpers/image_helper.rb b/lib/kitchen/docker/helpers/image_helper.rb index 3c0c7bee..3baab8fd 100644 --- a/lib/kitchen/docker/helpers/image_helper.rb +++ b/lib/kitchen/docker/helpers/image_helper.rb @@ -11,11 +11,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'kitchen' -require 'kitchen/configurable' -require 'pathname' -require_relative 'cli_helper' -require_relative 'container_helper' +require "kitchen" +require "kitchen/configurable" +require "pathname" unless defined?(Pathname) +require_relative "cli_helper" +require_relative "container_helper" module Kitchen module Docker @@ -28,7 +28,7 @@ module ImageHelper def parse_image_id(output) output.split("\n").reverse_each do |line| if line =~ /writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i - img_id = line[/writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i,1] + img_id = line[/writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i, 1] return img_id end if line =~ /image id|build successful|successfully built/i @@ -36,7 +36,7 @@ def parse_image_id(output) return img_id end end - raise ActionFailed, 'Could not parse Docker build output for image ID' + raise ActionFailed, "Could not parse Docker build output for image ID" end def remove_image(state) @@ -44,22 +44,22 @@ def remove_image(state) docker_command("rmi #{image_id}") end - def build_image(state, dockerfile) - cmd = 'build' - cmd << ' --no-cache' unless config[:use_cache] + def build_image(_state, dockerfile) + cmd = "build" + cmd << " --no-cache" unless config[:use_cache] cmd << " --platform=#{config[:docker_platform]}" if config[:docker_platform] extra_build_options = config_to_options(config[:build_options]) cmd << " #{extra_build_options}" unless extra_build_options.empty? dockerfile_contents = dockerfile - file = Tempfile.new('Dockerfile-kitchen', Pathname.pwd + config[:build_tempdir]) + file = Tempfile.new("Dockerfile-kitchen", Pathname.pwd + config[:build_tempdir]) cmd << " -f #{Shellwords.escape(dockerfile_path(file))}" if config[:build_context] - build_context = config[:build_context] ? '.' : '-' + build_context = config[:build_context] ? "." : "-" output = begin file.write(dockerfile) file.close docker_command("#{cmd} #{build_context}", input: dockerfile_contents, - environment: { BUILDKIT_PROGRESS: 'plain' }) + environment: { BUILDKIT_PROGRESS: "plain" }) ensure file.close unless file.closed? file.unlink @@ -69,7 +69,9 @@ def build_image(state, dockerfile) end def image_exists?(state) - state[:image_id] && !!docker_command("inspect --type=image #{state[:image_id]}") rescue false + state[:image_id] && !!docker_command("inspect --type=image #{state[:image_id]}") + rescue + false end end end diff --git a/lib/kitchen/docker/helpers/inspec_helper.rb b/lib/kitchen/docker/helpers/inspec_helper.rb index 5f0d3256..63333912 100644 --- a/lib/kitchen/docker/helpers/inspec_helper.rb +++ b/lib/kitchen/docker/helpers/inspec_helper.rb @@ -1,40 +1,40 @@ -# -# 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. - -# This helper should be removed when the kitchen-inspec gem has been updated to include these runner options -begin - require 'kitchen/verifier/inspec' - - # Add runner options for Docker transport for kitchen-inspec gem - module Kitchen - module Docker - module Helpers - module InspecHelper - Kitchen::Verifier::Inspec.class_eval do - def runner_options_for_docker(config_data) - opts = { - 'backend' => 'docker', - 'logger' => logger, - 'host' => config_data[:container_id], - } - logger.debug "Connect to Container: #{opts['host']}" - opts - end - end - end - end - end - end -rescue LoadError => e - logger.debug("[Docker] kitchen-inspec gem not found for InSpec verifier. #{e}") -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. + +# This helper should be removed when the kitchen-inspec gem has been updated to include these runner options +begin + require "kitchen/verifier/inspec" + + # Add runner options for Docker transport for kitchen-inspec gem + module Kitchen + module Docker + module Helpers + module InspecHelper + Kitchen::Verifier::Inspec.class_eval do + def runner_options_for_docker(config_data) + opts = { + "backend" => "docker", + "logger" => logger, + "host" => config_data[:container_id], + } + logger.debug "Connect to Container: #{opts["host"]}" + opts + end + end + end + end + end + end +rescue LoadError => e + logger.debug("[Docker] kitchen-inspec gem not found for InSpec verifier. #{e}") +end diff --git a/lib/kitchen/driver/docker.rb b/lib/kitchen/driver/docker.rb index 3606ee00..a01ccf40 100644 --- a/lib/kitchen/driver/docker.rb +++ b/lib/kitchen/driver/docker.rb @@ -1,5 +1,5 @@ # -# Copyright (C) 2014, Sean Porter +# Copyright:: (C) 2014, Sean Porter # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,17 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'kitchen' -require 'json' -require 'securerandom' -require 'net/ssh' +require "kitchen" +require "json" unless defined?(JSON) +require "securerandom" unless defined?(SecureRandom) +require "net/ssh" unless defined?(Net::SSH) -require 'kitchen/driver/base' +require "kitchen/driver/base" -require_relative '../docker/container/linux' -require_relative '../docker/container/windows' -require_relative '../docker/helpers/cli_helper' -require_relative '../docker/helpers/container_helper' +require_relative "../docker/container/linux" +require_relative "../docker/container/windows" +require_relative "../docker/helpers/cli_helper" +require_relative "../docker/helpers/container_helper" module Kitchen module Driver @@ -35,7 +35,7 @@ class Docker < Kitchen::Driver::Base include Kitchen::Docker::Helpers::ContainerHelper include ShellOut - default_config :binary, 'docker' + default_config :binary, "docker" default_config :build_options, nil default_config :build_tempdir, Dir.pwd default_config :cap_add, nil @@ -44,9 +44,9 @@ class Docker < Kitchen::Driver::Base default_config :env_variables, nil default_config :isolation, nil default_config :interactive, false - default_config :private_key, File.join(Dir.pwd, '.kitchen', 'docker_id_rsa') + default_config :private_key, File.join(Dir.pwd, ".kitchen", "docker_id_rsa") default_config :privileged, false - default_config :public_key, File.join(Dir.pwd, '.kitchen', 'docker_id_rsa.pub') + default_config :public_key, File.join(Dir.pwd, ".kitchen", "docker_id_rsa.pub") default_config :publish_all, false default_config :remove_images, false default_config :run_options, nil @@ -66,43 +66,39 @@ class Docker < Kitchen::Driver::Base !driver.remote_socket? end - default_config :image do |driver| - driver.default_image - end + default_config :image, &:default_image default_config :instance_name do |driver| # Borrowed from kitchen-rackspace [ - driver.instance.name.gsub(/\W/, ''), - (Etc.getlogin || 'nologin').gsub(/\W/, ''), - Socket.gethostname.gsub(/\W/, '')[0..20], - Array.new(8) { rand(36).to_s(36) }.join - ].join('-').downcase + driver.instance.name.gsub(/\W/, ""), + (Etc.getlogin || "nologin").gsub(/\W/, ""), + Socket.gethostname.gsub(/\W/, "")[0..20], + Array.new(8) { rand(36).to_s(36) }.join, + ].join("-").downcase end - default_config :platform do |driver| - driver.default_platform - end + default_config :platform, &:default_platform default_config :run_command do |driver| if driver.windows_os? # Launch arbitrary process to keep the Windows container alive # If running in interactive mode, launch powershell.exe instead if driver[:interactive] - 'powershell.exe' + "powershell.exe" else - 'ping -t localhost' + "ping -t localhost" end else - '/usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no -o PasswordAuthentication=yes '\ - '-o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid' + "/usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no -o PasswordAuthentication=yes "\ + "-o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid" end end default_config :socket do |driver| - socket = 'unix:///var/run/docker.sock' - socket = 'npipe:////./pipe/docker_engine' if driver.windows_os? - ENV['DOCKER_HOST'] || socket + socket = "unix:///var/run/docker.sock" + socket = "npipe:////./pipe/docker_engine" if driver.windows_os? + ENV["DOCKER_HOST"] || socket end default_config :username do |driver| @@ -111,14 +107,14 @@ class Docker < Kitchen::Driver::Base if driver.windows_os? nil else - 'kitchen' + "kitchen" end end def verify_dependencies run_command("#{config[:binary]} >> #{dev_null} 2>&1", quiet: true, use_sudo: config[:use_sudo]) rescue - raise UserError, 'You must first install the Docker CLI tool https://www.docker.com/get-started' + raise UserError, "You must first install the Docker CLI tool https://www.docker.com/get-started" end def create(state) @@ -133,22 +129,20 @@ def destroy(state) def wait_for_transport(state) if config[:wait_for_transport] - instance.transport.connection(state) do |conn| - conn.wait_until_ready - end + instance.transport.connection(state, &:wait_until_ready) end end def default_image - platform, release = instance.platform.name.split('-') - if platform == 'centos' && release - release = 'centos' + release.split('.').first + platform, release = instance.platform.name.split("-") + if platform == "centos" && release + release = "centos" + release.split(".").first end - release ? [platform, release].join(':') : platform + release ? [platform, release].join(":") : platform end def default_platform - instance.platform.name.split('-').first + instance.platform.name.split("-").first end protected diff --git a/lib/kitchen/transport/docker.rb b/lib/kitchen/transport/docker.rb index de31807e..10b6e256 100644 --- a/lib/kitchen/transport/docker.rb +++ b/lib/kitchen/transport/docker.rb @@ -11,15 +11,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'kitchen' +require "kitchen" -require_relative '../docker/container/linux' -require_relative '../docker/container/windows' +require_relative "../docker/container/linux" +require_relative "../docker/container/windows" -require_relative '../docker/helpers/inspec_helper' +require_relative "../docker/helpers/inspec_helper" -require_relative '../../docker/version.rb' -require_relative '../../train/docker.rb' +require_relative "../../docker/version" +require_relative "../../train/docker" module Kitchen module Transport @@ -29,7 +29,7 @@ class DockerFailed < TransportFailed; end kitchen_transport_api_version 1 plugin_version Kitchen::VERSION - default_config :binary, 'docker' + default_config :binary, "docker" default_config :env_variables, nil default_config :interactive, false default_config :privileged, false @@ -42,16 +42,16 @@ class DockerFailed < TransportFailed; end default_config :working_dir, nil default_config :socket do |transport| - socket = 'unix:///var/run/docker.sock' - socket = 'npipe:////./pipe/docker_engine' if transport.windows_os? - ENV['DOCKER_HOST'] || socket + socket = "unix:///var/run/docker.sock" + socket = "npipe:////./pipe/docker_engine" if transport.windows_os? + ENV["DOCKER_HOST"] || socket end default_config :temp_dir do |transport| if transport.windows_os? - '$env:TEMP' + "$env:TEMP" else - '/tmp' + "/tmp" end end @@ -61,7 +61,7 @@ class DockerFailed < TransportFailed; end if transport.windows_os? nil else - 'kitchen' + "kitchen" end end @@ -73,7 +73,7 @@ def connection(state, &block) # This allows Windows systems to use the TCP socket for the InSpec verifier # See the lib/docker.rb file here: https://github.com/swipely/docker-api/blob/master/lib/docker.rb # default_socket_url is set to a Unix socket and env_url requires an environment variable to be set - ENV['DOCKER_HOST'] = options[:socket] if !options[:socket].nil? && ENV['DOCKER_HOST'].nil? + ENV["DOCKER_HOST"] = options[:socket] if !options[:socket].nil? && ENV["DOCKER_HOST"].nil? Kitchen::Transport::Docker::Connection.new(options, &block) end @@ -98,7 +98,7 @@ def upload(locals, remote) end def container - @container ||= if @options[:platform].include?('windows') + @container ||= if @options[:platform].include?("windows") Kitchen::Docker::Container::Windows.new(@options) else Kitchen::Docker::Container::Linux.new(@options) diff --git a/lib/train/docker.rb b/lib/train/docker.rb index 024caae9..d311418e 100644 --- a/lib/train/docker.rb +++ b/lib/train/docker.rb @@ -14,12 +14,12 @@ # Monkey patched Docker train transport to support running the InSpec verifier on Windows begin # Requires train gem with a minimum version of 2.1.0 - require 'train' + require "train" module Train::Transports # Patched train transport with Windows support for InSpec verifier class Docker < Train.plugin(1) - name 'docker' + name "docker" include_options Train::Extras::CommandWrapper option :host, required: true @@ -71,10 +71,9 @@ def initialize(conf) super(conf) @id = options[:host] @container = ::Docker::Container.get(@id) || - fail("Can't find Docker container #{@id}") + raise("Can't find Docker container #{@id}") @cmd_wrapper = nil @cmd_wrapper = CommandWrapper.load(self, @options) - self end def uri @@ -101,10 +100,11 @@ def file_via_connection(path) def platform_specific_cmd(cmd) return cmd if @container.info.nil? - if @container.info['Platform'] == 'windows' - return ['cmd.exe', '/c', cmd] + + if @container.info["Platform"] == "windows" + ["cmd.exe", "/c", cmd] else - return ['/bin/sh', '-c', cmd] + ["/bin/sh", "-c", cmd] end end diff --git a/test/integration/capabilities/serverspec/capabilities_drop_spec.rb b/test/integration/capabilities/serverspec/capabilities_drop_spec.rb index 137b642f..9ed9f416 100644 --- a/test/integration/capabilities/serverspec/capabilities_drop_spec.rb +++ b/test/integration/capabilities/serverspec/capabilities_drop_spec.rb @@ -1,5 +1,5 @@ # -# Copyright 2016, Noah Kantrowitz +# Copyright:: 2016, Noah Kantrowitz # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,10 +14,10 @@ # limitations under the License. # -require 'serverspec' +require "serverspec" set :backend, :exec -describe command('/sbin/ifconfig eth0 multicast') do +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/ } + its(:stderr) { is_expected.to match(/Operation not permitted/) } end diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/serverspec/default_spec.rb index 5f3e3ec5..c72ab77d 100644 --- a/test/integration/default/serverspec/default_spec.rb +++ b/test/integration/default/serverspec/default_spec.rb @@ -1,5 +1,5 @@ # -# Copyright 2016, Noah Kantrowitz +# Copyright:: 2016, Noah Kantrowitz # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,10 +14,10 @@ # limitations under the License. # -require 'serverspec' -require 'spec_helper' +require "serverspec" +require "spec_helper" # Just make sure the image launched and is reachable. -describe command('true') do +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/serverspec/spec_helper.rb index 42086cc4..4d232bca 100644 --- a/test/integration/default/serverspec/spec_helper.rb +++ b/test/integration/default/serverspec/spec_helper.rb @@ -12,10 +12,10 @@ # limitations under the License. # -case RbConfig::CONFIG['host_os'] +case RbConfig::CONFIG["host_os"] when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ set :backend, :cmd - set :os, :family => 'windows' + set :os, family: "windows" else set :backend, :exec end diff --git a/test/integration/inspec/inspec_spec.rb b/test/integration/inspec/inspec_spec.rb index 0a128d70..26ebbb9a 100644 --- a/test/integration/inspec/inspec_spec.rb +++ b/test/integration/inspec/inspec_spec.rb @@ -1,5 +1,5 @@ # -# Copyright 2016, Noah Kantrowitz +# Copyright:: 2016, Noah Kantrowitz # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,12 +15,12 @@ # # Just make sure the image launched and is reachable. -if os[:family] == 'windows' - describe command('echo 1') do +if os[:family] == "windows" + describe command("echo 1") do its(:exit_status) { is_expected.to eq 0 } end else - describe command('true') do + describe command("true") do its(:exit_status) { is_expected.to eq 0 } end end diff --git a/test/spec/docker_spec.rb b/test/spec/docker_spec.rb index f04e8f6b..14c6fdec 100644 --- a/test/spec/docker_spec.rb +++ b/test/spec/docker_spec.rb @@ -1,5 +1,5 @@ # -# Copyright 2016, Noah Kantrowitz +# Copyright:: 2016, Noah Kantrowitz # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,50 +14,50 @@ # limitations under the License. # -require 'spec_helper' +require "spec_helper" describe Kitchen::Driver::Docker do - describe '#config_to_options' do - let(:config) { } + describe "#config_to_options" do + let(:config) {} subject { described_class.new.send(:config_to_options, config) } - context 'with nil' do + context "with nil" do let(:config) { nil } - it { is_expected.to eq '' } + it { is_expected.to eq "" } end # /context with nil - context 'with a string' do - let(:config) { '--foo' } - it { is_expected.to eq '--foo' } + context "with a string" do + let(:config) { "--foo" } + it { is_expected.to eq "--foo" } end # /context with a string - context 'with a string with spaces' do - let(:config) { '--foo bar' } - it { is_expected.to eq '--foo bar' } + context "with a string with spaces" do + let(:config) { "--foo bar" } + it { is_expected.to eq "--foo bar" } end # /context with a string with spaces - context 'with an array of strings' do + context "with an array of strings" do let(:config) { %w{--foo --bar} } - it { is_expected.to eq '--foo --bar' } + it { is_expected.to eq "--foo --bar" } end # /context with an array of strings - context 'with an array of hashes' do - let(:config) { [{foo: 'bar'}, {other: 'baz'}] } - it { is_expected.to eq '--foo=bar --other=baz' } + context "with an array of hashes" do + let(:config) { [{ foo: "bar" }, { other: "baz" }] } + it { is_expected.to eq "--foo=bar --other=baz" } end # /context with an array of hashes - context 'with a hash of strings' do - let(:config) { {foo: 'bar', other: 'baz'} } - it { is_expected.to eq '--foo=bar --other=baz' } + context "with a hash of strings" do + let(:config) { { foo: "bar", other: "baz" } } + it { is_expected.to eq "--foo=bar --other=baz" } end # /context with a hash of strings - context 'with a hash of arrays' do - let(:config) { {foo: %w{bar baz}} } - it { is_expected.to eq '--foo=bar --foo=baz' } + context "with a hash of arrays" do + let(:config) { { foo: %w{bar baz} } } + it { is_expected.to eq "--foo=bar --foo=baz" } end # /context with a hash of arrays - context 'with a hash of strings with spaces' do - let(:config) { {foo: 'bar two', other: 'baz'} } + context "with a hash of strings with spaces" do + let(:config) { { foo: "bar two", other: "baz" } } it { is_expected.to eq '--foo=bar\\ two --other=baz' } end # /context with a hash of strings with spaces end # /describe #config_to_options diff --git a/test/spec/spec_helper.rb b/test/spec/spec_helper.rb index 5804e11a..6b4ea973 100644 --- a/test/spec/spec_helper.rb +++ b/test/spec/spec_helper.rb @@ -1,5 +1,5 @@ # -# Copyright 2016, Noah Kantrowitz +# Copyright:: 2016, Noah Kantrowitz # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,16 +14,16 @@ # limitations under the License. # -require 'rake' -require 'rspec' -require 'rspec/its' -require 'simplecov' +require "rake" +require "rspec" +require "rspec/its" +require "simplecov" # Check for coverage stuffs formatters = [] -if ENV['CODECOV_TOKEN'] || ENV['TRAVIS'] - require 'codecov' +if ENV["CODECOV_TOKEN"] || ENV["TRAVIS"] + require "codecov" formatters << SimpleCov::Formatter::Codecov end @@ -33,13 +33,13 @@ SimpleCov.start do # Don't get coverage on the test cases themselves. - add_filter '/spec/' - add_filter '/test/' + add_filter "/spec/" + add_filter "/test/" # Codecov doesn't automatically ignore vendored files. - add_filter '/vendor/' + add_filter "/vendor/" end -require 'kitchen/driver/docker' +require "kitchen/driver/docker" RSpec.configure do |config| # Basic configuraiton @@ -50,5 +50,5 @@ # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 - config.order = 'random' + config.order = "random" end