bolt 1.14.0 → 1.15.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/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +8 -1
- data/bolt-modules/system/lib/puppet/functions/system/env.rb +1 -1
- data/lib/bolt/analytics.rb +7 -1
- data/lib/bolt/applicator.rb +3 -1
- data/lib/bolt/apply_result.rb +1 -0
- data/lib/bolt/config.rb +2 -1
- data/lib/bolt/executor.rb +20 -2
- data/lib/bolt/inventory/group.rb +25 -3
- data/lib/bolt/notifier.rb +5 -4
- data/lib/bolt/pal.rb +10 -2
- data/lib/bolt/pal/yaml_plan.rb +163 -0
- data/lib/bolt/pal/yaml_plan/evaluator.rb +163 -0
- data/lib/bolt/pal/yaml_plan/loader.rb +86 -0
- data/lib/bolt/result.rb +12 -14
- data/lib/bolt/task.rb +15 -2
- data/lib/bolt/task/puppet_server.rb +9 -6
- data/lib/bolt/transport/docker.rb +3 -3
- data/lib/bolt/transport/docker/connection.rb +3 -1
- data/lib/bolt/transport/local.rb +11 -3
- data/lib/bolt/transport/orch.rb +17 -11
- data/lib/bolt/transport/remote.rb +2 -2
- data/lib/bolt/transport/ssh.rb +12 -3
- data/lib/bolt/transport/ssh/connection.rb +10 -11
- data/lib/bolt/transport/winrm.rb +12 -3
- data/lib/bolt/transport/winrm/connection.rb +3 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/file_cache.rb +4 -1
- data/lib/bolt_server/transport_app.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
- data/lib/bolt_spec/run.rb +62 -0
- data/lib/plan_executor/app.rb +3 -1
- metadata +7 -5
- data/lib/bolt/task/remote.rb +0 -25
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/pal/yaml_plan'
|
4
|
+
require 'bolt/pal/yaml_plan/evaluator'
|
5
|
+
require 'psych'
|
6
|
+
|
7
|
+
module Bolt
|
8
|
+
class PAL
|
9
|
+
class YamlPlan
|
10
|
+
class Loader
|
11
|
+
class PuppetVisitor < Psych::Visitors::NoAliasRuby
|
12
|
+
def self.create_visitor
|
13
|
+
class_loader = Psych::ClassLoader::Restricted.new([], [])
|
14
|
+
scanner = Psych::ScalarScanner.new(class_loader)
|
15
|
+
new(scanner, class_loader)
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize(node)
|
19
|
+
if node.quoted
|
20
|
+
case node.style
|
21
|
+
when Psych::Nodes::Scalar::SINGLE_QUOTED
|
22
|
+
# Single-quoted strings are treated literally
|
23
|
+
# @ss is a ScalarScanner, from the base ToRuby visitor class
|
24
|
+
node.value
|
25
|
+
when Psych::Nodes::Scalar::DOUBLE_QUOTED
|
26
|
+
DoubleQuotedString.new(node.value)
|
27
|
+
# | style string or > style string
|
28
|
+
when Psych::Nodes::Scalar::LITERAL, Psych::Nodes::Scalar::FOLDED
|
29
|
+
CodeLiteral.new(node.value)
|
30
|
+
# This one shouldn't be possible
|
31
|
+
else
|
32
|
+
@ss.tokenize(node.value)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
value = @ss.tokenize(node.value)
|
36
|
+
if value.is_a?(String)
|
37
|
+
BareString.new(value)
|
38
|
+
else
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.parse_plan(yaml_string, source_ref)
|
46
|
+
# This passes the filename as the second arg for compatibility with Psych used with ruby < 2.6
|
47
|
+
# This can be removed when we remove support for ruby 2.5
|
48
|
+
parse_tree = if Psych.method(:parse).parameters.include?('legacy_filename')
|
49
|
+
Psych.parse(yaml_string, filename: source_ref)
|
50
|
+
else
|
51
|
+
Psych.parse(yaml_string, source_ref)
|
52
|
+
end
|
53
|
+
PuppetVisitor.create_visitor.accept(parse_tree)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.create(loader, typed_name, source_ref, yaml_string)
|
57
|
+
result = parse_plan(yaml_string, source_ref)
|
58
|
+
unless result.is_a?(Hash)
|
59
|
+
type = result.class.name
|
60
|
+
raise ArgumentError, "The data loaded from #{source_ref} does not contain an object - its type is #{type}"
|
61
|
+
end
|
62
|
+
|
63
|
+
begin
|
64
|
+
plan_definition = YamlPlan.new(typed_name, result).freeze
|
65
|
+
rescue Bolt::Error => e
|
66
|
+
raise Puppet::ParseError.new(e.message, source_ref)
|
67
|
+
end
|
68
|
+
|
69
|
+
created = create_function_class(plan_definition)
|
70
|
+
closure_scope = nil
|
71
|
+
|
72
|
+
created.new(closure_scope, loader.private_loader)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.create_function_class(plan_definition)
|
76
|
+
Puppet::Functions.create_function(plan_definition.name, Puppet::Functions::PuppetFunction) do
|
77
|
+
closure = Puppet::Pops::Evaluator::Closure::Named.new(plan_definition.name,
|
78
|
+
YamlPlan::Evaluator.new,
|
79
|
+
plan_definition)
|
80
|
+
init_dispatch(closure)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/bolt/result.rb
CHANGED
@@ -5,7 +5,7 @@ require 'bolt/error'
|
|
5
5
|
|
6
6
|
module Bolt
|
7
7
|
class Result
|
8
|
-
attr_reader :target, :value
|
8
|
+
attr_reader :target, :value, :type, :object
|
9
9
|
|
10
10
|
def self.from_exception(target, exception)
|
11
11
|
@exception = exception
|
@@ -23,7 +23,7 @@ module Bolt
|
|
23
23
|
Result.new(target, error: error)
|
24
24
|
end
|
25
25
|
|
26
|
-
def self.for_command(target, stdout, stderr, exit_code)
|
26
|
+
def self.for_command(target, stdout, stderr, exit_code, type, command)
|
27
27
|
value = {
|
28
28
|
'stdout' => stdout,
|
29
29
|
'stderr' => stderr,
|
@@ -37,10 +37,10 @@ module Bolt
|
|
37
37
|
'details' => { 'exit_code' => exit_code }
|
38
38
|
}
|
39
39
|
end
|
40
|
-
new(target, value: value)
|
40
|
+
new(target, value: value, type: type, object: command)
|
41
41
|
end
|
42
42
|
|
43
|
-
def self.for_task(target, stdout, stderr, exit_code)
|
43
|
+
def self.for_task(target, stdout, stderr, exit_code, task)
|
44
44
|
begin
|
45
45
|
value = JSON.parse(stdout)
|
46
46
|
unless value.is_a? Hash
|
@@ -61,11 +61,11 @@ module Bolt
|
|
61
61
|
'msg' => msg,
|
62
62
|
'details' => { 'exit_code' => exit_code } }
|
63
63
|
end
|
64
|
-
new(target, value: value)
|
64
|
+
new(target, value: value, type: 'task', object: task)
|
65
65
|
end
|
66
66
|
|
67
67
|
def self.for_upload(target, source, destination)
|
68
|
-
new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'")
|
68
|
+
new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'", type: 'upload', object: source)
|
69
69
|
end
|
70
70
|
|
71
71
|
# Satisfies the Puppet datatypes API
|
@@ -73,9 +73,11 @@ module Bolt
|
|
73
73
|
new(target, value: value)
|
74
74
|
end
|
75
75
|
|
76
|
-
def initialize(target, error: nil, message: nil, value: nil)
|
76
|
+
def initialize(target, error: nil, message: nil, value: nil, type: nil, object: nil)
|
77
77
|
@target = target
|
78
78
|
@value = value || {}
|
79
|
+
@type = type
|
80
|
+
@object = object
|
79
81
|
@value_set = !value.nil?
|
80
82
|
if error && !error.is_a?(Hash)
|
81
83
|
raise "TODO: how did we get a string error"
|
@@ -90,12 +92,12 @@ module Bolt
|
|
90
92
|
|
91
93
|
def status_hash
|
92
94
|
{ node: @target.name,
|
95
|
+
type: type,
|
96
|
+
object: object,
|
93
97
|
status: ok? ? 'success' : 'failure',
|
94
98
|
result: @value }
|
95
99
|
end
|
96
100
|
|
97
|
-
# TODO: what to call this it's the value minus special keys
|
98
|
-
# This should be {} if a value was set otherwise it's nil
|
99
101
|
def generic_value
|
100
102
|
if @value_set
|
101
103
|
value.reject { |k, _| %w[_error _output].include? k }
|
@@ -124,15 +126,11 @@ module Bolt
|
|
124
126
|
to_json
|
125
127
|
end
|
126
128
|
|
127
|
-
# TODO: remove in favor of ok?
|
128
|
-
def success?
|
129
|
-
ok?
|
130
|
-
end
|
131
|
-
|
132
129
|
def ok?
|
133
130
|
error_hash.nil?
|
134
131
|
end
|
135
132
|
alias ok ok?
|
133
|
+
alias success? ok?
|
136
134
|
|
137
135
|
# This allows access to errors outside puppet compilation
|
138
136
|
# it should be prefered over error in bolt code
|
data/lib/bolt/task.rb
CHANGED
@@ -21,12 +21,20 @@ module Bolt
|
|
21
21
|
:metadata
|
22
22
|
) do
|
23
23
|
|
24
|
-
|
24
|
+
attr_reader :remote
|
25
|
+
|
26
|
+
def initialize(task, remote: false)
|
25
27
|
super(nil, nil, [], {})
|
26
28
|
|
29
|
+
@remote = remote
|
30
|
+
|
27
31
|
task.reject { |k, _| k == 'parameters' }.each { |k, v| self[k] = v }
|
28
32
|
end
|
29
33
|
|
34
|
+
def remote_instance
|
35
|
+
self.class.new(to_h.each_with_object({}) { |(k, v), h| h[k.to_s] = v }, remote: true)
|
36
|
+
end
|
37
|
+
|
30
38
|
def description
|
31
39
|
metadata['description']
|
32
40
|
end
|
@@ -67,13 +75,18 @@ module Bolt
|
|
67
75
|
def select_implementation(target, additional_features = [])
|
68
76
|
impl = if (impls = implementations)
|
69
77
|
available_features = target.features + additional_features
|
70
|
-
impl = impls.find
|
78
|
+
impl = impls.find do |imp|
|
79
|
+
remote_impl = imp['remote']
|
80
|
+
remote_impl = metadata['remote'] if remote_impl.nil?
|
81
|
+
Set.new(imp['requirements']).subset?(available_features) && !!remote_impl == @remote
|
82
|
+
end
|
71
83
|
raise NoImplementationError.new(target, self) unless impl
|
72
84
|
impl = impl.dup
|
73
85
|
impl['path'] = file_path(impl['name'])
|
74
86
|
impl.delete('requirements')
|
75
87
|
impl
|
76
88
|
else
|
89
|
+
raise NoImplementationError.new(target, self) unless !!metadata['remote'] == @remote
|
77
90
|
name = files.first['name']
|
78
91
|
{ 'name' => name, 'path' => file_path(name) }
|
79
92
|
end
|
@@ -1,14 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'bolt/task'
|
4
|
-
|
5
3
|
module Bolt
|
6
4
|
class Task
|
7
5
|
class PuppetServer < Bolt::Task
|
8
|
-
def
|
6
|
+
def remote_instance
|
7
|
+
self.class.new(to_h.each_with_object({}) { |(k, v), h| h[k.to_s] = v },
|
8
|
+
@file_cache,
|
9
|
+
remote: true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(task, file_cache, **opts)
|
13
|
+
super(task, **opts)
|
9
14
|
@file_cache = file_cache
|
10
|
-
|
11
|
-
super(task_data)
|
15
|
+
update_file_data(task)
|
12
16
|
end
|
13
17
|
|
14
18
|
# puppetserver file entries have 'filename' rather then 'name'
|
@@ -17,7 +21,6 @@ module Bolt
|
|
17
21
|
task_data
|
18
22
|
end
|
19
23
|
|
20
|
-
# Compute local path and download files from puppetserver as needed
|
21
24
|
def file_path(file_name)
|
22
25
|
file = file_map[file_name]
|
23
26
|
file['path'] ||= @file_cache.update_file(file)
|
@@ -59,7 +59,7 @@ module Bolt
|
|
59
59
|
def run_command(target, command, _options = {})
|
60
60
|
with_connection(target) do |conn|
|
61
61
|
stdout, stderr, exitcode = conn.execute(*Shellwords.split(command), {})
|
62
|
-
Bolt::Result.for_command(target, stdout, stderr, exitcode)
|
62
|
+
Bolt::Result.for_command(target, stdout, stderr, exitcode, 'command', command)
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
@@ -71,7 +71,7 @@ module Bolt
|
|
71
71
|
conn.with_remote_tempdir do |dir|
|
72
72
|
remote_path = conn.write_remote_executable(dir, script)
|
73
73
|
stdout, stderr, exitcode = conn.execute(remote_path, *arguments, {})
|
74
|
-
Bolt::Result.for_command(target, stdout, stderr, exitcode)
|
74
|
+
Bolt::Result.for_command(target, stdout, stderr, exitcode, 'script', script)
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
@@ -112,7 +112,7 @@ module Bolt
|
|
112
112
|
end
|
113
113
|
|
114
114
|
stdout, stderr, exitcode = conn.execute(remote_task_path, execute_options)
|
115
|
-
Bolt::Result.for_task(target, stdout, stderr, exitcode)
|
115
|
+
Bolt::Result.for_task(target, stdout, stderr, exitcode, task.name)
|
116
116
|
end
|
117
117
|
end
|
118
118
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'docker'
|
4
3
|
require 'logging'
|
5
4
|
require 'bolt/node/errors'
|
6
5
|
|
@@ -9,6 +8,9 @@ module Bolt
|
|
9
8
|
class Docker < Base
|
10
9
|
class Connection
|
11
10
|
def initialize(target)
|
11
|
+
# lazy-load expensive gem code
|
12
|
+
require 'docker'
|
13
|
+
|
12
14
|
@target = target
|
13
15
|
@logger = Logging.logger[target.host]
|
14
16
|
end
|
data/lib/bolt/transport/local.rb
CHANGED
@@ -78,7 +78,11 @@ module Bolt
|
|
78
78
|
def run_command(target, command, _options = {})
|
79
79
|
in_tmpdir(target.options['tmpdir']) do |dir|
|
80
80
|
output = @conn.execute(command, dir: dir)
|
81
|
-
Bolt::Result.for_command(target,
|
81
|
+
Bolt::Result.for_command(target,
|
82
|
+
output.stdout.string,
|
83
|
+
output.stderr.string,
|
84
|
+
output.exit_code,
|
85
|
+
'command', command)
|
82
86
|
end
|
83
87
|
end
|
84
88
|
|
@@ -106,7 +110,11 @@ module Bolt
|
|
106
110
|
end
|
107
111
|
output = @conn.execute(file, *arguments, dir: dir)
|
108
112
|
end
|
109
|
-
Bolt::Result.for_command(target,
|
113
|
+
Bolt::Result.for_command(target,
|
114
|
+
output.stdout.string,
|
115
|
+
output.stderr.string,
|
116
|
+
output.exit_code,
|
117
|
+
'script', script)
|
110
118
|
end
|
111
119
|
end
|
112
120
|
|
@@ -181,7 +189,7 @@ module Bolt
|
|
181
189
|
env = ENVIRONMENT_METHODS.include?(input_method) ? envify_params(unwrapped_arguments) : nil
|
182
190
|
output = @conn.execute(script, stdin: stdin, env: env, dir: dir, interpreter: interpreter)
|
183
191
|
end
|
184
|
-
Bolt::Result.for_task(target, output.stdout.string, output.stderr.string, output.exit_code)
|
192
|
+
Bolt::Result.for_task(target, output.stdout.string, output.stderr.string, output.exit_code, task.name)
|
185
193
|
end
|
186
194
|
end
|
187
195
|
|
data/lib/bolt/transport/orch.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'base64'
|
4
|
-
require 'concurrent'
|
5
4
|
require 'find'
|
6
5
|
require 'json'
|
7
6
|
require 'minitar'
|
8
|
-
require 'orchestrator_client'
|
9
7
|
require 'pathname'
|
10
8
|
require 'zlib'
|
11
9
|
require 'bolt/transport/base'
|
@@ -40,6 +38,9 @@ module Bolt
|
|
40
38
|
def self.validate(options); end
|
41
39
|
|
42
40
|
def initialize(*args)
|
41
|
+
# lazy-load expensive gem code
|
42
|
+
require 'orchestrator_client'
|
43
|
+
|
43
44
|
@connections = {}
|
44
45
|
super
|
45
46
|
end
|
@@ -66,7 +67,7 @@ module Bolt
|
|
66
67
|
conn
|
67
68
|
end
|
68
69
|
|
69
|
-
def process_run_results(targets, results)
|
70
|
+
def process_run_results(targets, results, task_name)
|
70
71
|
targets_by_name = Hash[targets.map(&:host).zip(targets)]
|
71
72
|
results.map do |node_result|
|
72
73
|
target = targets_by_name[node_result['name']]
|
@@ -76,7 +77,7 @@ module Bolt
|
|
76
77
|
# If it's finished or already has a proper error simply pass it to the
|
77
78
|
# the result otherwise make sure an error is generated
|
78
79
|
if state == 'finished' || (result && result['_error'])
|
79
|
-
Bolt::Result.new(target, value: result)
|
80
|
+
Bolt::Result.new(target, value: result, type: 'task', object: task_name)
|
80
81
|
elsif state == 'skipped'
|
81
82
|
Bolt::Result.new(
|
82
83
|
target,
|
@@ -84,11 +85,12 @@ module Bolt
|
|
84
85
|
'kind' => 'puppetlabs.tasks/skipped-node',
|
85
86
|
'msg' => "Node #{target.host} was skipped",
|
86
87
|
'details' => {}
|
87
|
-
} }
|
88
|
+
} },
|
89
|
+
type: 'task', object: task_name
|
88
90
|
)
|
89
91
|
else
|
90
92
|
# Make a generic error with a unkown exit_code
|
91
|
-
Bolt::Result.for_task(target, result.to_json, '', 'unknown')
|
93
|
+
Bolt::Result.for_task(target, result.to_json, '', 'unknown', task_name)
|
92
94
|
end
|
93
95
|
end
|
94
96
|
end
|
@@ -103,7 +105,7 @@ module Bolt
|
|
103
105
|
options,
|
104
106
|
&callback)
|
105
107
|
callback ||= proc {}
|
106
|
-
results.map! { |result| unwrap_bolt_result(result.target, result) }
|
108
|
+
results.map! { |result| unwrap_bolt_result(result.target, result, 'command', command) }
|
107
109
|
results.each do |result|
|
108
110
|
callback.call(type: :node_result, result: result)
|
109
111
|
end
|
@@ -119,7 +121,7 @@ module Bolt
|
|
119
121
|
}
|
120
122
|
callback ||= proc {}
|
121
123
|
results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, &callback)
|
122
|
-
results.map! { |result| unwrap_bolt_result(result.target, result) }
|
124
|
+
results.map! { |result| unwrap_bolt_result(result.target, result, 'script', script) }
|
123
125
|
results.each do |result|
|
124
126
|
callback.call(type: :node_result, result: result)
|
125
127
|
end
|
@@ -198,7 +200,7 @@ module Bolt
|
|
198
200
|
arguments = unwrap_sensitive_args(arguments)
|
199
201
|
results = get_connection(targets.first.options).run_task(targets, task, arguments, options)
|
200
202
|
|
201
|
-
process_run_results(targets, results)
|
203
|
+
process_run_results(targets, results, task.name)
|
202
204
|
rescue OrchestratorClient::ApiError => e
|
203
205
|
targets.map do |target|
|
204
206
|
Bolt::Result.new(target, error: e.data)
|
@@ -226,13 +228,17 @@ module Bolt
|
|
226
228
|
# run_task generates a result that makes sense for a generic task which
|
227
229
|
# needs to be unwrapped to extract stdout/stderr/exitcode.
|
228
230
|
#
|
229
|
-
def unwrap_bolt_result(target, result)
|
231
|
+
def unwrap_bolt_result(target, result, type, obj)
|
230
232
|
if result.error_hash
|
231
233
|
# something went wrong return the failure
|
232
234
|
return result
|
233
235
|
end
|
234
236
|
|
235
|
-
Bolt::Result.for_command(target,
|
237
|
+
Bolt::Result.for_command(target,
|
238
|
+
result.value['stdout'],
|
239
|
+
result.value['stderr'],
|
240
|
+
result.value['exit_code'],
|
241
|
+
type, obj)
|
236
242
|
end
|
237
243
|
end
|
238
244
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'bolt/task
|
3
|
+
require 'bolt/task'
|
4
4
|
require 'bolt/transport/base'
|
5
5
|
|
6
6
|
module Bolt
|
@@ -47,7 +47,7 @@ module Bolt
|
|
47
47
|
transport = @executor.transport(proxy_target.protocol)
|
48
48
|
arguments = arguments.merge('_target' => target.to_h.reject { |_, v| v.nil? })
|
49
49
|
|
50
|
-
remote_task =
|
50
|
+
remote_task = task.remote_instance
|
51
51
|
|
52
52
|
result = transport.run_task(proxy_target, remote_task, arguments, options)
|
53
53
|
Bolt::Result.new(target, value: result.value)
|
data/lib/bolt/transport/ssh.rb
CHANGED
@@ -108,7 +108,11 @@ module Bolt
|
|
108
108
|
with_connection(target) do |conn|
|
109
109
|
conn.running_as(options['_run_as']) do
|
110
110
|
output = conn.execute(command, sudoable: true)
|
111
|
-
Bolt::Result.for_command(target,
|
111
|
+
Bolt::Result.for_command(target,
|
112
|
+
output.stdout.string,
|
113
|
+
output.stderr.string,
|
114
|
+
output.exit_code,
|
115
|
+
'command', command)
|
112
116
|
end
|
113
117
|
end
|
114
118
|
end
|
@@ -123,7 +127,11 @@ module Bolt
|
|
123
127
|
remote_path = conn.write_remote_executable(dir, script)
|
124
128
|
dir.chown(conn.run_as)
|
125
129
|
output = conn.execute([remote_path, *arguments], sudoable: true)
|
126
|
-
Bolt::Result.for_command(target,
|
130
|
+
Bolt::Result.for_command(target,
|
131
|
+
output.stdout.string,
|
132
|
+
output.stderr.string,
|
133
|
+
output.exit_code,
|
134
|
+
'script', script)
|
127
135
|
end
|
128
136
|
end
|
129
137
|
end
|
@@ -184,7 +192,8 @@ module Bolt
|
|
184
192
|
end
|
185
193
|
Bolt::Result.for_task(target, output.stdout.string,
|
186
194
|
output.stderr.string,
|
187
|
-
output.exit_code
|
195
|
+
output.exit_code,
|
196
|
+
task.name)
|
188
197
|
end
|
189
198
|
end
|
190
199
|
end
|