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.
- checksums.yaml +4 -4
- data/Puppetfile +13 -11
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +24 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
- data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
- data/lib/bolt/analytics.rb +4 -8
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +6 -3
- data/lib/bolt/cli.rb +123 -37
- data/lib/bolt/config.rb +15 -7
- data/lib/bolt/config/options.rb +62 -12
- data/lib/bolt/config/transport/lxd.rb +23 -0
- data/lib/bolt/config/transport/options.rb +8 -1
- data/lib/bolt/config/transport/podman.rb +33 -0
- data/lib/bolt/container_result.rb +105 -0
- data/lib/bolt/error.rb +15 -0
- data/lib/bolt/executor.rb +37 -18
- data/lib/bolt/inventory/options.rb +9 -0
- data/lib/bolt/inventory/target.rb +16 -0
- data/lib/bolt/logger.rb +8 -0
- data/lib/bolt/module_installer.rb +2 -2
- data/lib/bolt/module_installer/puppetfile.rb +2 -2
- data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
- data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
- data/lib/bolt/node/output.rb +14 -4
- data/lib/bolt/outputter/human.rb +259 -90
- data/lib/bolt/outputter/json.rb +3 -1
- data/lib/bolt/outputter/logger.rb +17 -0
- data/lib/bolt/pal.rb +25 -4
- data/lib/bolt/pal/yaml_plan.rb +1 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -141
- data/lib/bolt/pal/yaml_plan/step.rb +91 -31
- data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
- 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 +36 -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 +3 -3
- data/lib/bolt/plan_creator.rb +1 -1
- data/lib/bolt/plugin.rb +13 -11
- data/lib/bolt/project_manager.rb +1 -1
- data/lib/bolt/project_manager/module_migrator.rb +1 -1
- data/lib/bolt/result.rb +11 -15
- data/lib/bolt/shell.rb +16 -0
- data/lib/bolt/shell/bash.rb +61 -31
- data/lib/bolt/shell/bash/tmpdir.rb +2 -2
- data/lib/bolt/shell/powershell.rb +34 -12
- data/lib/bolt/shell/powershell/snippets.rb +30 -3
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/base.rb +0 -9
- data/lib/bolt/transport/docker.rb +2 -126
- data/lib/bolt/transport/docker/connection.rb +81 -167
- data/lib/bolt/transport/lxd.rb +26 -0
- data/lib/bolt/transport/lxd/connection.rb +99 -0
- data/lib/bolt/transport/orch.rb +13 -5
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/podman/connection.rb +98 -0
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/transport/winrm/connection.rb +1 -1
- data/lib/bolt/util.rb +42 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +64 -33
- 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/action_stubs/command_stub.rb +8 -1
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
- data/lib/bolt_spec/plans/mock_executor.rb +91 -7
- data/modules/puppet_connect/plans/test_input_data.pp +65 -7
- 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
|
-
|
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
|
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
|
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
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
require 'shellwords'
|
5
|
-
require 'bolt/transport/
|
5
|
+
require 'bolt/transport/simple'
|
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,141 @@ 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"]
|
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 =
|
22
|
-
index = output.find_index { |item| item["ID"] ==
|
23
|
-
raise "Could not find a container with name or ID matching '#{
|
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 =
|
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 #{
|
60
|
+
"Failed to connect to #{target.safe_name}: #{e.message}",
|
33
61
|
'CONNECT_ERROR'
|
34
62
|
)
|
35
63
|
end
|
36
64
|
|
37
|
-
|
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 [
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
if options[
|
49
|
-
|
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
|
-
|
53
|
-
|
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
|
-
|
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
|
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
|
-
|
95
|
-
unless
|
96
|
-
raise "Error writing
|
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
|
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
|
-
|
109
|
-
unless
|
110
|
-
raise "Error downloading content from container #{
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
134
|
+
# @param stdout [String] The string to convert
|
172
135
|
# @return [Object] Ruby object representation of the JSON string
|
173
|
-
def extract_json(
|
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
|
-
|
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'
|
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
|