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