bolt 2.42.0 → 3.3.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.
- checksums.yaml +4 -4
- data/Puppetfile +21 -19
- data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +25 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +6 -8
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -5
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +7 -3
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
- data/lib/bolt/analytics.rb +3 -2
- data/lib/bolt/applicator.rb +11 -1
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +9 -116
- data/lib/bolt/catalog.rb +10 -29
- data/lib/bolt/cli.rb +90 -154
- data/lib/bolt/config.rb +66 -239
- data/lib/bolt/config/options.rb +79 -102
- data/lib/bolt/config/transport/local.rb +1 -0
- data/lib/bolt/config/transport/lxd.rb +21 -0
- data/lib/bolt/config/transport/options.rb +9 -2
- data/lib/bolt/config/transport/orch.rb +1 -0
- data/lib/bolt/executor.rb +23 -6
- data/lib/bolt/inventory.rb +1 -1
- data/lib/bolt/inventory/group.rb +7 -4
- data/lib/bolt/logger.rb +123 -11
- data/lib/bolt/module_installer.rb +6 -4
- data/lib/bolt/module_installer/puppetfile.rb +2 -2
- data/lib/bolt/module_installer/resolver.rb +59 -14
- data/lib/bolt/module_installer/specs/forge_spec.rb +10 -4
- data/lib/bolt/module_installer/specs/git_spec.rb +19 -4
- data/lib/bolt/outputter/human.rb +56 -17
- data/lib/bolt/outputter/json.rb +16 -16
- data/lib/bolt/outputter/rainbow.rb +3 -3
- data/lib/bolt/pal.rb +95 -15
- data/lib/bolt/pal/yaml_plan.rb +9 -4
- data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -153
- data/lib/bolt/pal/yaml_plan/step.rb +91 -52
- data/lib/bolt/pal/yaml_plan/step/command.rb +16 -16
- data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
- data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
- data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
- data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
- data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
- data/lib/bolt/pal/yaml_plan/step/script.rb +32 -17
- data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
- data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
- data/lib/bolt/pal/yaml_plan/transpiler.rb +2 -1
- data/lib/bolt/plan_creator.rb +1 -1
- data/lib/bolt/plugin.rb +2 -2
- data/lib/bolt/plugin/cache.rb +7 -7
- data/lib/bolt/plugin/module.rb +0 -23
- data/lib/bolt/plugin/puppet_connect_data.rb +77 -0
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +54 -81
- data/lib/bolt/project_manager.rb +5 -4
- data/lib/bolt/project_manager/module_migrator.rb +7 -6
- data/lib/bolt/rerun.rb +1 -1
- data/lib/bolt/result.rb +6 -1
- data/lib/bolt/shell.rb +16 -0
- data/lib/bolt/shell/bash.rb +57 -25
- data/lib/bolt/shell/bash/tmpdir.rb +6 -3
- data/lib/bolt/shell/powershell.rb +33 -10
- data/lib/bolt/shell/powershell/snippets.rb +37 -150
- data/lib/bolt/task.rb +2 -2
- data/lib/bolt/transport/base.rb +0 -9
- data/lib/bolt/transport/docker.rb +1 -125
- data/lib/bolt/transport/docker/connection.rb +86 -161
- data/lib/bolt/transport/local.rb +1 -9
- data/lib/bolt/transport/lxd.rb +26 -0
- data/lib/bolt/transport/lxd/connection.rb +99 -0
- data/lib/bolt/transport/orch/connection.rb +1 -1
- data/lib/bolt/transport/ssh.rb +1 -2
- data/lib/bolt/transport/ssh/connection.rb +2 -2
- data/lib/bolt/transport/winrm/connection.rb +1 -1
- data/lib/bolt/validator.rb +2 -2
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/config.rb +1 -1
- data/lib/bolt_server/transport_app.rb +61 -32
- data/lib/bolt_spec/bolt_context.rb +9 -4
- data/lib/bolt_spec/plans.rb +1 -109
- data/lib/bolt_spec/plans/action_stubs.rb +1 -1
- data/lib/bolt_spec/plans/mock_executor.rb +4 -0
- data/libexec/bolt_catalog +1 -1
- data/modules/aggregate/plans/count.pp +21 -0
- data/modules/aggregate/plans/targets.pp +21 -0
- data/modules/puppet_connect/plans/test_input_data.pp +67 -0
- data/modules/puppetdb_fact/plans/init.pp +10 -0
- metadata +13 -9
- data/modules/aggregate/plans/nodes.pp +0 -36
data/lib/bolt/task.rb
CHANGED
@@ -148,8 +148,8 @@ 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
|
152
|
-
|
151
|
+
msg += " This could be a typo in the task metadata or might result in incorrect behavior."
|
152
|
+
Bolt::Logger.warn("unknown_task_metadata_keys", msg)
|
153
153
|
end
|
154
154
|
end
|
155
155
|
end
|
data/lib/bolt/transport/base.rb
CHANGED
@@ -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
|
@@ -6,7 +6,7 @@ require 'bolt/transport/base'
|
|
6
6
|
|
7
7
|
module Bolt
|
8
8
|
module Transport
|
9
|
-
class Docker <
|
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,152 @@ require 'bolt/node/errors'
|
|
5
5
|
|
6
6
|
module Bolt
|
7
7
|
module Transport
|
8
|
-
class Docker <
|
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
|
-
@
|
15
|
-
@
|
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"]
|
16
35
|
end
|
17
36
|
|
18
37
|
def connect
|
19
38
|
# We don't actually have a connection, but we do need to
|
20
39
|
# check that the container exists and is running.
|
21
|
-
output =
|
22
|
-
index = output.find_index { |item| item["ID"] ==
|
23
|
-
raise "Could not find a container with name or ID matching '#{
|
40
|
+
output = execute_local_json_command('ps')
|
41
|
+
index = output.find_index { |item| item["ID"] == target.host || item["Names"] == target.host }
|
42
|
+
raise "Could not find a container with name or ID matching '#{target.host}'" if index.nil?
|
24
43
|
# Now find the indepth container information
|
25
|
-
output =
|
44
|
+
output = execute_local_json_command('inspect', [output[index]["ID"]])
|
26
45
|
# Store the container information for later
|
27
46
|
@container_info = output[0]
|
28
47
|
@logger.trace { "Opened session" }
|
29
48
|
true
|
30
49
|
rescue StandardError => e
|
31
50
|
raise Bolt::Node::ConnectError.new(
|
32
|
-
"Failed to connect to #{
|
51
|
+
"Failed to connect to #{target.safe_name}: #{e.message}",
|
33
52
|
'CONNECT_ERROR'
|
34
53
|
)
|
35
54
|
end
|
36
55
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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}"]) }
|
56
|
+
def add_env_vars(env_vars)
|
57
|
+
@env_vars = env_vars.each_with_object([]) do |env_var, acc|
|
58
|
+
acc << "--env"
|
59
|
+
acc << "#{env_var[0]}=#{env_var[1]}"
|
50
60
|
end
|
61
|
+
end
|
51
62
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
63
|
+
# Executes a command inside the target container. This is called from the shell class.
|
64
|
+
#
|
65
|
+
# @param command [string] The command to run
|
66
|
+
def execute(command)
|
67
|
+
args = []
|
68
|
+
# CODEREVIEW: Is it always safe to pass --interactive?
|
69
|
+
args += %w[--interactive]
|
70
|
+
args += %w[--tty] if target.options['tty']
|
71
|
+
args += %W[--env DOCKER_HOST=#{@docker_host}] if @docker_host
|
72
|
+
args += @env_vars if @env_vars
|
73
|
+
|
74
|
+
if target.options['shell-command'] && !target.options['shell-command'].empty?
|
75
|
+
# escape any double quotes in command
|
76
|
+
command = command.gsub('"', '\"')
|
77
|
+
command = "#{target.options['shell-command']} \"#{command}\""
|
78
|
+
end
|
61
79
|
|
62
|
-
|
80
|
+
docker_command = %w[docker exec] + args + [container_id] + Shellwords.split(command)
|
81
|
+
@logger.trace { "Executing: #{docker_command.join(' ')}" }
|
63
82
|
|
64
|
-
|
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]
|
83
|
+
Open3.popen3(*docker_command)
|
77
84
|
rescue StandardError
|
78
85
|
@logger.trace { "Command aborted" }
|
79
86
|
raise
|
80
87
|
end
|
81
88
|
|
82
|
-
def
|
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)
|
89
|
+
def upload_file(source, destination)
|
93
90
|
@logger.trace { "Uploading #{source} to #{destination}" }
|
94
|
-
|
91
|
+
_stdout, stderr, status = execute_local_command('cp', [source, "#{container_id}:#{destination}"])
|
95
92
|
unless status.exitstatus.zero?
|
96
|
-
raise "Error writing
|
93
|
+
raise "Error writing to container #{container_id}: #{stderr}"
|
97
94
|
end
|
98
95
|
rescue StandardError => e
|
99
96
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
100
97
|
end
|
101
98
|
|
102
|
-
def
|
99
|
+
def download_file(source, destination, _download)
|
103
100
|
@logger.trace { "Downloading #{source} to #{destination}" }
|
104
101
|
# Create the destination directory, otherwise copying a source directory with Docker will
|
105
102
|
# copy the *contents* of the directory.
|
106
103
|
# https://docs.docker.com/engine/reference/commandline/cp/
|
107
104
|
FileUtils.mkdir_p(destination)
|
108
|
-
|
105
|
+
_stdout, stderr, status = execute_local_command('cp', ["#{container_id}:#{source}", destination])
|
109
106
|
unless status.exitstatus.zero?
|
110
|
-
raise "Error downloading content from container #{
|
107
|
+
raise "Error downloading content from container #{container_id}: #{stderr}"
|
111
108
|
end
|
112
109
|
rescue StandardError => e
|
113
110
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
114
111
|
end
|
115
112
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
@logger.warn("Failed to clean up tmpdir '#{dir}': #{stderr}")
|
144
|
-
end
|
145
|
-
else
|
146
|
-
@logger.warn("Skipping cleanup of tmpdir '#{dir}'")
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
113
|
+
# Executes a Docker CLI command. This is useful for running commands as
|
114
|
+
# part of this class without having to go through the `execute`
|
115
|
+
# function and manage pipes.
|
116
|
+
#
|
117
|
+
# @param subcommand [String] The docker subcommand to run
|
118
|
+
# e.g. 'inspect' for `docker inspect`
|
119
|
+
# @param arguments [Array] Arguments to pass to the docker command
|
120
|
+
# e.g. 'src' and 'dest' for `docker cp <src> <dest>
|
121
|
+
# @return [String, String, Process::Status] The output of the command: STDOUT, STDERR, Process Status
|
122
|
+
private def execute_local_command(subcommand, arguments = [])
|
123
|
+
# Set the DOCKER_HOST if we are using a non-default service-url
|
124
|
+
env_hash = @docker_host.nil? ? {} : { 'DOCKER_HOST' => @docker_host }
|
125
|
+
docker_command = [subcommand].concat(arguments)
|
150
126
|
|
151
|
-
|
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
|
127
|
+
Open3.capture3(env_hash, 'docker', *docker_command, { binmode: true })
|
157
128
|
end
|
158
129
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
130
|
+
# Executes a Docker CLI command and parses the output in JSON format
|
131
|
+
#
|
132
|
+
# @param subcommand [String] The docker subcommand to run
|
133
|
+
# e.g. 'inspect' for `docker inspect`
|
134
|
+
# @param arguments [Array] Arguments to pass to the docker command
|
135
|
+
# e.g. 'src' and 'dest' for `docker cp <src> <dest>
|
136
|
+
# @return [Object] Ruby object representation of the JSON string
|
137
|
+
private def execute_local_json_command(subcommand, arguments = [])
|
138
|
+
command_options = ['--format', '{{json .}}'].concat(arguments)
|
139
|
+
stdout, _stderr, _status = execute_local_command(subcommand, command_options)
|
140
|
+
extract_json(stdout)
|
165
141
|
end
|
166
142
|
|
167
|
-
private
|
168
|
-
|
169
143
|
# Converts the JSON encoded STDOUT string from the docker cli into ruby objects
|
170
144
|
#
|
171
145
|
# @param stdout_string [String] The string to convert
|
172
146
|
# @return [Object] Ruby object representation of the JSON string
|
173
|
-
def extract_json(
|
147
|
+
private def extract_json(stdout)
|
174
148
|
# The output from the docker format command is a JSON string per line.
|
175
149
|
# We can't do a direct convert but this helper method will convert it into
|
176
150
|
# an array of Objects
|
177
|
-
|
178
|
-
|
179
|
-
|
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'
|
151
|
+
stdout.split("\n")
|
152
|
+
.reject { |str| str.strip.empty? }
|
153
|
+
.map { |str| JSON.parse(str) }
|
229
154
|
end
|
230
155
|
end
|
231
156
|
end
|