bolt 3.0.1 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +13 -11
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +24 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
  9. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  10. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  11. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
  12. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  13. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  14. data/lib/bolt/analytics.rb +4 -8
  15. data/lib/bolt/apply_result.rb +1 -1
  16. data/lib/bolt/bolt_option_parser.rb +6 -3
  17. data/lib/bolt/cli.rb +123 -37
  18. data/lib/bolt/config.rb +15 -7
  19. data/lib/bolt/config/options.rb +62 -12
  20. data/lib/bolt/config/transport/lxd.rb +23 -0
  21. data/lib/bolt/config/transport/options.rb +8 -1
  22. data/lib/bolt/config/transport/podman.rb +33 -0
  23. data/lib/bolt/container_result.rb +105 -0
  24. data/lib/bolt/error.rb +15 -0
  25. data/lib/bolt/executor.rb +37 -18
  26. data/lib/bolt/inventory/options.rb +9 -0
  27. data/lib/bolt/inventory/target.rb +16 -0
  28. data/lib/bolt/logger.rb +8 -0
  29. data/lib/bolt/module_installer.rb +2 -2
  30. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  31. data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
  32. data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
  33. data/lib/bolt/node/output.rb +14 -4
  34. data/lib/bolt/outputter/human.rb +259 -90
  35. data/lib/bolt/outputter/json.rb +3 -1
  36. data/lib/bolt/outputter/logger.rb +17 -0
  37. data/lib/bolt/pal.rb +25 -4
  38. data/lib/bolt/pal/yaml_plan.rb +1 -2
  39. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -141
  40. data/lib/bolt/pal/yaml_plan/step.rb +91 -31
  41. data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
  42. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  43. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  44. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  45. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  46. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  47. data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
  48. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  49. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  50. data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
  51. data/lib/bolt/plan_creator.rb +1 -1
  52. data/lib/bolt/plugin.rb +13 -11
  53. data/lib/bolt/project_manager.rb +1 -1
  54. data/lib/bolt/project_manager/module_migrator.rb +1 -1
  55. data/lib/bolt/result.rb +11 -15
  56. data/lib/bolt/shell.rb +16 -0
  57. data/lib/bolt/shell/bash.rb +61 -31
  58. data/lib/bolt/shell/bash/tmpdir.rb +2 -2
  59. data/lib/bolt/shell/powershell.rb +34 -12
  60. data/lib/bolt/shell/powershell/snippets.rb +30 -3
  61. data/lib/bolt/task.rb +1 -1
  62. data/lib/bolt/transport/base.rb +0 -9
  63. data/lib/bolt/transport/docker.rb +2 -126
  64. data/lib/bolt/transport/docker/connection.rb +81 -167
  65. data/lib/bolt/transport/lxd.rb +26 -0
  66. data/lib/bolt/transport/lxd/connection.rb +99 -0
  67. data/lib/bolt/transport/orch.rb +13 -5
  68. data/lib/bolt/transport/podman.rb +19 -0
  69. data/lib/bolt/transport/podman/connection.rb +98 -0
  70. data/lib/bolt/transport/ssh/connection.rb +1 -1
  71. data/lib/bolt/transport/winrm/connection.rb +1 -1
  72. data/lib/bolt/util.rb +42 -0
  73. data/lib/bolt/version.rb +1 -1
  74. data/lib/bolt_server/transport_app.rb +64 -33
  75. data/lib/bolt_spec/bolt_context.rb +9 -4
  76. data/lib/bolt_spec/plans.rb +1 -109
  77. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  78. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  79. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  80. data/lib/bolt_spec/plans/mock_executor.rb +91 -7
  81. data/modules/puppet_connect/plans/test_input_data.pp +65 -7
  82. metadata +12 -2
@@ -55,18 +55,45 @@ module Bolt
55
55
  }
56
56
  #{build_arg_list}
57
57
 
58
+ switch -regex ( Get-ExecutionPolicy )
59
+ {
60
+ '^AllSigned'
61
+ {
62
+ if ((Get-AuthenticodeSignature -File "#{script_path}").Status -ne 'Valid') {
63
+ $Host.UI.WriteErrorLine("Error: Target host Powershell ExecutionPolicy is set to ${_} and script '#{script_path}' does not contain a valid signature.")
64
+ exit 1;
65
+ }
66
+ }
67
+ '^Restricted'
68
+ {
69
+ $Host.UI.WriteErrorLine("Error: Target host Powershell ExecutionPolicy is set to ${_} which denies running any scripts on the target.")
70
+ exit 1;
71
+ }
72
+ }
73
+
74
+ if([string]::IsNullOrEmpty($invokeArgs.ScriptBlock)){
75
+ $Host.UI.WriteErrorLine("Error: Failed to obtain scriptblock from '#{script_path}'. Running scripts might be disabled on this system. For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170");
76
+ exit 1;
77
+ }
78
+
58
79
  try
59
80
  {
60
81
  Invoke-Command @invokeArgs
61
82
  }
62
83
  catch
63
84
  {
64
- Write-Error $_.Exception
65
- exit 1
85
+ $Host.UI.WriteErrorLine("[$($_.FullyQualifiedErrorId)] Exception $($_.InvocationInfo.PositionMessage).`n$($_.Exception.Message)");
86
+ exit 1;
66
87
  }
67
88
  PS
68
89
  end
69
90
 
91
+ def append_ps_module_path(directory)
92
+ <<~PS
93
+ $env:PSModulePath += ";#{directory}"
94
+ PS
95
+ end
96
+
70
97
  def ps_task(path, arguments)
71
98
  <<~PS
72
99
  $private:tempArgs = Get-ContentAsJson (
@@ -138,7 +165,7 @@ module Bolt
138
165
  [Parameter(Mandatory = $true)] $Text,
139
166
  [Parameter(Mandatory = $false)] [Text.Encoding] $Encoding = [Text.Encoding]::UTF8
140
167
  )
141
-
168
+
142
169
  $Text | ConvertFrom-Json | ConvertFrom-PSCustomObject
143
170
  }
144
171
  PS
data/lib/bolt/task.rb CHANGED
@@ -148,7 +148,7 @@ module Bolt
148
148
 
149
149
  if unknown_keys.any?
150
150
  msg = "Metadata for task '#{@name}' contains unknown keys: #{unknown_keys.join(', ')}."
151
- msg += " This could be a typo in the task metadata or may result in incorrect behavior."
151
+ msg += " This could be a typo in the task metadata or might result in incorrect behavior."
152
152
  Bolt::Logger.warn("unknown_task_metadata_keys", msg)
153
153
  end
154
154
  end
@@ -74,15 +74,6 @@ module Bolt
74
74
  interpreters[Pathname(executable).extname] if interpreters
75
75
  end
76
76
 
77
- # Transform a parameter map to an environment variable map, with parameter names prefixed
78
- # with 'PT_' and values transformed to JSON unless they're strings.
79
- def envify_params(params)
80
- params.each_with_object({}) do |(k, v), h|
81
- v = v.to_json unless v.is_a?(String)
82
- h["PT_#{k}"] = v
83
- end
84
- end
85
-
86
77
  # Raises an error if more than one target was given in the batch.
87
78
  #
88
79
  # The default implementations of batch_* strictly assume the transport is
@@ -2,11 +2,11 @@
2
2
 
3
3
  require 'json'
4
4
  require 'shellwords'
5
- require 'bolt/transport/base'
5
+ require 'bolt/transport/simple'
6
6
 
7
7
  module Bolt
8
8
  module Transport
9
- class Docker < Base
9
+ class Docker < Simple
10
10
  def provided_features
11
11
  ['shell']
12
12
  end
@@ -16,130 +16,6 @@ module Bolt
16
16
  conn.connect
17
17
  yield conn
18
18
  end
19
-
20
- def upload(target, source, destination, _options = {})
21
- with_connection(target) do |conn|
22
- conn.with_remote_tmpdir do |dir|
23
- basename = File.basename(source)
24
- tmpfile = "#{dir}/#{basename}"
25
- if File.directory?(source)
26
- conn.write_remote_directory(source, tmpfile)
27
- else
28
- conn.write_remote_file(source, tmpfile)
29
- end
30
-
31
- _, stderr, exitcode = conn.execute('mv', tmpfile, destination, {})
32
- if exitcode != 0
33
- message = "Could not move temporary file '#{tmpfile}' to #{destination}: #{stderr}"
34
- raise Bolt::Node::FileError.new(message, 'MV_ERROR')
35
- end
36
- end
37
- Bolt::Result.for_upload(target, source, destination)
38
- end
39
- end
40
-
41
- def download(target, source, destination, _options = {})
42
- with_connection(target) do |conn|
43
- download = File.join(destination, Bolt::Util.unix_basename(source))
44
- conn.download_remote_content(source, destination)
45
- Bolt::Result.for_download(target, source, destination, download)
46
- end
47
- end
48
-
49
- def run_command(target, command, options = {}, position = [])
50
- execute_options = {}
51
- execute_options[:tty] = target.options['tty']
52
- execute_options[:environment] = options[:env_vars]
53
-
54
- if target.options['shell-command'] && !target.options['shell-command'].empty?
55
- # escape any double quotes in command
56
- command = command.gsub('"', '\"')
57
- command = "#{target.options['shell-command']} \" #{command}\""
58
- end
59
- with_connection(target) do |conn|
60
- stdout, stderr, exitcode = conn.execute(*Shellwords.split(command), execute_options)
61
- Bolt::Result.for_command(target,
62
- stdout,
63
- stderr,
64
- exitcode,
65
- 'command',
66
- command,
67
- position)
68
- end
69
- end
70
-
71
- def run_script(target, script, arguments, options = {}, position = [])
72
- # unpack any Sensitive data
73
- arguments = unwrap_sensitive_args(arguments)
74
- execute_options = {}
75
- execute_options[:environment] = options[:env_vars]
76
-
77
- with_connection(target) do |conn|
78
- conn.with_remote_tmpdir do |dir|
79
- remote_path = conn.write_remote_executable(dir, script)
80
- stdout, stderr, exitcode = conn.execute(remote_path, *arguments, execute_options)
81
- Bolt::Result.for_command(target,
82
- stdout,
83
- stderr,
84
- exitcode,
85
- 'script',
86
- script,
87
- position)
88
- end
89
- end
90
- end
91
-
92
- def run_task(target, task, arguments, _options = {}, position = [])
93
- implementation = task.select_implementation(target, provided_features)
94
- executable = implementation['path']
95
- input_method = implementation['input_method']
96
- extra_files = implementation['files']
97
- input_method ||= 'both'
98
-
99
- # unpack any Sensitive data
100
- arguments = unwrap_sensitive_args(arguments)
101
- with_connection(target) do |conn|
102
- execute_options = {}
103
- execute_options[:interpreter] = select_interpreter(executable, target.options['interpreters'])
104
- conn.with_remote_tmpdir do |dir|
105
- if extra_files.empty?
106
- task_dir = dir
107
- else
108
- # TODO: optimize upload of directories
109
- arguments['_installdir'] = dir
110
- task_dir = File.join(dir, task.tasks_dir)
111
- conn.mkdirs([task_dir] + extra_files.map { |file| File.join(dir, File.dirname(file['name'])) })
112
- extra_files.each do |file|
113
- conn.write_remote_file(file['path'], File.join(dir, file['name']))
114
- end
115
- end
116
-
117
- remote_task_path = conn.write_remote_executable(task_dir, executable)
118
-
119
- if Bolt::Task::STDIN_METHODS.include?(input_method)
120
- execute_options[:stdin] = StringIO.new(JSON.dump(arguments))
121
- end
122
-
123
- if Bolt::Task::ENVIRONMENT_METHODS.include?(input_method)
124
- execute_options[:environment] = envify_params(arguments)
125
- end
126
-
127
- stdout, stderr, exitcode = conn.execute(remote_task_path, execute_options)
128
- Bolt::Result.for_task(target,
129
- stdout,
130
- stderr,
131
- exitcode,
132
- task.name,
133
- position)
134
- end
135
- end
136
- end
137
-
138
- def connected?(target)
139
- with_connection(target) { true }
140
- rescue Bolt::Node::ConnectError
141
- false
142
- end
143
19
  end
144
20
  end
145
21
  end
@@ -5,227 +5,141 @@ require 'bolt/node/errors'
5
5
 
6
6
  module Bolt
7
7
  module Transport
8
- class Docker < Base
8
+ class Docker < Simple
9
9
  class Connection
10
+ attr_reader :user, :target
11
+
10
12
  def initialize(target)
11
13
  raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
12
14
  @target = target
15
+ @user = ENV['USER'] || Etc.getlogin
13
16
  @logger = Bolt::Logger.logger(target.safe_name)
14
- @docker_host = @target.options['service-url']
15
- @logger.trace("Initializing docker connection to #{@target.safe_name}")
17
+ @container_info = {}
18
+ @docker_host = target.options['service-url']
19
+ @logger.trace("Initializing docker connection to #{target.safe_name}")
20
+ end
21
+
22
+ def shell
23
+ @shell ||= if Bolt::Util.windows?
24
+ Bolt::Shell::Powershell.new(target, self)
25
+ else
26
+ Bolt::Shell::Bash.new(target, self)
27
+ end
28
+ end
29
+
30
+ # The full ID of the target container
31
+ #
32
+ # @return [String] The full ID of the target container
33
+ def container_id
34
+ @container_info["Id"]
35
+ end
36
+
37
+ def run_cmd(cmd, env_vars)
38
+ Bolt::Util.exec_docker(cmd, env_vars)
39
+ end
40
+
41
+ private def env_hash
42
+ # Set the DOCKER_HOST if we are using a non-default service-url
43
+ @docker_host.nil? ? {} : { 'DOCKER_HOST' => @docker_host }
16
44
  end
17
45
 
18
46
  def connect
19
47
  # We don't actually have a connection, but we do need to
20
48
  # check that the container exists and is running.
21
- output = execute_local_docker_json_command('ps')
22
- index = output.find_index { |item| item["ID"] == @target.host || item["Names"] == @target.host }
23
- raise "Could not find a container with name or ID matching '#{@target.host}'" if index.nil?
49
+ output = execute_local_json_command('ps')
50
+ index = output.find_index { |item| item["ID"] == target.host || item["Names"] == target.host }
51
+ raise "Could not find a container with name or ID matching '#{target.host}'" if index.nil?
24
52
  # Now find the indepth container information
25
- output = execute_local_docker_json_command('inspect', [output[index]["ID"]])
53
+ output = execute_local_json_command('inspect', [output[index]["ID"]])
26
54
  # Store the container information for later
27
55
  @container_info = output[0]
28
56
  @logger.trace { "Opened session" }
29
57
  true
30
58
  rescue StandardError => e
31
59
  raise Bolt::Node::ConnectError.new(
32
- "Failed to connect to #{@target.safe_name}: #{e.message}",
60
+ "Failed to connect to #{target.safe_name}: #{e.message}",
33
61
  'CONNECT_ERROR'
34
62
  )
35
63
  end
36
64
 
37
- # Executes a command inside the target container
65
+ def add_env_vars(env_vars)
66
+ @env_vars = Bolt::Util.format_env_vars_for_cli(env_vars)
67
+ end
68
+
69
+ # Executes a command inside the target container. This is called from the shell class.
38
70
  #
39
- # @param command [Array] The command to run, expressed as an array of strings
40
- # @param options [Hash] command specific options
41
- # @option opts [String] :interpreter statements that are prefixed to the command e.g `/bin/bash` or `cmd.exe /c`
42
- # @option opts [Hash] :environment A hash of environment variables that will be injected into the command
43
- # @option opts [IO] :stdin An IO object that will be used to redirect STDIN for the docker command
44
- def execute(*command, options)
45
- command.unshift(options[:interpreter]) if options[:interpreter]
46
- # Build the `--env` parameters
47
- envs = []
48
- if options[:environment]
49
- options[:environment].each { |env, val| envs.concat(['--env', "#{env}=#{val}"]) }
71
+ # @param command [string] The command to run
72
+ def execute(command)
73
+ args = []
74
+ # CODEREVIEW: Is it always safe to pass --interactive?
75
+ args += %w[--interactive]
76
+ args += %w[--tty] if target.options['tty']
77
+ args += %W[--env DOCKER_HOST=#{@docker_host}] if @docker_host
78
+ args += @env_vars if @env_vars
79
+
80
+ if target.options['shell-command'] && !target.options['shell-command'].empty?
81
+ # escape any double quotes in command
82
+ command = command.gsub('"', '\"')
83
+ command = "#{target.options['shell-command']} \"#{command}\""
50
84
  end
51
85
 
52
- command_options = []
53
- # Need to be interactive if redirecting STDIN
54
- command_options << '--interactive' unless options[:stdin].nil?
55
- command_options << '--tty' if options[:tty]
56
- command_options.concat(envs) unless envs.empty?
57
- command_options << container_id
58
- command_options.concat(command)
59
-
60
- @logger.trace { "Executing: exec #{command_options}" }
86
+ docker_command = %w[docker exec] + args + [container_id] + Shellwords.split(command)
87
+ @logger.trace { "Executing: #{docker_command.join(' ')}" }
61
88
 
62
- stdout_str, stderr_str, status = execute_local_docker_command('exec', command_options, options[:stdin])
63
-
64
- # The actual result is the exitstatus not the process object
65
- status = status.nil? ? -32768 : status.exitstatus
66
- if status == 0
67
- @logger.trace { "Command returned successfully" }
68
- else
69
- @logger.trace { "Command failed with exit code #{status}" }
70
- end
71
- stdout_str.force_encoding(Encoding::UTF_8)
72
- stderr_str.force_encoding(Encoding::UTF_8)
73
- # Normalise line endings
74
- stdout_str.gsub!("\r\n", "\n")
75
- stderr_str.gsub!("\r\n", "\n")
76
- [stdout_str, stderr_str, status]
89
+ Open3.popen3(*docker_command)
77
90
  rescue StandardError
78
91
  @logger.trace { "Command aborted" }
79
92
  raise
80
93
  end
81
94
 
82
- def write_remote_file(source, destination)
83
- @logger.trace { "Uploading #{source} to #{destination}" }
84
- _, stdout_str, status = execute_local_docker_command('cp', [source, "#{container_id}:#{destination}"])
85
- unless status.exitstatus.zero?
86
- raise "Error writing file to container #{@container_id}: #{stdout_str}"
87
- end
88
- rescue StandardError => e
89
- raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
90
- end
91
-
92
- def write_remote_directory(source, destination)
95
+ def upload_file(source, destination)
93
96
  @logger.trace { "Uploading #{source} to #{destination}" }
94
- _, stdout_str, status = execute_local_docker_command('cp', [source, "#{container_id}:#{destination}"])
95
- unless status.exitstatus.zero?
96
- raise "Error writing directory to container #{@container_id}: #{stdout_str}"
97
+ _out, err, stat = run_cmd(['cp', source, "#{container_id}:#{destination}"], env_hash)
98
+ unless stat.exitstatus.zero?
99
+ raise "Error writing to container #{container_id}: #{err}"
97
100
  end
98
101
  rescue StandardError => e
99
102
  raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
100
103
  end
101
104
 
102
- def download_remote_content(source, destination)
105
+ def download_file(source, destination, _download)
103
106
  @logger.trace { "Downloading #{source} to #{destination}" }
104
107
  # Create the destination directory, otherwise copying a source directory with Docker will
105
108
  # copy the *contents* of the directory.
106
109
  # https://docs.docker.com/engine/reference/commandline/cp/
107
110
  FileUtils.mkdir_p(destination)
108
- _, stdout_str, status = execute_local_docker_command('cp', ["#{container_id}:#{source}", destination])
109
- unless status.exitstatus.zero?
110
- raise "Error downloading content from container #{@container_id}: #{stdout_str}"
111
+ _out, err, stat = run_cmd(['cp', "#{container_id}:#{source}", destination], env_hash)
112
+ unless stat.exitstatus.zero?
113
+ raise "Error downloading content from container #{container_id}: #{err}"
111
114
  end
112
115
  rescue StandardError => e
113
116
  raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
114
117
  end
115
118
 
116
- def mkdirs(dirs)
117
- _, stderr, exitcode = execute('mkdir', '-p', *dirs, {})
118
- if exitcode != 0
119
- message = "Could not create directories: #{stderr}"
120
- raise Bolt::Node::FileError.new(message, 'MKDIR_ERROR')
121
- end
122
- end
123
-
124
- def make_tmpdir
125
- tmpdir = @target.options.fetch('tmpdir', container_tmpdir)
126
- tmppath = "#{tmpdir}/#{SecureRandom.uuid}"
127
-
128
- stdout, stderr, exitcode = execute('mkdir', '-m', '700', tmppath, {})
129
- if exitcode != 0
130
- raise Bolt::Node::FileError.new("Could not make tmpdir: #{stderr}", 'TMPDIR_ERROR')
131
- end
132
- tmppath || stdout.first
133
- end
134
-
135
- def with_remote_tmpdir
136
- dir = make_tmpdir
137
- yield dir
138
- ensure
139
- if dir
140
- if @target.options['cleanup']
141
- _, stderr, exitcode = execute('rm', '-rf', dir, {})
142
- if exitcode != 0
143
- Bolt::Logger.warn("fail_cleanup", "Failed to clean up tmpdir '#{dir}': #{stderr}")
144
- end
145
- else
146
- Bolt::Logger.warn("skip_cleanup", "Skipping cleanup of tmpdir '#{dir}'")
147
- end
148
- end
149
- end
150
-
151
- def write_remote_executable(dir, file, filename = nil)
152
- filename ||= File.basename(file)
153
- remote_path = File.join(dir.to_s, filename)
154
- write_remote_file(file, remote_path)
155
- make_executable(remote_path)
156
- remote_path
157
- end
158
-
159
- def make_executable(path)
160
- _, stderr, exitcode = execute('chmod', 'u+x', path, {})
161
- if exitcode != 0
162
- message = "Could not make file '#{path}' executable: #{stderr}"
163
- raise Bolt::Node::FileError.new(message, 'CHMOD_ERROR')
164
- end
119
+ # Executes a Docker CLI command and parses the output in JSON format
120
+ #
121
+ # @param subcommand [String] The docker subcommand to run
122
+ # e.g. 'inspect' for `docker inspect`
123
+ # @param arguments [Array] Arguments to pass to the docker command
124
+ # e.g. 'src' and 'dest' for `docker cp <src> <dest>
125
+ # @return [Object] Ruby object representation of the JSON string
126
+ def execute_local_json_command(subcommand, arguments = [])
127
+ cmd = [subcommand, '--format', '{{json .}}'].concat(arguments)
128
+ out, _err, _stat = run_cmd(cmd, env_hash)
129
+ extract_json(out)
165
130
  end
166
131
 
167
- private
168
-
169
132
  # Converts the JSON encoded STDOUT string from the docker cli into ruby objects
170
133
  #
171
- # @param stdout_string [String] The string to convert
134
+ # @param stdout [String] The string to convert
172
135
  # @return [Object] Ruby object representation of the JSON string
173
- def extract_json(stdout_string)
136
+ private def extract_json(stdout)
174
137
  # The output from the docker format command is a JSON string per line.
175
138
  # We can't do a direct convert but this helper method will convert it into
176
139
  # an array of Objects
177
- stdout_string.split("\n")
178
- .reject { |str| str.strip.empty? }
179
- .map { |str| JSON.parse(str) }
180
- end
181
-
182
- # rubocop:disable Layout/LineLength
183
- # Executes a Docker CLI command
184
- #
185
- # @param subcommand [String] The docker subcommand to run e.g. 'inspect' for `docker inspect`
186
- # @param command_options [Array] Additional command options e.g. ['--size'] for `docker inspect --size`
187
- # @param redir_stdin [IO] IO object which will be use to as STDIN in the docker command. Default is nil, which does not perform redirection
188
- # @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
189
- # rubocop:enable Layout/LineLength
190
- def execute_local_docker_command(subcommand, command_options = [], redir_stdin = nil)
191
- env_hash = {}
192
- # Set the DOCKER_HOST if we are using a non-default service-url
193
- env_hash['DOCKER_HOST'] = @docker_host unless @docker_host.nil?
194
-
195
- command_options = [] if command_options.nil?
196
- docker_command = [subcommand].concat(command_options)
197
-
198
- # Always use binary mode for any text data
199
- capture_options = { binmode: true }
200
- capture_options[:stdin_data] = redir_stdin unless redir_stdin.nil?
201
- stdout_str, stderr_str, status = Open3.capture3(env_hash, 'docker', *docker_command, capture_options)
202
- [stdout_str, stderr_str, status]
203
- end
204
-
205
- # Executes a Docker CLI command and parses the output in JSON format
206
- #
207
- # @param subcommand [String] The docker subcommand to run e.g. 'inspect' for `docker inspect`
208
- # @param command_options [Array] Additional command options e.g. ['--size'] for `docker inspect --size`
209
- # @return [Object] Ruby object representation of the JSON string
210
- def execute_local_docker_json_command(subcommand, command_options = [])
211
- command_options = [] if command_options.nil?
212
- command_options = ['--format', '{{json .}}'].concat(command_options)
213
- stdout_str, _stderr_str, _status = execute_local_docker_command(subcommand, command_options)
214
- extract_json(stdout_str)
215
- end
216
-
217
- # The full ID of the target container
218
- #
219
- # @return [String] The full ID of the target container
220
- def container_id
221
- @container_info["Id"]
222
- end
223
-
224
- # The temp path inside the target container
225
- #
226
- # @return [String] The absolute path to the temp directory
227
- def container_tmpdir
228
- '/tmp'
140
+ stdout.split("\n")
141
+ .reject { |str| str.strip.empty? }
142
+ .map { |str| JSON.parse(str) }
229
143
  end
230
144
  end
231
145
  end