bolt 2.16.0 → 2.21.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +3 -1
  3. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +20 -9
  4. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +123 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +2 -0
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +6 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +6 -4
  8. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
  9. data/lib/bolt/applicator.rb +19 -14
  10. data/lib/bolt/apply_result.rb +1 -1
  11. data/lib/bolt/bolt_option_parser.rb +60 -16
  12. data/lib/bolt/catalog.rb +3 -2
  13. data/lib/bolt/cli.rb +121 -43
  14. data/lib/bolt/config.rb +37 -34
  15. data/lib/bolt/config/options.rb +340 -173
  16. data/lib/bolt/config/transport/options.rb +315 -160
  17. data/lib/bolt/config/transport/ssh.rb +24 -10
  18. data/lib/bolt/executor.rb +21 -0
  19. data/lib/bolt/inventory/group.rb +3 -2
  20. data/lib/bolt/inventory/inventory.rb +4 -3
  21. data/lib/bolt/logger.rb +24 -1
  22. data/lib/bolt/outputter.rb +1 -1
  23. data/lib/bolt/outputter/rainbow.rb +14 -3
  24. data/lib/bolt/pal.rb +28 -10
  25. data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -2
  26. data/lib/bolt/pal/yaml_plan/step.rb +24 -2
  27. data/lib/bolt/pal/yaml_plan/step/download.rb +38 -0
  28. data/lib/bolt/pal/yaml_plan/step/message.rb +30 -0
  29. data/lib/bolt/pal/yaml_plan/step/upload.rb +3 -3
  30. data/lib/bolt/plugin/module.rb +2 -4
  31. data/lib/bolt/plugin/puppetdb.rb +3 -2
  32. data/lib/bolt/project.rb +20 -6
  33. data/lib/bolt/puppetdb/client.rb +2 -0
  34. data/lib/bolt/puppetdb/config.rb +16 -0
  35. data/lib/bolt/result.rb +7 -0
  36. data/lib/bolt/shell/bash.rb +45 -37
  37. data/lib/bolt/shell/powershell.rb +21 -11
  38. data/lib/bolt/shell/powershell/snippets.rb +15 -6
  39. data/lib/bolt/transport/base.rb +24 -0
  40. data/lib/bolt/transport/docker.rb +16 -4
  41. data/lib/bolt/transport/docker/connection.rb +20 -2
  42. data/lib/bolt/transport/local/connection.rb +14 -1
  43. data/lib/bolt/transport/orch.rb +20 -0
  44. data/lib/bolt/transport/simple.rb +6 -0
  45. data/lib/bolt/transport/ssh.rb +7 -1
  46. data/lib/bolt/transport/ssh/connection.rb +9 -1
  47. data/lib/bolt/transport/ssh/exec_connection.rb +23 -2
  48. data/lib/bolt/transport/winrm/connection.rb +118 -8
  49. data/lib/bolt/util.rb +26 -11
  50. data/lib/bolt/version.rb +1 -1
  51. data/lib/bolt_server/transport_app.rb +3 -2
  52. data/lib/bolt_spec/bolt_context.rb +7 -2
  53. data/lib/bolt_spec/plans.rb +15 -2
  54. data/lib/bolt_spec/plans/action_stubs.rb +2 -1
  55. data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
  56. data/lib/bolt_spec/plans/mock_executor.rb +14 -1
  57. data/lib/bolt_spec/run.rb +22 -0
  58. data/libexec/bolt_catalog +3 -2
  59. data/modules/secure_env_vars/plans/init.pp +20 -0
  60. metadata +21 -29
@@ -138,9 +138,10 @@ module Bolt
138
138
  # That means the apply body either a) consists of just a
139
139
  # NodeDefinition, b) consists of a BlockExpression which may
140
140
  # contain NodeDefinitions, or c) doesn't contain NodeDefinitions.
141
- definitions = if ast.is_a?(Puppet::Pops::Model::BlockExpression)
141
+ definitions = case ast
142
+ when Puppet::Pops::Model::BlockExpression
142
143
  ast.statements.select { |st| st.is_a?(Puppet::Pops::Model::NodeDefinition) }
143
- elsif ast.is_a?(Puppet::Pops::Model::NodeDefinition)
144
+ when Puppet::Pops::Model::NodeDefinition
144
145
  [ast]
145
146
  else
146
147
  []
@@ -32,7 +32,7 @@ module Bolt
32
32
  'script' => %w[run],
33
33
  'task' => %w[show run],
34
34
  'plan' => %w[show run convert],
35
- 'file' => %w[upload],
35
+ 'file' => %w[download upload],
36
36
  'puppetfile' => %w[install show-modules generate-types],
37
37
  'secret' => %w[encrypt decrypt createkeys],
38
38
  'inventory' => %w[show],
@@ -46,7 +46,6 @@ module Bolt
46
46
  Bolt::Logger.initialize_logging
47
47
  @logger = Logging.logger[self]
48
48
  @argv = argv
49
- @config = Bolt::Config.default
50
49
  @options = {}
51
50
  end
52
51
 
@@ -76,7 +75,23 @@ module Bolt
76
75
  end
77
76
  private :help?
78
77
 
78
+ # Wrapper method that is called by the Bolt executable. Parses the command and
79
+ # then loads the project and config. Once config is loaded, it completes the
80
+ # setup process by configuring Bolt and issuing warnings.
81
+ #
82
+ # This separation is needed since the Bolt::Outputter class that normally handles
83
+ # printing errors relies on config being loaded. All setup that happens before
84
+ # config is loaded will have errors printed directly to stdout, while all errors
85
+ # raised after config is loaded are handled by the outputter.
79
86
  def parse
87
+ parse_command
88
+ load_config
89
+ finalize_setup
90
+ end
91
+
92
+ # Parses the command and validates options. All errors that are raised here
93
+ # are not handled by the outputter, as it relies on config being loaded.
94
+ def parse_command
80
95
  parser = BoltOptionParser.new(options)
81
96
  # This part aims to handle both `bolt <mode> --help` and `bolt help <mode>`.
82
97
  remaining = handle_parser_errors { parser.permute(@argv) } unless @argv.empty?
@@ -109,51 +124,66 @@ module Bolt
109
124
  end
110
125
  options[:leftovers] = remaining
111
126
 
127
+ # Default to verbose for everything except plans
128
+ unless options.key?(:verbose)
129
+ options[:verbose] = options[:subcommand] != 'plan'
130
+ end
131
+
112
132
  validate(options)
113
133
 
114
- @config = if options[:configfile]
134
+ # Deprecation warnings can't be issued until after config is loaded, so
135
+ # store them for later.
136
+ @parser_deprecations = parser.deprecations
137
+ rescue Bolt::Error => e
138
+ fatal_error(e)
139
+ raise e
140
+ end
141
+
142
+ # Loads the project and configuration. All errors that are raised here are not
143
+ # handled by the outputter, as it relies on config being loaded.
144
+ def load_config
145
+ @config = if ENV['BOLT_PROJECT']
146
+ project = Bolt::Project.create_project(ENV['BOLT_PROJECT'], 'environment')
147
+ Bolt::Config.from_project(project, options)
148
+ elsif options[:configfile]
115
149
  Bolt::Config.from_file(options[:configfile], options)
116
150
  else
117
151
  project = if options[:boltdir]
118
- Bolt::Project.create_project(options[:boltdir])
152
+ dir = Pathname.new(options[:boltdir])
153
+ if (dir + Bolt::Project::BOLTDIR_NAME).directory?
154
+ Bolt::Project.create_project(dir + Bolt::Project::BOLTDIR_NAME)
155
+ else
156
+ Bolt::Project.create_project(dir)
157
+ end
119
158
  else
120
159
  Bolt::Project.find_boltdir(Dir.pwd)
121
160
  end
122
161
  Bolt::Config.from_project(project, options)
123
162
  end
163
+ rescue Bolt::Error => e
164
+ fatal_error(e)
165
+ raise e
166
+ end
124
167
 
168
+ # Completes the setup process by configuring Bolt and issuing warnings
169
+ def finalize_setup
125
170
  Bolt::Logger.configure(config.log, config.color)
171
+ Bolt::Logger.analytics = analytics
126
172
 
127
173
  # Logger must be configured before checking path case and project file, otherwise warnings will not display
128
- @config.check_path_case('modulepath', @config.modulepath)
129
- @config.project.check_deprecated_file
174
+ config.check_path_case('modulepath', config.modulepath)
175
+ config.project.check_deprecated_file
130
176
 
131
177
  # Log the file paths for loaded config files
132
178
  config_loaded
133
179
 
134
180
  # Display warnings created during parser and config initialization
135
- parser.warnings.each { |warning| @logger.warn(warning[:msg]) }
136
181
  config.warnings.each { |warning| @logger.warn(warning[:msg]) }
137
-
138
- # After validation, initialize inventory and targets. Errors here are better to catch early.
139
- # After this step
140
- # options[:target_args] will contain a string/array version of the targetting options this is passed to plans
141
- # options[:targets] will contain a resolved set of Target objects
142
- unless options[:subcommand] == 'puppetfile' ||
143
- options[:subcommand] == 'secret' ||
144
- options[:subcommand] == 'project' ||
145
- options[:action] == 'show' ||
146
- options[:action] == 'convert'
147
-
148
- update_targets(options)
149
- end
150
-
151
- unless options.key?(:verbose)
152
- # Default to verbose for everything except plans
153
- options[:verbose] = options[:subcommand] != 'plan'
154
- end
182
+ @parser_deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
183
+ config.deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
155
184
 
156
185
  warn_inventory_overrides_cli(options)
186
+
157
187
  options
158
188
  rescue Bolt::Error => e
159
189
  outputter.fatal_error(e)
@@ -228,6 +258,13 @@ module Bolt
228
258
  "Option '--noop' may only be specified when running a task or applying manifest code"
229
259
  end
230
260
 
261
+ if options[:env_vars]
262
+ unless %w[command script].include?(options[:subcommand]) && options[:action] == 'run'
263
+ raise Bolt::CLIError,
264
+ "Option '--env-var' may only be specified when running a command or script"
265
+ end
266
+ end
267
+
231
268
  if options[:subcommand] == 'apply' && (options[:object] && options[:code])
232
269
  raise Bolt::CLIError, "--execute is unsupported when specifying a manifest file"
233
270
  end
@@ -245,6 +282,10 @@ module Bolt
245
282
  !options[:object]
246
283
  raise Bolt::CLIError, "Must specify a value to #{options[:action]}"
247
284
  end
285
+
286
+ if options.key?(:debug) && options.key?(:log)
287
+ raise Bolt::CLIError, "Only one of '--debug' or '--log-level' may be specified"
288
+ end
248
289
  end
249
290
 
250
291
  def handle_parser_errors
@@ -272,12 +313,12 @@ module Bolt
272
313
  def warn_inventory_overrides_cli(opts)
273
314
  inventory_source = if ENV[Bolt::Inventory::ENVIRONMENT_VAR]
274
315
  Bolt::Inventory::ENVIRONMENT_VAR
275
- elsif @config.inventoryfile && Bolt::Util.file_stat(@config.inventoryfile)
276
- @config.inventoryfile
316
+ elsif config.inventoryfile && Bolt::Util.file_stat(config.inventoryfile)
317
+ config.inventoryfile
277
318
  else
278
319
  begin
279
- Bolt::Util.file_stat(@config.default_inventoryfile)
280
- @config.default_inventoryfile
320
+ Bolt::Util.file_stat(config.default_inventoryfile)
321
+ config.default_inventoryfile
281
322
  rescue Errno::ENOENT
282
323
  nil
283
324
  end
@@ -306,6 +347,17 @@ module Bolt
306
347
  exit!
307
348
  end
308
349
 
350
+ # Initialize inventory and targets. Errors here are better to catch early.
351
+ # options[:target_args] will contain a string/array version of the targetting options this is passed to plans
352
+ # options[:targets] will contain a resolved set of Target objects
353
+ unless options[:subcommand] == 'puppetfile' ||
354
+ options[:subcommand] == 'secret' ||
355
+ options[:subcommand] == 'project' ||
356
+ options[:action] == 'show' ||
357
+ options[:action] == 'convert'
358
+ update_targets(options)
359
+ end
360
+
309
361
  if options[:action] == 'convert'
310
362
  convert_plan(options[:object])
311
363
  return 0
@@ -334,30 +386,32 @@ module Bolt
334
386
 
335
387
  analytics.screen_view(screen, screen_view_fields)
336
388
 
337
- if options[:action] == 'show'
338
- if options[:subcommand] == 'task'
389
+ case options[:action]
390
+ when 'show'
391
+ case options[:subcommand]
392
+ when 'task'
339
393
  if options[:object]
340
394
  show_task(options[:object])
341
395
  else
342
396
  list_tasks
343
397
  end
344
- elsif options[:subcommand] == 'plan'
398
+ when 'plan'
345
399
  if options[:object]
346
400
  show_plan(options[:object])
347
401
  else
348
402
  list_plans
349
403
  end
350
- elsif options[:subcommand] == 'inventory'
404
+ when 'inventory'
351
405
  if options[:detail]
352
406
  show_targets
353
407
  else
354
408
  list_targets
355
409
  end
356
- elsif options[:subcommand] == 'group'
410
+ when 'group'
357
411
  list_groups
358
412
  end
359
413
  return 0
360
- elsif options[:action] == 'show-modules'
414
+ when 'show-modules'
361
415
  list_modules
362
416
  return 0
363
417
  end
@@ -370,18 +424,20 @@ module Bolt
370
424
 
371
425
  case options[:subcommand]
372
426
  when 'project'
373
- if options[:action] == 'init'
427
+ case options[:action]
428
+ when 'init'
374
429
  code = initialize_project
375
- elsif options[:action] == 'migrate'
430
+ when 'migrate'
376
431
  code = migrate_project
377
432
  end
378
433
  when 'plan'
379
434
  code = run_plan(options[:object], options[:task_options], options[:target_args], options)
380
435
  when 'puppetfile'
381
- if options[:action] == 'generate-types'
436
+ case options[:action]
437
+ when 'generate-types'
382
438
  code = generate_types
383
- elsif options[:action] == 'install'
384
- code = install_puppetfile(@config.puppetfile_config, @config.puppetfile, @config.modulepath)
439
+ when 'install'
440
+ code = install_puppetfile(config.puppetfile_config, config.puppetfile, config.modulepath)
385
441
  end
386
442
  when 'secret'
387
443
  code = Bolt::Secret.execute(plugins, outputter, options)
@@ -401,6 +457,7 @@ module Bolt
401
457
  elapsed_time = Benchmark.realtime do
402
458
  executor_opts = {}
403
459
  executor_opts[:description] = options[:description] if options.key?(:description)
460
+ executor_opts[:env_vars] = options[:env_vars] if options.key?(:env_vars)
404
461
  executor.subscribe(outputter)
405
462
  executor.subscribe(log_outputter)
406
463
  results =
@@ -422,11 +479,22 @@ module Bolt
422
479
  src = options[:object]
423
480
  dest = options[:leftovers].first
424
481
 
482
+ if src.nil?
483
+ raise Bolt::CLIError, "A source path must be specified"
484
+ end
485
+
425
486
  if dest.nil?
426
487
  raise Bolt::CLIError, "A destination path must be specified"
427
488
  end
428
- validate_file('source file', src, true)
429
- executor.upload_file(targets, src, dest, executor_opts)
489
+
490
+ case options[:action]
491
+ when 'download'
492
+ dest = File.expand_path(dest, Dir.pwd)
493
+ executor.download_file(targets, src, dest, executor_opts)
494
+ when 'upload'
495
+ validate_file('source file', src, true)
496
+ executor.upload_file(targets, src, dest, executor_opts)
497
+ end
430
498
  end
431
499
  end
432
500
 
@@ -793,7 +861,7 @@ module Bolt
793
861
  end
794
862
 
795
863
  def rerun
796
- @rerun ||= Bolt::Rerun.new(@config.rerunfile, @config.save_rerun)
864
+ @rerun ||= Bolt::Rerun.new(config.rerunfile, config.save_rerun)
797
865
  end
798
866
 
799
867
  def outputter
@@ -856,5 +924,15 @@ module Bolt
856
924
  def incomplete_install?
857
925
  (Dir.children(Bolt::PAL::MODULES_PATH) - %w[aggregate canary puppetdb_fact]).empty?
858
926
  end
927
+
928
+ # Mimicks the output from Outputter::Human#fatal_error. This should be used to print
929
+ # errors prior to config being loaded, as the outputter relies on config being loaded.
930
+ def fatal_error(error)
931
+ if $stdout.isatty
932
+ $stdout.puts("\033[31m#{error.message}\033[0m")
933
+ else
934
+ $stdout.puts(error.message)
935
+ end
936
+ end
859
937
  end
860
938
  end
@@ -6,13 +6,6 @@ require 'pathname'
6
6
  require 'bolt/project'
7
7
  require 'bolt/logger'
8
8
  require 'bolt/util'
9
- # Transport config objects
10
- require 'bolt/config/transport/ssh'
11
- require 'bolt/config/transport/winrm'
12
- require 'bolt/config/transport/orch'
13
- require 'bolt/config/transport/local'
14
- require 'bolt/config/transport/docker'
15
- require 'bolt/config/transport/remote'
16
9
  require 'bolt/config/options'
17
10
 
18
11
  module Bolt
@@ -26,27 +19,16 @@ module Bolt
26
19
  class Config
27
20
  include Bolt::Config::Options
28
21
 
29
- attr_reader :config_files, :warnings, :data, :transports, :project, :modified_concurrency
22
+ attr_reader :config_files, :warnings, :data, :transports, :project, :modified_concurrency, :deprecations
30
23
 
31
24
  BOLT_CONFIG_NAME = 'bolt.yaml'
32
25
  BOLT_DEFAULTS_NAME = 'bolt-defaults.yaml'
33
26
 
34
- # Transport config classes. Used to load default transport config which
35
- # gets passed along to the inventory.
36
- TRANSPORT_CONFIG = {
37
- 'ssh' => Bolt::Config::Transport::SSH,
38
- 'winrm' => Bolt::Config::Transport::WinRM,
39
- 'pcp' => Bolt::Config::Transport::Orch,
40
- 'local' => Bolt::Config::Transport::Local,
41
- 'docker' => Bolt::Config::Transport::Docker,
42
- 'remote' => Bolt::Config::Transport::Remote
43
- }.freeze
44
-
45
27
  # The default concurrency value that is used when the ulimit is not low (i.e. < 700)
46
28
  DEFAULT_DEFAULT_CONCURRENCY = 100
47
29
 
48
30
  def self.default
49
- new(Bolt::Project.create_project('.'), {})
31
+ new(Bolt::Project.default_project, {})
50
32
  end
51
33
 
52
34
  def self.from_project(project, overrides = {})
@@ -59,7 +41,8 @@ module Bolt
59
41
  data = load_defaults(project).push(
60
42
  filepath: project.config_file,
61
43
  data: conf,
62
- warnings: []
44
+ warnings: [],
45
+ deprecations: []
63
46
  )
64
47
 
65
48
  new(project, data, overrides)
@@ -77,7 +60,8 @@ module Bolt
77
60
  data = load_defaults(project).push(
78
61
  filepath: project.config_file,
79
62
  data: conf,
80
- warnings: []
63
+ warnings: [],
64
+ deprecations: []
81
65
  )
82
66
 
83
67
  new(project, data, overrides)
@@ -139,13 +123,26 @@ module Bolt
139
123
  )
140
124
  end
141
125
 
142
- # Move data under transport-config to top-level so it can be easily merged with
143
- # config from other sources.
126
+ # Move data under inventory-config to top-level so it can be easily merged with
127
+ # config from other sources. Error early if inventory-config is not a hash or
128
+ # has a plugin reference.
144
129
  if data.key?('inventory-config')
130
+ unless data['inventory-config'].is_a?(Hash)
131
+ raise Bolt::ValidationError,
132
+ "Option 'inventory-config' must be of type Hash, received #{data['inventory-config']} "\
133
+ "#{data['inventory-config']} (file: #{filepath})"
134
+ end
135
+
136
+ if data['inventory-config'].key?('_plugin')
137
+ raise Bolt::ValidationError,
138
+ "Found unsupported key '_plugin' for option 'inventory-config'; supported keys are "\
139
+ "'#{INVENTORY_OPTIONS.keys.join("', '")}' (file: #{filepath})"
140
+ end
141
+
145
142
  data = data.merge(data.delete('inventory-config'))
146
143
  end
147
144
 
148
- { filepath: filepath, data: data, warnings: warnings }
145
+ { filepath: filepath, data: data, warnings: warnings, deprecations: [] }
149
146
  end
150
147
 
151
148
  # Loads a 'bolt.yaml' file, the legacy configuration file. There's no special munging needed
@@ -153,10 +150,11 @@ module Bolt
153
150
  def self.load_bolt_yaml(dir)
154
151
  filepath = dir + BOLT_CONFIG_NAME
155
152
  data = Bolt::Util.read_yaml_hash(filepath, 'config')
156
- warnings = [msg: "Configuration file #{filepath} is deprecated and will be removed in a future version "\
157
- "of Bolt. Use '#{dir + BOLT_DEFAULTS_NAME}' instead."]
153
+ deprecations = [{ type: 'Using bolt.yaml for system configuration',
154
+ msg: "Configuration file #{filepath} is deprecated and will be removed in a future version "\
155
+ "of Bolt. Use '#{dir + BOLT_DEFAULTS_NAME}' instead." }]
158
156
 
159
- { filepath: filepath, data: data, warnings: warnings }
157
+ { filepath: filepath, data: data, warnings: [], deprecations: deprecations }
160
158
  end
161
159
 
162
160
  def self.load_defaults(project)
@@ -187,12 +185,16 @@ module Bolt
187
185
 
188
186
  def initialize(project, config_data, overrides = {})
189
187
  unless config_data.is_a?(Array)
190
- config_data = [{ filepath: project.config_file, data: config_data, warnings: [] }]
188
+ config_data = [{ filepath: project.config_file,
189
+ data: config_data,
190
+ warnings: [],
191
+ deprecations: [] }]
191
192
  end
192
193
 
193
194
  @logger = Logging.logger[self]
194
195
  @project = project
195
196
  @warnings = @project.warnings.dup
197
+ @deprecations = @project.deprecations.dup
196
198
  @transports = {}
197
199
  @config_files = []
198
200
 
@@ -213,6 +215,7 @@ module Bolt
213
215
 
214
216
  loaded_data = config_data.each_with_object([]) do |data, acc|
215
217
  @warnings.concat(data[:warnings]) if data[:warnings].any?
218
+ @deprecations.concat(data[:deprecations]) if data[:deprecations].any?
216
219
 
217
220
  if data[:data].any?
218
221
  @config_files.push(data[:filepath])
@@ -316,8 +319,8 @@ module Bolt
316
319
  end
317
320
 
318
321
  # Filter hashes to only include valid options
319
- @data['apply_settings'] = @data['apply_settings'].slice(*SUBOPTIONS['apply_settings'].keys)
320
- @data['puppetfile'] = @data['puppetfile'].slice(*SUBOPTIONS['puppetfile'].keys)
322
+ @data['apply_settings'] = @data['apply_settings'].slice(*OPTIONS['apply_settings'][:properties].keys)
323
+ @data['puppetfile'] = @data['puppetfile'].slice(*OPTIONS['puppetfile'][:properties].keys)
321
324
  end
322
325
 
323
326
  private def normalize_log(target)
@@ -331,7 +334,7 @@ module Bolt
331
334
  next unless val.is_a?(Hash)
332
335
 
333
336
  name = normalize_log(key)
334
- acc[name] = val.slice(*SUBOPTIONS['log'].keys)
337
+ acc[name] = val.slice('append', 'level')
335
338
  .transform_keys(&:to_sym)
336
339
 
337
340
  if (v = acc[name][:level])
@@ -383,7 +386,7 @@ module Bolt
383
386
  raise Bolt::ValidationError, "Compilation is CPU-intensive, set concurrency less than #{compile_limit}"
384
387
  end
385
388
 
386
- if (format == 'rainbow' && Bolt::Util.windows?) || !(%w[human json rainbow].include? format)
389
+ unless %w[human json rainbow].include? format
387
390
  raise Bolt::ValidationError, "Unsupported format: '#{format}'"
388
391
  end
389
392
 
@@ -496,7 +499,7 @@ module Bolt
496
499
  end
497
500
 
498
501
  def matching_paths(paths)
499
- [*paths].map { |p| Dir.glob([p, casefold(p)]) }.flatten.uniq.reject { |p| [*paths].include?(p) }
502
+ Array(paths).map { |p| Dir.glob([p, casefold(p)]) }.flatten.uniq.reject { |p| Array(paths).include?(p) }
500
503
  end
501
504
 
502
505
  private def casefold(path)