lib/kitchen/docker/helpers/cli_helper.rb in kitchen-docker-2.15.0 vs lib/kitchen/docker/helpers/cli_helper.rb in kitchen-docker-3.0.0

- old
+ new

@@ -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