bolt 0.14.0 → 0.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/exe/bolt +3 -2
- data/lib/bolt/cli.rb +99 -92
- data/lib/bolt/config.rb +7 -8
- data/lib/bolt/error.rb +20 -3
- data/lib/bolt/executor.rb +44 -45
- data/lib/bolt/logger.rb +28 -52
- data/lib/bolt/node.rb +12 -41
- data/lib/bolt/node/orch.rb +28 -34
- data/lib/bolt/node/ssh.rb +26 -21
- data/lib/bolt/node/winrm.rb +41 -19
- data/lib/bolt/notifier.rb +2 -2
- data/lib/bolt/outputter/human.rb +34 -29
- data/lib/bolt/outputter/json.rb +4 -10
- data/lib/bolt/pal.rb +106 -0
- data/lib/bolt/result.rb +90 -87
- data/lib/bolt/result_set.rb +94 -0
- data/lib/bolt/target.rb +61 -13
- data/lib/bolt/version.rb +1 -1
- data/modules/boltlib/lib/puppet/datatypes/result.rb +18 -0
- data/modules/boltlib/lib/puppet/datatypes/resultset.rb +22 -0
- data/modules/boltlib/lib/puppet/datatypes/target.rb +4 -1
- data/modules/boltlib/lib/puppet/functions/file_upload.rb +15 -10
- data/modules/boltlib/lib/puppet/functions/run_command.rb +15 -8
- data/modules/boltlib/lib/puppet/functions/run_script.rb +15 -26
- data/modules/boltlib/lib/puppet/functions/run_task.rb +17 -15
- metadata +23 -9
- data/lib/bolt/execution_result.rb +0 -109
- data/lib/bolt/formatter.rb +0 -9
- data/lib/bolt/node_uri.rb +0 -42
- data/modules/boltlib/lib/puppet/datatypes/executionresult.rb +0 -30
data/lib/bolt/result.rb
CHANGED
@@ -3,9 +3,9 @@ require 'bolt/error'
|
|
3
3
|
|
4
4
|
module Bolt
|
5
5
|
class Result
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :target, :value
|
7
7
|
|
8
|
-
def self.from_exception(exception)
|
8
|
+
def self.from_exception(target, exception)
|
9
9
|
@exception = exception
|
10
10
|
if @exception.is_a?(Bolt::Error)
|
11
11
|
error = @exception.to_h
|
@@ -18,121 +18,124 @@ module Bolt
|
|
18
18
|
}
|
19
19
|
error['details']['stack_trace'] = exception.backtrace.join('\n') if exception.backtrace
|
20
20
|
end
|
21
|
-
Result.new(error)
|
21
|
+
Result.new(target, error: error)
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
24
|
+
def self.for_command(target, stdout, stderr, exit_code)
|
25
|
+
value = {
|
26
|
+
'stdout' => stdout,
|
27
|
+
'stderr' => stderr,
|
28
|
+
'exit_code' => exit_code
|
29
|
+
}
|
30
|
+
unless exit_code == 0
|
31
|
+
value['_error'] = {
|
32
|
+
'kind' => 'puppetlabs.tasks/command-error',
|
33
|
+
'issue_code' => 'COMMAND_ERROR',
|
34
|
+
'msg' => "The command failed with exit code #{exit_code}",
|
35
|
+
'details' => { 'exit_code' => exit_code }
|
36
|
+
}
|
37
|
+
end
|
38
|
+
new(target, value: value)
|
27
39
|
end
|
28
40
|
|
29
|
-
def
|
30
|
-
|
41
|
+
def self.for_task(target, stdout, stderr, exit_code)
|
42
|
+
begin
|
43
|
+
value = JSON.parse(stdout)
|
44
|
+
unless value.is_a? Hash
|
45
|
+
value = nil
|
46
|
+
end
|
47
|
+
rescue JSON::ParserError
|
48
|
+
value = nil
|
49
|
+
end
|
50
|
+
value ||= { '_output' => stdout }
|
51
|
+
if exit_code != 0 && value['_error'].nil?
|
52
|
+
msg = if stdout.empty?
|
53
|
+
"The task failed with exit code #{exit_code}:\n#{stderr}"
|
54
|
+
else
|
55
|
+
"The task failed with exit code #{exit_code}"
|
56
|
+
end
|
57
|
+
value['_error'] = { 'kind' => 'puppetlabs.tasks/task-error',
|
58
|
+
'issue_code' => 'TASK_ERROR',
|
59
|
+
'msg' => msg,
|
60
|
+
'details' => { 'exit_code' => exit_code } }
|
61
|
+
end
|
62
|
+
new(target, value: value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.for_upload(target, source, destination)
|
66
|
+
new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'")
|
31
67
|
end
|
32
68
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
h['value'] = { '_output' => message }
|
69
|
+
def initialize(target, error: nil, message: nil, value: nil)
|
70
|
+
@target = target
|
71
|
+
@value = value || {}
|
72
|
+
@value_set = !value.nil?
|
73
|
+
if error && !error.is_a?(Hash)
|
74
|
+
raise "TODO: how did we get a string error"
|
40
75
|
end
|
41
|
-
|
42
|
-
|
76
|
+
@value['_error'] = error if error
|
77
|
+
@value['_output'] = message if message
|
43
78
|
end
|
44
79
|
|
45
|
-
def
|
46
|
-
|
47
|
-
# use this hash instead
|
48
|
-
special_keys = {}
|
49
|
-
special_keys['_error'] = error if error
|
50
|
-
special_keys['_output'] = message if message
|
51
|
-
val = value || {}
|
52
|
-
val.merge(special_keys)
|
80
|
+
def message
|
81
|
+
@value['_output']
|
53
82
|
end
|
54
83
|
|
55
|
-
def
|
56
|
-
|
84
|
+
def status_hash
|
85
|
+
{ node: @target.name,
|
86
|
+
status: ok? ? 'success' : 'failure',
|
87
|
+
result: @value }
|
57
88
|
end
|
58
|
-
end
|
59
89
|
|
60
|
-
|
61
|
-
|
90
|
+
# TODO: what to call this it's the value minus special keys
|
91
|
+
# This should be {} if a value was set otherwise it's nil
|
92
|
+
def generic_value
|
93
|
+
if @value_set
|
94
|
+
value.reject { |k, _| %w[_error _output].include? k }
|
95
|
+
end
|
96
|
+
end
|
62
97
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
98
|
+
def eql?(other)
|
99
|
+
self.class == other.class &&
|
100
|
+
target == other.target &&
|
101
|
+
value == other.value
|
67
102
|
end
|
68
103
|
|
69
|
-
def
|
70
|
-
|
71
|
-
@stderr = stderr || ""
|
72
|
-
@exit_code = exit_code
|
104
|
+
def [](key)
|
105
|
+
value[key]
|
73
106
|
end
|
74
107
|
|
75
|
-
def
|
76
|
-
|
77
|
-
'stdout' => @stdout,
|
78
|
-
'stderr' => @stderr,
|
79
|
-
'exit_code' => @exit_code
|
80
|
-
}
|
108
|
+
def ==(other)
|
109
|
+
eql?(other)
|
81
110
|
end
|
82
111
|
|
112
|
+
# TODO: remove in favor of ok?
|
83
113
|
def success?
|
84
|
-
|
114
|
+
ok?
|
85
115
|
end
|
86
116
|
|
87
|
-
def
|
88
|
-
|
89
|
-
{
|
90
|
-
'kind' => 'puppetlabs.tasks/command-error',
|
91
|
-
'issue_code' => 'COMMAND_ERROR',
|
92
|
-
'msg' => "The command failed with exit code #{@exit_code}",
|
93
|
-
'details' => { 'exit_code' => @exit_code }
|
94
|
-
}
|
95
|
-
end
|
117
|
+
def ok?
|
118
|
+
error_hash.nil?
|
96
119
|
end
|
97
|
-
|
98
|
-
|
99
|
-
class TaskResult < CommandResult
|
100
|
-
attr_reader :value
|
120
|
+
alias ok ok?
|
101
121
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
@error = @value.delete('_error')
|
122
|
+
# This allows access to errors outside puppet compilation
|
123
|
+
# it should be prefered over error in bolt code
|
124
|
+
def error_hash
|
125
|
+
value['_error']
|
107
126
|
end
|
108
127
|
|
128
|
+
# Warning: This will fail outside of a compilation.
|
129
|
+
# Use error_hash inside bolt.
|
130
|
+
# Is it crazy for this to behave differently outside a compiler?
|
109
131
|
def error
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
"The task failed with exit code #{@exit_code}:\n#{@stderr}"
|
116
|
-
end
|
117
|
-
{ 'kind' => 'puppetlabs.tasks/task-error',
|
118
|
-
'issue_code' => 'TASK_ERROR',
|
119
|
-
'msg' => msg,
|
120
|
-
'details' => { 'exit_code' => @exit_code } }
|
121
|
-
end
|
122
|
-
end
|
132
|
+
if error_hash
|
133
|
+
Puppet::DataTypes::Error.new(error_hash['msg'],
|
134
|
+
error_hash['kind'],
|
135
|
+
nil, nil,
|
136
|
+
error_hash['details'])
|
123
137
|
|
124
|
-
private
|
125
|
-
|
126
|
-
def parse_output(output)
|
127
|
-
begin
|
128
|
-
obj = JSON.parse(output)
|
129
|
-
unless obj.is_a? Hash
|
130
|
-
obj = nil
|
131
|
-
end
|
132
|
-
rescue JSON::ParserError
|
133
|
-
obj = nil
|
134
138
|
end
|
135
|
-
obj || { '_output' => output }
|
136
139
|
end
|
137
140
|
end
|
138
141
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Bolt
|
2
|
+
class ResultSet
|
3
|
+
attr_reader :results
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
# We only want want to include these when puppet is loaded
|
8
|
+
def self.include_iterable
|
9
|
+
include(Puppet::Pops::Types::Iterable)
|
10
|
+
include(Puppet::Pops::Types::IteratorProducer)
|
11
|
+
end
|
12
|
+
|
13
|
+
def iterator
|
14
|
+
if Object.const_defined?(:Puppet) && Puppet.const_defined?(:Pops) &&
|
15
|
+
self.class.included_modules.include?(Puppet::Pops::Types::Iterable)
|
16
|
+
Puppet::Pops::Types::Iterable.on(@results, Bolt::Result)
|
17
|
+
else
|
18
|
+
raise NotImplementedError, "iterator requires puppet code to be loaded."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(results)
|
23
|
+
@results = results
|
24
|
+
end
|
25
|
+
|
26
|
+
def each
|
27
|
+
@results.each { |r| yield r }
|
28
|
+
end
|
29
|
+
|
30
|
+
def result_hash
|
31
|
+
@result_hash ||= @results.each_with_object({}) do |result, acc|
|
32
|
+
acc[result.target.name] = result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def count
|
37
|
+
@results.size
|
38
|
+
end
|
39
|
+
alias length count
|
40
|
+
alias size count
|
41
|
+
|
42
|
+
def empty
|
43
|
+
@results.empty?
|
44
|
+
end
|
45
|
+
alias empty? empty
|
46
|
+
|
47
|
+
def targets
|
48
|
+
results.map(&:target)
|
49
|
+
end
|
50
|
+
|
51
|
+
def names
|
52
|
+
@results.map { |r| r.target.name }
|
53
|
+
end
|
54
|
+
|
55
|
+
def ok
|
56
|
+
@results.all?(&:ok?)
|
57
|
+
end
|
58
|
+
alias ok? ok
|
59
|
+
|
60
|
+
def error_set
|
61
|
+
filtered = @results.reject(&:ok?)
|
62
|
+
ResultSet.new(filtered)
|
63
|
+
end
|
64
|
+
|
65
|
+
def ok_set
|
66
|
+
filtered = @results.select(&:success?)
|
67
|
+
self.class.new(filtered)
|
68
|
+
end
|
69
|
+
|
70
|
+
def find(target_name)
|
71
|
+
result_hash[target_name]
|
72
|
+
end
|
73
|
+
|
74
|
+
def first
|
75
|
+
@results.first
|
76
|
+
end
|
77
|
+
|
78
|
+
def eql?(other)
|
79
|
+
self.class == other.class && @results == other.results
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_json(opts = nil)
|
83
|
+
@results.map(&:status_hash).to_json(opts)
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
to_json
|
88
|
+
end
|
89
|
+
|
90
|
+
def ==(other)
|
91
|
+
eql?(other)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/bolt/target.rb
CHANGED
@@ -1,32 +1,80 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
require 'bolt/error'
|
3
|
+
|
1
4
|
module Bolt
|
2
5
|
class Target
|
3
|
-
attr_reader :
|
6
|
+
attr_reader :uri, :options
|
4
7
|
|
8
|
+
# Satisfies the Puppet datatypes API
|
5
9
|
def self.from_asserted_hash(hash)
|
6
|
-
new(hash['
|
10
|
+
new(hash['uri'], hash['options'])
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_uri(uri)
|
14
|
+
new(uri)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.parse_urls(urls)
|
18
|
+
urls.split(/[[:space:],]+/).reject(&:empty?).uniq.map { |url| from_uri(url) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(uri, options = nil)
|
22
|
+
@uri = uri
|
23
|
+
@uri_obj = parse(uri)
|
24
|
+
@options = options || {}
|
7
25
|
end
|
8
26
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
27
|
+
def parse(string)
|
28
|
+
if string =~ %r{^[^:]+://}
|
29
|
+
Addressable::URI.parse(string)
|
30
|
+
else
|
31
|
+
# Initialize with an empty scheme to ensure we parse the hostname correctly
|
32
|
+
Addressable::URI.parse("//#{string}")
|
33
|
+
end
|
12
34
|
end
|
35
|
+
private :parse
|
13
36
|
|
14
37
|
def eql?(other)
|
15
|
-
self.class.equal?(other.class) && @
|
38
|
+
self.class.equal?(other.class) && @uri == other.uri && @options == other.options
|
16
39
|
end
|
17
40
|
alias == eql?
|
18
41
|
|
19
42
|
def hash
|
20
|
-
@
|
43
|
+
@uri.hash ^ @options.hash
|
21
44
|
end
|
22
45
|
|
23
46
|
def to_s
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
47
|
+
"Target('#{@uri}', #{@options})"
|
48
|
+
end
|
49
|
+
|
50
|
+
def host
|
51
|
+
@uri_obj.hostname
|
52
|
+
end
|
53
|
+
|
54
|
+
def port
|
55
|
+
@uri_obj.port
|
56
|
+
end
|
57
|
+
|
58
|
+
# name is currently just uri but should be be used instead to identify the
|
59
|
+
# Target ouside the transport or uri options.
|
60
|
+
def name
|
61
|
+
uri
|
62
|
+
end
|
63
|
+
|
64
|
+
def user
|
65
|
+
Addressable::URI.unencode_component(
|
66
|
+
@uri_obj.user
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def password
|
71
|
+
Addressable::URI.unencode_component(
|
72
|
+
@uri_obj.password
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def protocol
|
77
|
+
@uri_obj.scheme
|
30
78
|
end
|
31
79
|
end
|
32
80
|
end
|
data/lib/bolt/version.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
Puppet::DataTypes.create_type('Result') do
|
2
|
+
interface <<-PUPPET
|
3
|
+
attributes => {
|
4
|
+
'value' => Hash[String[1], Data],
|
5
|
+
'target' => Target
|
6
|
+
},
|
7
|
+
functions => {
|
8
|
+
error => Callable[[], Optional[Error]],
|
9
|
+
message => Callable[[], Optional[String]],
|
10
|
+
ok => Callable[[], Boolean],
|
11
|
+
'[]' => Callable[[String[1]], Data]
|
12
|
+
}
|
13
|
+
PUPPET
|
14
|
+
|
15
|
+
load_file('bolt/result')
|
16
|
+
|
17
|
+
implementation_class Bolt::Result
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Puppet::DataTypes.create_type('ResultSet') do
|
2
|
+
interface <<-PUPPET
|
3
|
+
attributes => {
|
4
|
+
'results' => Array[Result],
|
5
|
+
},
|
6
|
+
functions => {
|
7
|
+
count => Callable[[], Integer],
|
8
|
+
empty => Callable[[], Boolean],
|
9
|
+
error_set => Callable[[], ResultSet],
|
10
|
+
find => Callable[[String[1]], Optional[Result]],
|
11
|
+
first => Callable[[], Optional[Result]],
|
12
|
+
names => Callable[[], Array[String[1]]],
|
13
|
+
ok => Callable[[], Boolean],
|
14
|
+
ok_set => Callable[[], ResultSet],
|
15
|
+
targets => Callable[[], Array[Target]],
|
16
|
+
}
|
17
|
+
PUPPET
|
18
|
+
|
19
|
+
load_file('bolt/result_set')
|
20
|
+
|
21
|
+
implementation_class Bolt::ResultSet
|
22
|
+
end
|