Module: Kitchen::Docker::Helpers::CliHelper
- Includes:
- Configurable, Logging, ShellOut
- Included in:
- Container, ContainerHelper, ImageHelper, Kitchen::Driver::Docker
- Defined in:
- lib/kitchen/docker/helpers/cli_helper.rb
Overview
rubocop:disable Metrics/ModuleLength Builds and runs docker CLI command lines.
Instance Method Summary collapse
-
#build_copy_command(local_file, remote_file, opts = {}) ⇒ String
Builds a
docker cpcommand line. -
#build_env_variable_args(vars) ⇒ String
Turns a hash of environment variables into
-eflags. -
#build_exec_command(state, command) ⇒ String
Builds a
docker execcommand line from the configuration. -
#build_powershell_command(args) ⇒ String
Wraps PowerShell code so it can be run through
docker exec. -
#build_run_command(image_id, transport_port = nil) ⇒ String
Builds the
docker runcommand line from the configuration. -
#config_to_options(config) ⇒ String
Convert the config input for
:build_optionsor:run_optionsin to a command line string for use with Docker. -
#dev_null ⇒ String
The platform's null device,
NULon Windows and/dev/nulleverywhere else. -
#docker_command(cmd, options = {}) ⇒ String
Runs a docker CLI command with the configured connection flags.
-
#docker_shell_opts(options = {}) ⇒ Hash
Normalizes shell-out options for a docker command.
-
#docker_sudo_opts(options = {}) ⇒ Hash
Adds the sudo options #run_command reads, when
use_sudois set. -
#run_command(cmd, options = {}) ⇒ String
Runs a shell command, returning stderr as well as stdout.
-
#shell_escape(value) ⇒ String
Escapes a configured value for the shell.
Instance Method Details
#build_copy_command(local_file, remote_file, opts = {}) ⇒ String
Builds a docker cp command line.
206 207 208 209 210 211 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 206 def build_copy_command(local_file, remote_file, opts = {}) cmd = "cp" cmd << " -a" if opts[:archive] cmd << " #{shell_escape(local_file)} #{shell_escape(remote_file)}" cmd end |
#build_env_variable_args(vars) ⇒ String
Turns a hash of environment variables into -e flags.
229 230 231 232 233 234 235 236 237 238 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 229 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 #{shell_escape("#{k.to_s.strip}=#{v.to_s.strip}")}" end args end |
#build_exec_command(state, command) ⇒ String
Builds a docker exec command line from the configuration.
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 183 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 #{shell_escape(config[:username])}" if config[:username] cmd << " -w #{shell_escape(config[:working_dir])}" if config[:working_dir] cmd << " #{shell_escape(state[:container_id])}" # command is a command line, not a single value, so it stays raw. cmd << " #{command}" logger.debug("build_exec_command: #{cmd}") cmd end |
#build_powershell_command(args) ⇒ String
Wraps PowerShell code so it can be run through docker exec.
217 218 219 220 221 222 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 217 def build_powershell_command(args) cmd = "powershell -ExecutionPolicy Bypass -NoLogo " cmd << args logger.debug("build_powershell_command: #{cmd}") cmd end |
#build_run_command(image_id, transport_port = nil) ⇒ String
Builds the docker run command line from the configuration.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 138 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 #{shell_escape(port)}" } Array(config[:dns]).each { |dns| cmd << " --dns #{shell_escape(dns)}" } Array(config[:add_host]).each { |host, ip| cmd << " --add-host=#{shell_escape("#{host}:#{ip}")}" } Array(config[:volume]).each { |volume| cmd << " -v #{shell_escape(volume)}" } Array(config[:volumes_from]).each { |container| cmd << " --volumes-from #{shell_escape(container)}" } Array(config[:links]).each { |link| cmd << " --link #{shell_escape(link)}" } Array(config[:devices]).each { |device| cmd << " --device #{shell_escape(device)}" } Array(config[:mount]).each { |mount| cmd << " --mount #{shell_escape(mount)}" } Array(config[:tmpfs]).each { |tmpfs| cmd << " --tmpfs #{shell_escape(tmpfs)}" } cmd << " --name #{shell_escape(config[:instance_name])}" if config[:instance_name] cmd << " -P" if config[:publish_all] cmd << " -h #{shell_escape(config[:hostname])}" if config[:hostname] cmd << " -m #{shell_escape(config[:memory])}" if config[:memory] cmd << " -c #{shell_escape(config[:cpu])}" if config[:cpu] cmd << " --gpus #{shell_escape(config[:gpus])}" if config[:gpus] cmd << " -e http_proxy=#{shell_escape(config[:http_proxy])}" if config[:http_proxy] cmd << " -e https_proxy=#{shell_escape(config[:https_proxy])}" if config[:https_proxy] cmd << " --privileged" if config[:privileged] cmd << " --isolation #{shell_escape(config[:isolation])}" if config[:isolation] Array(config[:cap_add]).each { |cap| cmd << " --cap-add=#{shell_escape(cap)}" } if config[:cap_add] Array(config[:cap_drop]).each { |cap| cmd << " --cap-drop=#{shell_escape(cap)}" } if config[:cap_drop] Array(config[:security_opt]).each { |opt| cmd << " --security-opt=#{shell_escape(opt)}" } if config[:security_opt] cmd << " --platform=#{shell_escape(config[:docker_platform])}" if config[:docker_platform] = (config[:run_options]) cmd << " #{}" unless .empty? # run_command is a command line, not a single value, so it stays raw. cmd << " #{shell_escape(image_id)} #{config[:run_command]}" logger.debug("build_run_command: #{cmd}") cmd end |
#config_to_options(config) ⇒ String
Convert the config input for :build_options or :run_options in to a
command line string for use with Docker.
rubocop:disable Metrics/CyclomaticComplexity
272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 272 def (config) case config when nil "" when String config when Array config.map { |c| (c) }.join(" ") when Hash config.map { |k, v| Array(v).map { |c| "--#{k}=#{Shellwords.escape(c)}" }.join(" ") }.join(" ") end end |
#dev_null ⇒ String
Returns the platform's null device, NUL on Windows and
/dev/null everywhere else.
242 243 244 245 246 247 248 249 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 242 def dev_null case RbConfig::CONFIG["host_os"] when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ "NUL" else "/dev/null" end end |
#docker_command(cmd, options = {}) ⇒ String
Runs a docker CLI command with the configured connection flags.
59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 59 def docker_command(cmd, = {}) docker = config[:binary].dup docker << " -H #{shell_escape(config[:socket])}" if config[:socket] docker << " --tls" if config[:tls] docker << " --tlsverify" if config[:tls_verify] docker << " --tlscacert=#{shell_escape(config[:tls_cacert])}" if config[:tls_cacert] docker << " --tlscert=#{shell_escape(config[:tls_cert])}" if config[:tls_cert] docker << " --tlskey=#{shell_escape(config[:tls_key])}" if config[:tls_key] = docker_sudo_opts() logger.debug("docker_command: #{docker} #{cmd} shell_opts: #{docker_shell_opts()}") run_command("#{docker} #{cmd}", docker_shell_opts()) end |
#docker_shell_opts(options = {}) ⇒ Hash
Normalizes shell-out options for a docker command.
Translates :suppress_output into silencing the live stream, and removes
it, since Mixlib::ShellOut would reject the unknown key.
258 259 260 261 262 263 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 258 def docker_shell_opts( = {}) [:live_stream] = nil if [:suppress_output] .delete(:suppress_output) end |
#docker_sudo_opts(options = {}) ⇒ Hash
Adds the sudo options #run_command reads, when use_sudo is set.
Sudo is a property of the call rather than of the configuration as far
as the shell-out layer is concerned: it reads :use_sudo from the
options hash it is handed and knows nothing about config. So a
docker command only runs through sudo if these are passed to it.
Without this, use_sudo reached exactly one command -- the
docker probe in verify_dependencies -- and every build, run,
exec, cp, and rm still ran as the invoking user. On a host where the
daemon socket needs root, that made the documented answer to
"permission denied while trying to connect to the Docker daemon
socket" do nothing at all.
A copy is returned rather than the hash being edited in place, so a caller that reuses its options hash does not accumulate sudo.
92 93 94 95 96 97 98 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 92 def docker_sudo_opts( = {}) return unless config[:use_sudo] = .merge(use_sudo: true) [:sudo_command] = config[:sudo_command] if config[:sudo_command] end |
#run_command(cmd, options = {}) ⇒ String
Runs a shell command, returning stderr as well as stdout.
Test Kitchen's own run_command discards stderr, but docker writes build
progress and image ids there, so this reimplements it to keep both.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 111 def run_command(cmd, = {}) if .fetch(:use_sudo, false) cmd = "#{.fetch(:sudo_command, "sudo -E")} #{cmd}" end subject = "[#{.fetch(:log_subject, "local")} command]" debug("#{subject} BEGIN (#{cmd})") sh = Mixlib::ShellOut.new(cmd, shell_opts()) 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. rescue Exception => error # rubocop:disable Lint/RescueException error.extend(Kitchen::Error) raise end |
#shell_escape(value) ⇒ String
Escapes a configured value for the shell.
Docker command lines are assembled as strings and handed to a shell, so a value that is a single datum -- a path, a name, a port mapping -- has to be escaped, or a space inside it is read as an argument separator and the rest of the value is taken as the image name.
Values that are deliberately shell fragments are not escaped:
run_command, run_options, build_options, and the command handed to
docker exec are all documented as accepting flags and arguments.
Escaping is a no-op for ordinary values -- db:db and 8.8.8.8 come
back unchanged -- so this only alters command lines that were already
broken.
48 49 50 |
# File 'lib/kitchen/docker/helpers/cli_helper.rb', line 48 def shell_escape(value) Shellwords.escape(value.to_s) end |