bolt 0.18.1 → 0.18.2
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/file_upload.rb +17 -1
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +15 -4
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +15 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +16 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +18 -4
- data/lib/bolt/cli.rb +92 -70
- data/lib/bolt/config.rb +11 -1
- data/lib/bolt/executor.rb +43 -34
- data/lib/bolt/inventory.rb +4 -0
- data/lib/bolt/pal.rb +15 -15
- data/lib/bolt/puppetdb/client.rb +3 -9
- data/lib/bolt/result.rb +5 -0
- data/lib/bolt/transport/orch.rb +25 -19
- data/lib/bolt/transport/winrm.rb +3 -2
- data/lib/bolt/util/on_access.rb +24 -0
- data/lib/bolt/version.rb +1 -1
- data/modules/aggregate/plans/count.pp +2 -2
- data/modules/aggregate/plans/nodes.pp +2 -2
- data/modules/facts/lib/puppet/functions/facts/group_by.rb +14 -0
- data/modules/facts/plans/info.pp +15 -0
- data/modules/facts/plans/init.pp +16 -0
- data/modules/facts/plans/retrieve.pp +32 -0
- data/modules/facts/tasks/bash.sh +74 -0
- data/modules/facts/tasks/powershell.ps1 +61 -0
- data/modules/facts/tasks/ruby.rb +18 -0
- data/modules/puppetdb_fact/plans/init.pp +1 -1
- metadata +10 -2
data/lib/bolt/config.rb
CHANGED
@@ -16,11 +16,17 @@ module Bolt
|
|
16
16
|
local: Bolt::Transport::Local
|
17
17
|
}.freeze
|
18
18
|
|
19
|
+
class UnknownTransportError < Bolt::Error
|
20
|
+
def initialize(transport, uri = nil)
|
21
|
+
msg = uri.nil? ? "Unknown transport #{transport}" : "Unknown transport #{transport} found for #{uri}"
|
22
|
+
super(msg, 'bolt/unknown-transport')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
19
26
|
Config = Struct.new(
|
20
27
|
:concurrency,
|
21
28
|
:format,
|
22
29
|
:inventoryfile,
|
23
|
-
:log_level,
|
24
30
|
:log,
|
25
31
|
:modulepath,
|
26
32
|
:puppetdb,
|
@@ -204,6 +210,10 @@ module Bolt
|
|
204
210
|
raise Bolt::CLIError, "Unsupported format: '#{self[:format]}'"
|
205
211
|
end
|
206
212
|
|
213
|
+
unless self[:transport].nil? || Bolt::TRANSPORTS.include?(self[:transport].to_sym)
|
214
|
+
raise UnknownTransportError, self[:transport]
|
215
|
+
end
|
216
|
+
|
207
217
|
TRANSPORTS.each do |transport, impl|
|
208
218
|
impl.validate(self[:transports][transport])
|
209
219
|
end
|
data/lib/bolt/executor.rb
CHANGED
@@ -19,16 +19,12 @@ module Bolt
|
|
19
19
|
def initialize(config = Bolt::Config.new, noop = nil, plan_logging = false)
|
20
20
|
@config = config
|
21
21
|
@logger = Logging.logger[self]
|
22
|
+
@plan_logging = plan_logging
|
22
23
|
|
23
24
|
@transports = Bolt::TRANSPORTS.each_with_object({}) do |(key, val), coll|
|
24
25
|
coll[key.to_s] = Concurrent::Delay.new { val.new }
|
25
26
|
end
|
26
27
|
|
27
|
-
# If a specific elevated log level has been requested, honor that.
|
28
|
-
# Otherwise, escalate the log level to "info" if running in plan mode, so
|
29
|
-
# that certain progress messages will be visible.
|
30
|
-
default_log_level = plan_logging ? :info : :notice
|
31
|
-
@logger.level = @config[:log_level] || default_log_level
|
32
28
|
@noop = noop
|
33
29
|
@run_as = nil
|
34
30
|
@pool = Concurrent::CachedThreadPool.new(max_threads: @config[:concurrency])
|
@@ -43,11 +39,11 @@ module Bolt
|
|
43
39
|
impl.value
|
44
40
|
end
|
45
41
|
|
46
|
-
def summary(
|
42
|
+
def summary(description, result)
|
47
43
|
fc = result.error_set.length
|
48
44
|
npl = result.length == 1 ? '' : 's'
|
49
45
|
fpl = fc == 1 ? '' : 's'
|
50
|
-
"
|
46
|
+
"Finished: #{description} on #{result.length} node#{npl} with #{fc} failure#{fpl}"
|
51
47
|
end
|
52
48
|
private :summary
|
53
49
|
|
@@ -97,75 +93,88 @@ module Bolt
|
|
97
93
|
ResultSet.new(promises.map(&:value))
|
98
94
|
end
|
99
95
|
|
96
|
+
# When running a plan, info messages like starting a task are promoted to notice.
|
97
|
+
def log_action(msg)
|
98
|
+
@plan_logging ? @logger.notice(msg) : @logger.info(msg)
|
99
|
+
end
|
100
|
+
private :log_action
|
101
|
+
|
102
|
+
def with_node_logging(description, batch)
|
103
|
+
@logger.info("#{description} on #{batch.map(&:uri)}")
|
104
|
+
result = yield
|
105
|
+
@logger.info(result.to_json)
|
106
|
+
result
|
107
|
+
end
|
108
|
+
private :with_node_logging
|
109
|
+
|
100
110
|
def run_command(targets, command, options = {}, &callback)
|
101
|
-
|
111
|
+
description = options.fetch('_description', "command '#{command}'")
|
112
|
+
log_action("Starting: #{description} on #{targets.map(&:uri)}")
|
102
113
|
notify = proc { |event| @notifier.notify(callback, event) if callback }
|
103
114
|
options = { '_run_as' => run_as }.merge(options) if run_as
|
104
115
|
|
105
116
|
results = batch_execute(targets) do |transport, batch|
|
106
|
-
|
117
|
+
with_node_logging("Running command '#{command}'", batch) do
|
118
|
+
transport.batch_command(batch, command, options, ¬ify)
|
119
|
+
end
|
107
120
|
end
|
108
121
|
|
109
|
-
|
122
|
+
log_action(summary(description, results))
|
110
123
|
@notifier.shutdown
|
111
124
|
results
|
112
125
|
end
|
113
126
|
|
114
127
|
def run_script(targets, script, arguments, options = {}, &callback)
|
115
|
-
|
116
|
-
|
128
|
+
description = options.fetch('_description', "script #{script}")
|
129
|
+
log_action("Starting: #{description} on #{targets.map(&:uri)}")
|
130
|
+
|
117
131
|
notify = proc { |event| @notifier.notify(callback, event) if callback }
|
118
132
|
options = { '_run_as' => run_as }.merge(options) if run_as
|
119
133
|
|
120
134
|
results = batch_execute(targets) do |transport, batch|
|
121
|
-
|
135
|
+
with_node_logging("Running script #{script} with '#{arguments}'", batch) do
|
136
|
+
transport.batch_script(batch, script, arguments, options, ¬ify)
|
137
|
+
end
|
122
138
|
end
|
123
139
|
|
124
|
-
|
140
|
+
log_action(summary(description, results))
|
125
141
|
@notifier.shutdown
|
126
142
|
results
|
127
143
|
end
|
128
144
|
|
129
145
|
def run_task(targets, task, arguments, options = {}, &callback)
|
130
|
-
|
131
|
-
|
132
|
-
|
146
|
+
description = options.fetch('_description', "task #{task.name}")
|
147
|
+
log_action("Starting: #{description} on #{targets.map(&:uri)}")
|
148
|
+
|
133
149
|
notify = proc { |event| @notifier.notify(callback, event) if callback }
|
134
150
|
options = { '_run_as' => run_as }.merge(options) if run_as
|
135
151
|
|
136
152
|
results = batch_execute(targets) do |transport, batch|
|
137
|
-
|
153
|
+
with_node_logging("Running task #{task.name} with '#{arguments}' via #{task.input_method}", batch) do
|
154
|
+
transport.batch_task(batch, task, arguments, options, ¬ify)
|
155
|
+
end
|
138
156
|
end
|
139
157
|
|
140
|
-
|
158
|
+
log_action(summary(description, results))
|
141
159
|
@notifier.shutdown
|
142
160
|
results
|
143
161
|
end
|
144
162
|
|
145
163
|
def file_upload(targets, source, destination, options = {}, &callback)
|
146
|
-
|
164
|
+
description = options.fetch('_description', "file upload from #{source} to #{destination}")
|
165
|
+
log_action("Starting: #{description} on #{targets.map(&:uri)}")
|
147
166
|
notify = proc { |event| @notifier.notify(callback, event) if callback }
|
148
167
|
options = { '_run_as' => run_as }.merge(options) if run_as
|
149
168
|
|
150
169
|
results = batch_execute(targets) do |transport, batch|
|
151
|
-
|
170
|
+
with_node_logging("Uploading file #{source} to #{destination}", batch) do
|
171
|
+
transport.batch_upload(batch, source, destination, options, ¬ify)
|
172
|
+
end
|
152
173
|
end
|
153
174
|
|
154
|
-
|
175
|
+
log_action(summary(description, results))
|
155
176
|
@notifier.shutdown
|
156
177
|
results
|
157
178
|
end
|
158
|
-
|
159
|
-
def puppetdb_client
|
160
|
-
return @puppetdb_client if @puppetdb_client
|
161
|
-
puppetdb_config = Bolt::PuppetDB::Config.new(nil, @config.puppetdb)
|
162
|
-
@puppetdb_client = Bolt::PuppetDB::Client.from_config(puppetdb_config)
|
163
|
-
end
|
164
|
-
|
165
|
-
def puppetdb_fact(certnames)
|
166
|
-
puppetdb_client.facts_for_node(certnames)
|
167
|
-
rescue StandardError => e
|
168
|
-
raise Bolt::CLIError, "Could not retrieve targets from PuppetDB: #{e}"
|
169
|
-
end
|
170
179
|
end
|
171
180
|
end
|
data/lib/bolt/inventory.rb
CHANGED
@@ -138,6 +138,10 @@ module Bolt
|
|
138
138
|
conf.update_from_inventory(data['config'])
|
139
139
|
conf.validate
|
140
140
|
|
141
|
+
unless target.protocol.nil? || Bolt::TRANSPORTS.include?(target.protocol.to_sym)
|
142
|
+
raise Bolt::UnknownTransportError.new(target.protocol, target.uri)
|
143
|
+
end
|
144
|
+
|
141
145
|
target.update_conf(conf.transport_conf)
|
142
146
|
end
|
143
147
|
private :update_target
|
data/lib/bolt/pal.rb
CHANGED
@@ -12,14 +12,15 @@ module Bolt
|
|
12
12
|
# Nothing works without initialized this global state. Reinitializing
|
13
13
|
# is safe and in practice only happen in tests
|
14
14
|
self.class.load_puppet
|
15
|
-
self.class.configure_logging
|
15
|
+
self.class.configure_logging
|
16
|
+
# This makes sure we don't accidentally create puppet dirs
|
17
|
+
with_puppet_settings { |_| nil }
|
16
18
|
|
17
19
|
@config = config
|
18
20
|
end
|
19
21
|
|
20
22
|
# Puppet logging is global so this is class method to avoid confusion
|
21
|
-
def self.configure_logging
|
22
|
-
Puppet[:log_level] = log_level == :debug ? 'debug' : 'notice'
|
23
|
+
def self.configure_logging
|
23
24
|
Puppet::Util::Log.newdestination(:console)
|
24
25
|
end
|
25
26
|
|
@@ -40,10 +41,6 @@ module Bolt
|
|
40
41
|
|
41
42
|
# Now that puppet is loaded we can include puppet mixins in data types
|
42
43
|
Bolt::ResultSet.include_iterable
|
43
|
-
|
44
|
-
unless Puppet.settings.global_defaults_initialized?
|
45
|
-
Puppet.initialize_settings
|
46
|
-
end
|
47
44
|
end
|
48
45
|
|
49
46
|
# Create a top-level alias for TargetSpec so that users don't have to
|
@@ -98,12 +95,15 @@ module Bolt
|
|
98
95
|
r
|
99
96
|
end
|
100
97
|
|
101
|
-
def with_bolt_executor(executor, inventory, &block)
|
102
|
-
Puppet.override({ bolt_executor: executor, bolt_inventory: inventory }, &block)
|
98
|
+
def with_bolt_executor(executor, inventory, pdb_client = nil, &block)
|
99
|
+
Puppet.override({ bolt_executor: executor, bolt_inventory: inventory, bolt_pdb_client: pdb_client }, &block)
|
103
100
|
end
|
104
101
|
|
105
|
-
def in_plan_compiler(executor, inventory)
|
106
|
-
with_bolt_executor(executor, inventory) do
|
102
|
+
def in_plan_compiler(executor, inventory, pdb_client)
|
103
|
+
with_bolt_executor(executor, inventory, pdb_client) do
|
104
|
+
# TODO: remove this call and see if anything breaks when
|
105
|
+
# settings dirs don't actually exist. Plans shouldn't
|
106
|
+
# actually be using them.
|
107
107
|
with_puppet_settings do
|
108
108
|
in_bolt_compiler do |compiler|
|
109
109
|
yield compiler
|
@@ -219,14 +219,14 @@ module Bolt
|
|
219
219
|
plan_info
|
220
220
|
end
|
221
221
|
|
222
|
-
def run_task(task_name, targets, params, executor, inventory, &eventblock)
|
222
|
+
def run_task(task_name, targets, params, executor, inventory, description = nil, &eventblock)
|
223
223
|
in_task_compiler(executor, inventory) do |compiler|
|
224
|
-
compiler.call_function('run_task', task_name, targets, params, &eventblock)
|
224
|
+
compiler.call_function('run_task', task_name, targets, description, params, &eventblock)
|
225
225
|
end
|
226
226
|
end
|
227
227
|
|
228
|
-
def run_plan(plan_name, params, executor = nil, inventory = nil)
|
229
|
-
in_plan_compiler(executor, inventory) do |compiler|
|
228
|
+
def run_plan(plan_name, params, executor = nil, inventory = nil, pdb_client = nil)
|
229
|
+
in_plan_compiler(executor, inventory, pdb_client) do |compiler|
|
230
230
|
r = compiler.call_function('run_plan', plan_name, params)
|
231
231
|
Bolt::PuppetError.convert_puppet_errors(r)
|
232
232
|
end
|
data/lib/bolt/puppetdb/client.rb
CHANGED
@@ -55,27 +55,21 @@ module Bolt
|
|
55
55
|
def facts_for_node(certnames)
|
56
56
|
return {} if certnames.empty? || certnames.nil?
|
57
57
|
|
58
|
-
# This inits a hash of {certname1 => {}, certname2 => {}} etc.
|
59
|
-
output = Hash[certnames.product([{}])]
|
60
|
-
|
61
58
|
certnames.uniq!
|
62
59
|
name_query = certnames.map { |c| ["=", "certname", c] }
|
63
60
|
name_query.insert(0, "or")
|
64
61
|
body = JSON.generate(query: name_query)
|
65
62
|
|
66
|
-
response = http_client.post("#{@uri}/pdb/query/v4/
|
63
|
+
response = http_client.post("#{@uri}/pdb/query/v4/inventory", body: body, header: headers)
|
67
64
|
if response.code != 200
|
68
65
|
raise Bolt::PuppetDBError, "Failed to query PuppetDB: #{response.body}"
|
69
66
|
else
|
70
67
|
parsed = JSON.parse(response.body)
|
71
68
|
end
|
72
69
|
|
73
|
-
|
74
|
-
|
75
|
-
output[f['certname']][f['name']] = f['value']
|
70
|
+
parsed&.each_with_object({}) do |node, coll|
|
71
|
+
coll[node['certname']] = node['facts']
|
76
72
|
end
|
77
|
-
|
78
|
-
output
|
79
73
|
end
|
80
74
|
|
81
75
|
def http_client
|
data/lib/bolt/result.rb
CHANGED
@@ -68,6 +68,11 @@ module Bolt
|
|
68
68
|
new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'")
|
69
69
|
end
|
70
70
|
|
71
|
+
# Satisfies the Puppet datatypes API
|
72
|
+
def self.from_asserted_args(target, value)
|
73
|
+
new(target, value: value)
|
74
|
+
end
|
75
|
+
|
71
76
|
def initialize(target, error: nil, message: nil, value: nil)
|
72
77
|
@target = target
|
73
78
|
@value = value || {}
|
data/lib/bolt/transport/orch.rb
CHANGED
@@ -38,14 +38,16 @@ module Bolt
|
|
38
38
|
OrchestratorClient.new(client_opts, true)
|
39
39
|
end
|
40
40
|
|
41
|
-
def build_request(targets, task, arguments)
|
42
|
-
{ task: task.name,
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
41
|
+
def build_request(targets, task, arguments, description = nil)
|
42
|
+
body = { task: task.name,
|
43
|
+
environment: targets.first.options["task-environment"],
|
44
|
+
noop: arguments['_noop'],
|
45
|
+
params: arguments.reject { |k, _| k == '_noop' },
|
46
|
+
scope: {
|
47
|
+
nodes: targets.map(&:host)
|
48
|
+
} }
|
49
|
+
body[:description] = description if description
|
50
|
+
body
|
49
51
|
end
|
50
52
|
|
51
53
|
def process_run_results(targets, results)
|
@@ -75,11 +77,15 @@ module Bolt
|
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
78
|
-
def batch_command(targets, command,
|
80
|
+
def batch_command(targets, command, options = {}, &callback)
|
81
|
+
params = {
|
82
|
+
action: 'command',
|
83
|
+
command: command
|
84
|
+
}
|
79
85
|
results = run_task_job(targets,
|
80
86
|
BOLT_MOCK_TASK,
|
81
|
-
|
82
|
-
|
87
|
+
params,
|
88
|
+
options,
|
83
89
|
&callback)
|
84
90
|
callback ||= proc {}
|
85
91
|
results.map! { |result| unwrap_bolt_result(result.target, result) }
|
@@ -88,7 +94,7 @@ module Bolt
|
|
88
94
|
end
|
89
95
|
end
|
90
96
|
|
91
|
-
def batch_script(targets, script, arguments,
|
97
|
+
def batch_script(targets, script, arguments, options = {}, &callback)
|
92
98
|
content = File.open(script, &:read)
|
93
99
|
content = Base64.encode64(content)
|
94
100
|
params = {
|
@@ -97,14 +103,14 @@ module Bolt
|
|
97
103
|
arguments: arguments
|
98
104
|
}
|
99
105
|
callback ||= proc {}
|
100
|
-
results = run_task_job(targets, BOLT_MOCK_TASK, params, &callback)
|
106
|
+
results = run_task_job(targets, BOLT_MOCK_TASK, params, options, &callback)
|
101
107
|
results.map! { |result| unwrap_bolt_result(result.target, result) }
|
102
108
|
results.each do |result|
|
103
109
|
callback.call(type: :node_result, result: result)
|
104
110
|
end
|
105
111
|
end
|
106
112
|
|
107
|
-
def batch_upload(targets, source, destination,
|
113
|
+
def batch_upload(targets, source, destination, options = {}, &callback)
|
108
114
|
content = File.open(source, &:read)
|
109
115
|
content = Base64.encode64(content)
|
110
116
|
mode = File.stat(source).mode
|
@@ -115,7 +121,7 @@ module Bolt
|
|
115
121
|
mode: mode
|
116
122
|
}
|
117
123
|
callback ||= proc {}
|
118
|
-
results = run_task_job(targets, BOLT_MOCK_TASK, params, &callback)
|
124
|
+
results = run_task_job(targets, BOLT_MOCK_TASK, params, options, &callback)
|
119
125
|
results.map! do |result|
|
120
126
|
if result.error_hash
|
121
127
|
result
|
@@ -136,8 +142,8 @@ module Bolt
|
|
136
142
|
end.values
|
137
143
|
end
|
138
144
|
|
139
|
-
def run_task_job(targets, task, arguments)
|
140
|
-
body = build_request(targets, task, arguments)
|
145
|
+
def run_task_job(targets, task, arguments, options)
|
146
|
+
body = build_request(targets, task, arguments, options['_description'])
|
141
147
|
|
142
148
|
targets.each do |target|
|
143
149
|
yield(type: :node_start, target: target) if block_given?
|
@@ -158,9 +164,9 @@ module Bolt
|
|
158
164
|
end
|
159
165
|
end
|
160
166
|
|
161
|
-
def batch_task(targets, task, arguments,
|
167
|
+
def batch_task(targets, task, arguments, options = {}, &callback)
|
162
168
|
callback ||= proc {}
|
163
|
-
results = run_task_job(targets, task, arguments, &callback)
|
169
|
+
results = run_task_job(targets, task, arguments, options, &callback)
|
164
170
|
results.each do |result|
|
165
171
|
callback.call(type: :node_result, result: result)
|
166
172
|
end
|
data/lib/bolt/transport/winrm.rb
CHANGED
@@ -83,6 +83,7 @@ try
|
|
83
83
|
}
|
84
84
|
catch
|
85
85
|
{
|
86
|
+
Write-Error $_.Exception
|
86
87
|
exit 1
|
87
88
|
}
|
88
89
|
PS
|
@@ -124,10 +125,10 @@ catch
|
|
124
125
|
$private:taskArgs = Get-ContentAsJson (
|
125
126
|
$utf8.GetString([System.Convert]::FromBase64String('#{Base64.encode64(JSON.dump(arguments))}'))
|
126
127
|
)
|
127
|
-
try { & "#{remote_path}" @taskArgs } catch { exit 1 }
|
128
|
+
try { & "#{remote_path}" @taskArgs } catch { Write-Error $_.Exception; exit 1 }
|
128
129
|
PS
|
129
130
|
else
|
130
|
-
conn.execute(%(try { & "#{remote_path}" } catch { exit 1 }))
|
131
|
+
conn.execute(%(try { & "#{remote_path}" } catch { Write-Error $_.Exception; exit 1 }))
|
131
132
|
end
|
132
133
|
else
|
133
134
|
path, args = *process_from_extension(remote_path)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
module Util
|
5
|
+
class OnAccess
|
6
|
+
def initialize(&block)
|
7
|
+
@constructor = block
|
8
|
+
@obj = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
# If a method is called and we haven't constructed the object,
|
12
|
+
# construct it. Then pass the call to the object.
|
13
|
+
# rubocop:disable Style/MethodMissing
|
14
|
+
def method_missing(method, *args, &block)
|
15
|
+
if @obj.nil?
|
16
|
+
@obj = @constructor.call
|
17
|
+
end
|
18
|
+
|
19
|
+
@obj.send(method, *args, &block)
|
20
|
+
end
|
21
|
+
# rubocop:enable Style/MethodMissing
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/bolt/version.rb
CHANGED