bolt 1.19.0 → 1.20.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.

Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +2 -0
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +1 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +3 -10
  6. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +3 -10
  7. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +3 -9
  8. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +2 -8
  9. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +2 -2
  10. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +3 -8
  11. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +2 -8
  12. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +2 -7
  13. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -7
  14. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +2 -7
  15. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +1 -6
  16. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +2 -7
  17. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +7 -33
  18. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +3 -10
  19. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +3 -10
  20. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +2 -7
  21. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +2 -8
  22. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +2 -7
  23. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -1
  24. data/lib/bolt/applicator.rb +7 -3
  25. data/lib/bolt/apply_result.rb +8 -21
  26. data/lib/bolt/bolt_option_parser.rb +2 -2
  27. data/lib/bolt/catalog.rb +0 -1
  28. data/lib/bolt/cli.rb +51 -29
  29. data/lib/bolt/config.rb +0 -2
  30. data/lib/bolt/executor.rb +38 -50
  31. data/lib/bolt/inventory/group.rb +5 -0
  32. data/lib/bolt/inventory/group2.rb +5 -0
  33. data/lib/bolt/logger.rb +7 -3
  34. data/lib/bolt/outputter.rb +6 -4
  35. data/lib/bolt/outputter/human.rb +90 -6
  36. data/lib/bolt/outputter/json.rb +4 -4
  37. data/lib/bolt/outputter/logger.rb +53 -0
  38. data/lib/bolt/pal.rb +3 -3
  39. data/lib/bolt/pal/yaml_plan/step.rb +1 -1
  40. data/lib/bolt/plugin.rb +2 -0
  41. data/lib/bolt/plugin/terraform.rb +84 -0
  42. data/lib/bolt/result.rb +12 -8
  43. data/lib/bolt/result_set.rb +4 -0
  44. data/lib/bolt/transport/orch.rb +4 -4
  45. data/lib/bolt/version.rb +1 -1
  46. metadata +4 -3
  47. data/lib/bolt/notifier.rb +0 -23
@@ -65,7 +65,7 @@ module Bolt
65
65
  def initialize(target, error: nil, report: nil)
66
66
  @target = target
67
67
  @value = {}
68
- @type = 'apply'
68
+ @action = 'apply'
69
69
  value['report'] = report if report
70
70
  value['_error'] = error if error
71
71
  value['_output'] = metrics_message if metrics_message
@@ -77,26 +77,13 @@ module Bolt
77
77
  end
78
78
  end
79
79
 
80
- # TODO: We've gotten requests for this type of logging but I'm not sure
81
- # what we shold do with it exactly.
82
- def log_events
83
- logger = Logging.logger[target.name]
84
- if (logs = value.dig('report', 'logs'))
85
- logs.each do |log|
86
- case log["level"]
87
- when 'err'
88
- logger.error(log['message'])
89
- when 'warn'
90
- logger.info(log['message'])
91
- when 'notice'
92
- logger.notice(log['message'])
93
- when 'info'
94
- logger.info(log['message'])
95
- else
96
- logger.debug(log["message"])
97
- end
98
- end
99
- end
80
+ def logs
81
+ value.dig('report', 'logs') || []
82
+ end
83
+
84
+ # Return only log messages associated with resources
85
+ def resource_logs
86
+ logs.reject { |log| log['source'] == 'Puppet' }
100
87
  end
101
88
 
102
89
  def metrics_message
@@ -281,8 +281,8 @@ Usage: bolt apply <manifest.pp> [options]
281
281
  define('-h', '--help', 'Display help') do |_|
282
282
  @options[:help] = true
283
283
  end
284
- define('-v', '--verbose', 'Display verbose logging') do |_|
285
- @options[:verbose] = true
284
+ define('-v', '--[no-]verbose', 'Display verbose logging') do |value|
285
+ @options[:verbose] = value
286
286
  end
287
287
  define('--debug', 'Display debug logging') do |_|
288
288
  @options[:debug] = true
@@ -65,7 +65,6 @@ module Bolt
65
65
  target = request['target']
66
66
  pdb_client = Bolt::PuppetDB::Client.new(Bolt::PuppetDB::Config.new(request['pdb_config']))
67
67
  options = request['puppet_config'] || {}
68
-
69
68
  with_puppet_settings(request['hiera_config']) do
70
69
  Puppet[:rich_data] = true
71
70
  Puppet[:node_name_value] = target['name']
@@ -126,6 +126,11 @@ module Bolt
126
126
  update_targets(options)
127
127
  end
128
128
 
129
+ unless options.key?(:verbose)
130
+ # Default to verbose for everything except plans
131
+ options[:verbose] = options[:subcommand] != 'plan'
132
+ end
133
+
129
134
  options
130
135
  rescue Bolt::Error => e
131
136
  warn e.message
@@ -261,11 +266,19 @@ module Bolt
261
266
  screen += '_object'
262
267
  end
263
268
 
264
- @analytics.screen_view(screen,
265
- output_format: config.format,
266
- target_nodes: options.fetch(:targets, []).count,
267
- inventory_nodes: inventory.node_names.count,
268
- inventory_groups: inventory.group_names.count)
269
+ screen_view_fields = {
270
+ output_format: config.format
271
+ }
272
+
273
+ # Only include target and inventory info for commands that take a targets
274
+ # list. This avoids loading inventory for commands that don't need it.
275
+ if options.key?(:targets)
276
+ screen_view_fields.merge!(target_nodes: options[:targets].count,
277
+ inventory_nodes: inventory.node_names.count,
278
+ inventory_groups: inventory.group_names.count)
279
+ end
280
+
281
+ @analytics.screen_view(screen, screen_view_fields)
269
282
 
270
283
  if options[:action] == 'show'
271
284
  if options[:subcommand] == 'task'
@@ -314,29 +327,23 @@ module Bolt
314
327
  elapsed_time = Benchmark.realtime do
315
328
  executor_opts = {}
316
329
  executor_opts['_description'] = options[:description] if options.key?(:description)
330
+ executor.subscribe(outputter)
331
+ executor.subscribe(log_outputter)
317
332
  results =
318
333
  case options[:subcommand]
319
334
  when 'command'
320
- executor.run_command(targets, options[:object], executor_opts) do |event|
321
- outputter.print_event(event)
322
- end
335
+ executor.run_command(targets, options[:object], executor_opts)
323
336
  when 'script'
324
337
  script = options[:object]
325
338
  validate_file('script', script)
326
- executor.run_script(
327
- targets, script, options[:leftovers], executor_opts
328
- ) do |event|
329
- outputter.print_event(event)
330
- end
339
+ executor.run_script(targets, script, options[:leftovers], executor_opts)
331
340
  when 'task'
332
341
  pal.run_task(options[:object],
333
342
  targets,
334
343
  options[:task_options],
335
344
  executor,
336
345
  inventory,
337
- options[:description]) do |event|
338
- outputter.print_event(event)
339
- end
346
+ options[:description])
340
347
  when 'file'
341
348
  src = options[:object]
342
349
  dest = options[:leftovers].first
@@ -345,13 +352,13 @@ module Bolt
345
352
  raise Bolt::CLIError, "A destination path must be specified"
346
353
  end
347
354
  validate_file('source file', src, true)
348
- executor.upload_file(targets, src, dest, executor_opts) do |event|
349
- outputter.print_event(event)
350
- end
355
+ executor.upload_file(targets, src, dest, executor_opts)
351
356
  end
352
357
  end
353
358
 
359
+ executor.shutdown
354
360
  rerun.update(results)
361
+
355
362
  outputter.print_summary(results, elapsed_time)
356
363
  code = results.ok ? 0 : 2
357
364
  end
@@ -398,12 +405,16 @@ module Bolt
398
405
  plan_context[:description] = options[:description] if options[:description]
399
406
 
400
407
  executor = Bolt::Executor.new(config.concurrency, @analytics, options[:noop])
408
+ executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
409
+ executor.subscribe(log_outputter)
401
410
  executor.start_plan(plan_context)
402
411
  result = pal.run_plan(plan_name, plan_arguments, executor, inventory, puppetdb_client)
403
412
 
404
413
  # If a non-bolt exception bubbles up the plan won't get finished
405
414
  executor.finish_plan(result)
415
+ executor.shutdown
406
416
  rerun.update(result)
417
+
407
418
  outputter.print_plan_result(result)
408
419
  result.ok? ? 0 : 1
409
420
  end
@@ -412,18 +423,25 @@ module Bolt
412
423
  ast = pal.parse_manifest(code, filename)
413
424
 
414
425
  executor = Bolt::Executor.new(config.concurrency, @analytics, noop)
415
- # Call start_plan just to enable plan_logging
416
- executor.start_plan(nil)
417
-
418
- pal.in_plan_compiler(executor, inventory, puppetdb_client) do |compiler|
419
- compiler.call_function('apply_prep', targets)
420
- end
426
+ executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
427
+ executor.subscribe(log_outputter)
428
+ # apply logging looks like plan logging, so tell the outputter we're in a
429
+ # plan even though we're not
430
+ executor.publish_event(type: :plan_start, plan: nil)
431
+
432
+ results = nil
433
+ elapsed_time = Benchmark.realtime do
434
+ pal.in_plan_compiler(executor, inventory, puppetdb_client) do |compiler|
435
+ compiler.call_function('apply_prep', targets)
436
+ end
421
437
 
422
- results = pal.with_bolt_executor(executor, inventory, puppetdb_client) do
423
- Puppet.lookup(:apply_executor).apply_ast(ast, targets, '_catch_errors' => true, '_noop' => noop)
438
+ results = pal.with_bolt_executor(executor, inventory, puppetdb_client) do
439
+ Puppet.lookup(:apply_executor).apply_ast(ast, targets, '_catch_errors' => true, '_noop' => noop)
440
+ end
424
441
  end
425
442
 
426
- outputter.print_apply_result(results)
443
+ executor.shutdown
444
+ outputter.print_apply_result(results, elapsed_time)
427
445
  rerun.update(results)
428
446
 
429
447
  results.ok ? 0 : 1
@@ -499,7 +517,11 @@ module Bolt
499
517
  end
500
518
 
501
519
  def outputter
502
- @outputter ||= Bolt::Outputter.for_format(config.format, config.color, config.trace)
520
+ @outputter ||= Bolt::Outputter.for_format(config.format, config.color, options[:verbose], config.trace)
521
+ end
522
+
523
+ def log_outputter
524
+ @log_outputter ||= Bolt::Outputter::Logger.new(options[:verbose], config.trace)
503
525
  end
504
526
 
505
527
  def bundled_content
@@ -175,8 +175,6 @@ module Bolt
175
175
 
176
176
  if options[:debug]
177
177
  @log['console'][:level] = :debug
178
- elsif options[:verbose]
179
- @log['console'][:level] = :info
180
178
  end
181
179
 
182
180
  @compile_concurrency = options[:'compile-concurrency'] if options[:'compile-concurrency']
@@ -8,7 +8,6 @@ require 'set'
8
8
  require 'bolt/analytics'
9
9
  require 'bolt/result'
10
10
  require 'bolt/config'
11
- require 'bolt/notifier'
12
11
  require 'bolt/result_set'
13
12
  require 'bolt/puppetdb'
14
13
 
@@ -41,6 +40,8 @@ module Bolt
41
40
  end
42
41
  end
43
42
  @reported_transports = Set.new
43
+ @subscribers = Set.new
44
+ @publisher = Concurrent::SingleThreadExecutor.new
44
45
 
45
46
  @noop = noop
46
47
  @run_as = nil
@@ -50,7 +51,6 @@ module Bolt
50
51
  Concurrent.global_immediate_executor
51
52
  end
52
53
  @logger.debug { "Started with #{concurrency} max thread(s)" }
53
- @notifier = Bolt::Notifier.new
54
54
  end
55
55
 
56
56
  def transport(transport)
@@ -61,6 +61,24 @@ module Bolt
61
61
  impl.value
62
62
  end
63
63
 
64
+ def subscribe(subscriber)
65
+ @subscribers << subscriber
66
+ self
67
+ end
68
+
69
+ def publish_event(event)
70
+ @subscribers.each do |subscriber|
71
+ @publisher.post(subscriber) do |sub|
72
+ sub.handle_event(event)
73
+ end
74
+ end
75
+ end
76
+
77
+ def shutdown
78
+ @publisher.shutdown
79
+ @publisher.wait_for_termination
80
+ end
81
+
64
82
  # Starts executing the given block on a list of nodes in parallel, one thread per "batch".
65
83
  #
66
84
  # This is the main driver of execution on a list of targets. It first
@@ -129,31 +147,19 @@ module Bolt
129
147
  end
130
148
 
131
149
  def log_action(description, targets)
132
- # When running a plan, info messages like starting a task are promoted to notice.
133
- log_method = @plan_logging ? :notice : :info
134
- target_str = if targets.length > 5
135
- "#{targets.count} targets"
136
- else
137
- targets.map(&:uri).join(', ')
138
- end
139
-
140
- @logger.send(log_method, "Starting: #{description} on #{target_str}")
150
+ publish_event(type: :step_start, description: description, targets: targets)
141
151
 
142
152
  start_time = Time.now
143
153
  results = yield
144
154
  duration = Time.now - start_time
145
155
 
146
- failures = results.error_set.length
147
- plural = failures == 1 ? '' : 's'
148
-
149
- @logger.send(log_method, "Finished: #{description} with #{failures} failure#{plural} in #{duration.round(2)} sec")
156
+ publish_event(type: :step_finish, description: description, result: results, duration: duration)
150
157
 
151
158
  results
152
159
  end
153
160
 
154
161
  def log_plan(plan_name)
155
- log_method = @plan_logging ? :notice : :info
156
- @logger.send(log_method, "Starting: plan #{plan_name}")
162
+ publish_event(type: :plan_start, plan: plan_name)
157
163
  start_time = Time.now
158
164
 
159
165
  results = nil
@@ -161,7 +167,7 @@ module Bolt
161
167
  results = yield
162
168
  ensure
163
169
  duration = Time.now - start_time
164
- @logger.send(log_method, "Finished: plan #{plan_name} in #{duration.round(2)} sec")
170
+ publish_event(type: :plan_finish, plan: plan_name, duration: duration)
165
171
  end
166
172
 
167
173
  results
@@ -220,72 +226,56 @@ module Bolt
220
226
  result
221
227
  end
222
228
 
223
- def run_command(targets, command, options = {}, &callback)
229
+ def run_command(targets, command, options = {})
224
230
  description = options.fetch('_description', "command '#{command}'")
225
231
  log_action(description, targets) do
226
- notify = proc { |event| @notifier.notify(callback, event) if callback }
227
232
  options = { '_run_as' => run_as }.merge(options) if run_as
228
233
 
229
- results = batch_execute(targets) do |transport, batch|
234
+ batch_execute(targets) do |transport, batch|
230
235
  with_node_logging("Running command '#{command}'", batch) do
231
- transport.batch_command(batch, command, options, &notify)
236
+ transport.batch_command(batch, command, options, &method(:publish_event))
232
237
  end
233
238
  end
234
-
235
- @notifier.shutdown
236
- results
237
239
  end
238
240
  end
239
241
 
240
- def run_script(targets, script, arguments, options = {}, &callback)
242
+ def run_script(targets, script, arguments, options = {})
241
243
  description = options.fetch('_description', "script #{script}")
242
244
  log_action(description, targets) do
243
- notify = proc { |event| @notifier.notify(callback, event) if callback }
244
245
  options = { '_run_as' => run_as }.merge(options) if run_as
245
246
 
246
- results = batch_execute(targets) do |transport, batch|
247
+ batch_execute(targets) do |transport, batch|
247
248
  with_node_logging("Running script #{script} with '#{arguments}'", batch) do
248
- transport.batch_script(batch, script, arguments, options, &notify)
249
+ transport.batch_script(batch, script, arguments, options, &method(:publish_event))
249
250
  end
250
251
  end
251
-
252
- @notifier.shutdown
253
- results
254
252
  end
255
253
  end
256
254
 
257
- def run_task(targets, task, arguments, options = {}, &callback)
255
+ def run_task(targets, task, arguments, options = {})
258
256
  description = options.fetch('_description', "task #{task.name}")
259
257
  log_action(description, targets) do
260
- notify = proc { |event| @notifier.notify(callback, event) if callback }
261
258
  options = { '_run_as' => run_as }.merge(options) if run_as
262
259
  arguments['_task'] = task.name
263
260
 
264
- results = batch_execute(targets) do |transport, batch|
261
+ batch_execute(targets) do |transport, batch|
265
262
  with_node_logging("Running task #{task.name} with '#{arguments}'", batch) do
266
- transport.batch_task(batch, task, arguments, options, &notify)
263
+ transport.batch_task(batch, task, arguments, options, &method(:publish_event))
267
264
  end
268
265
  end
269
-
270
- @notifier.shutdown
271
- results
272
266
  end
273
267
  end
274
268
 
275
- def upload_file(targets, source, destination, options = {}, &callback)
269
+ def upload_file(targets, source, destination, options = {})
276
270
  description = options.fetch('_description', "file upload from #{source} to #{destination}")
277
271
  log_action(description, targets) do
278
- notify = proc { |event| @notifier.notify(callback, event) if callback }
279
272
  options = { '_run_as' => run_as }.merge(options) if run_as
280
273
 
281
- results = batch_execute(targets) do |transport, batch|
274
+ batch_execute(targets) do |transport, batch|
282
275
  with_node_logging("Uploading file #{source} to #{destination}", batch) do
283
- transport.batch_upload(batch, source, destination, options, &notify)
276
+ transport.batch_upload(batch, source, destination, options, &method(:publish_event))
284
277
  end
285
278
  end
286
-
287
- @notifier.shutdown
288
- results
289
279
  end
290
280
  end
291
281
 
@@ -334,7 +324,6 @@ module Bolt
334
324
  # we'll need to refactor.
335
325
  def start_plan(plan_context)
336
326
  transport('pcp').plan_context = plan_context
337
- @plan_logging = true
338
327
  end
339
328
 
340
329
  def finish_plan(plan_result)
@@ -342,11 +331,10 @@ module Bolt
342
331
  end
343
332
 
344
333
  def without_default_logging
345
- old_log = @plan_logging
346
- @plan_logging = false
334
+ publish_event(type: :disable_default_output)
347
335
  yield
348
336
  ensure
349
- @plan_logging = old_log
337
+ publish_event(type: :enable_default_output)
350
338
  end
351
339
  end
352
340
  end
@@ -62,6 +62,11 @@ module Bolt
62
62
  msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in node #{node['name']}"
63
63
  @logger.warn(msg)
64
64
  end
65
+
66
+ unless node['config'].nil? || node['config'].is_a?(Hash)
67
+ raise ValidationError.new("Invalid configuration for node: #{node['name']}", @name)
68
+ end
69
+
65
70
  config_keys = node['config']&.keys || []
66
71
  unless (unexpected_keys = config_keys - CONFIG_KEYS).empty?
67
72
  msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for node #{node['name']}"
@@ -102,6 +102,11 @@ module Bolt
102
102
  msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in target #{target['name']}"
103
103
  @logger.warn(msg)
104
104
  end
105
+
106
+ unless target['config'].nil? || target['config'].is_a?(Hash)
107
+ raise ValidationError.new("Invalid configuration for target: #{target['name']}", @name)
108
+ end
109
+
105
110
  config_keys = target['config']&.keys || []
106
111
  unless (unexpected_keys = config_keys - CONFIG_KEYS).empty?
107
112
  msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for target #{target['name']}"
@@ -33,7 +33,7 @@ module Bolt
33
33
  root_logger.add_appenders Logging.appenders.stderr(
34
34
  'console',
35
35
  layout: console_layout(color),
36
- level: default_level
36
+ level: default_console_level
37
37
  )
38
38
 
39
39
  # We set the root logger's level so that it logs everything but we do
@@ -53,7 +53,7 @@ module Bolt
53
53
  filename: name[5..-1], # strip the "file:" prefix
54
54
  truncate: (params[:append] == false),
55
55
  layout: default_layout,
56
- level: default_level
56
+ level: default_file_level
57
57
  )
58
58
  rescue ArgumentError => e
59
59
  raise Bolt::Error.new("Failed to open log #{name}: #{e.message}", 'bolt/log-error')
@@ -81,7 +81,11 @@ module Bolt
81
81
  )
82
82
  end
83
83
 
84
- def self.default_level
84
+ def self.default_console_level
85
+ :warn
86
+ end
87
+
88
+ def self.default_file_level
85
89
  :notice
86
90
  end
87
91