bolt 2.15.0 → 2.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -0
  3. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +20 -9
  5. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -0
  6. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +123 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +1 -0
  8. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +1 -0
  9. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +1 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +1 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +1 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +1 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +1 -0
  16. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +1 -0
  17. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +3 -0
  18. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -1
  19. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +7 -4
  20. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +2 -1
  21. data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +1 -0
  22. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +1 -0
  23. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +1 -0
  24. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +1 -0
  25. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -0
  26. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +1 -0
  27. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +1 -0
  28. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -0
  29. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
  30. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
  31. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +2 -0
  32. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +1 -0
  33. data/bolt-modules/file/lib/puppet/functions/file/join.rb +2 -0
  34. data/bolt-modules/file/lib/puppet/functions/file/read.rb +2 -0
  35. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +2 -0
  36. data/bolt-modules/file/lib/puppet/functions/file/write.rb +2 -0
  37. data/bolt-modules/out/lib/puppet/functions/out/message.rb +2 -0
  38. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +1 -0
  39. data/bolt-modules/system/lib/puppet/functions/system/env.rb +2 -0
  40. data/lib/bolt/applicator.rb +21 -15
  41. data/lib/bolt/apply_result.rb +1 -1
  42. data/lib/bolt/bolt_option_parser.rb +55 -20
  43. data/lib/bolt/catalog.rb +3 -2
  44. data/lib/bolt/cli.rb +116 -47
  45. data/lib/bolt/config.rb +48 -148
  46. data/lib/bolt/config/options.rb +488 -0
  47. data/lib/bolt/config/transport/base.rb +16 -16
  48. data/lib/bolt/config/transport/docker.rb +9 -23
  49. data/lib/bolt/config/transport/local.rb +6 -44
  50. data/lib/bolt/config/transport/options.rb +460 -0
  51. data/lib/bolt/config/transport/orch.rb +9 -18
  52. data/lib/bolt/config/transport/remote.rb +3 -6
  53. data/lib/bolt/config/transport/ssh.rb +74 -154
  54. data/lib/bolt/config/transport/winrm.rb +18 -47
  55. data/lib/bolt/executor.rb +15 -0
  56. data/lib/bolt/inventory/group.rb +4 -3
  57. data/lib/bolt/inventory/inventory.rb +4 -17
  58. data/lib/bolt/inventory/target.rb +18 -5
  59. data/lib/bolt/logger.rb +24 -1
  60. data/lib/bolt/outputter.rb +1 -1
  61. data/lib/bolt/outputter/rainbow.rb +14 -3
  62. data/lib/bolt/pal.rb +31 -11
  63. data/lib/bolt/pal/yaml_plan/evaluator.rb +19 -2
  64. data/lib/bolt/pal/yaml_plan/step.rb +11 -2
  65. data/lib/bolt/pal/yaml_plan/step/download.rb +38 -0
  66. data/lib/bolt/pal/yaml_plan/step/upload.rb +3 -3
  67. data/lib/bolt/plugin/module.rb +2 -4
  68. data/lib/bolt/plugin/puppetdb.rb +3 -2
  69. data/lib/bolt/project.rb +41 -44
  70. data/lib/bolt/puppetdb/client.rb +2 -0
  71. data/lib/bolt/puppetdb/config.rb +16 -0
  72. data/lib/bolt/result.rb +7 -0
  73. data/lib/bolt/shell/bash.rb +53 -45
  74. data/lib/bolt/shell/powershell.rb +23 -12
  75. data/lib/bolt/shell/powershell/snippets.rb +15 -6
  76. data/lib/bolt/transport/base.rb +24 -0
  77. data/lib/bolt/transport/docker.rb +17 -5
  78. data/lib/bolt/transport/docker/connection.rb +20 -2
  79. data/lib/bolt/transport/local/connection.rb +14 -1
  80. data/lib/bolt/transport/orch.rb +20 -0
  81. data/lib/bolt/transport/simple.rb +6 -0
  82. data/lib/bolt/transport/ssh.rb +7 -1
  83. data/lib/bolt/transport/ssh/connection.rb +9 -1
  84. data/lib/bolt/transport/ssh/exec_connection.rb +23 -2
  85. data/lib/bolt/transport/winrm/connection.rb +109 -8
  86. data/lib/bolt/util.rb +26 -11
  87. data/lib/bolt/version.rb +1 -1
  88. data/lib/bolt_server/transport_app.rb +3 -2
  89. data/lib/bolt_spec/bolt_context.rb +7 -2
  90. data/lib/bolt_spec/plans.rb +15 -2
  91. data/lib/bolt_spec/plans/action_stubs.rb +2 -1
  92. data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
  93. data/lib/bolt_spec/plans/mock_executor.rb +14 -1
  94. data/lib/bolt_spec/run.rb +22 -0
  95. data/libexec/bolt_catalog +3 -2
  96. metadata +20 -29
@@ -33,6 +33,7 @@ Puppet::Functions.create_function(:wait_until_available) do
33
33
  executor = Puppet.lookup(:bolt_executor)
34
34
  inventory = Puppet.lookup(:bolt_inventory)
35
35
 
36
+ # Send Analytics Report
36
37
  executor.report_function_call(self.class.name)
37
38
 
38
39
  # Ensure that given targets are all Target instances
@@ -29,6 +29,7 @@ Puppet::Functions.create_function(:without_default_logging) do
29
29
  end
30
30
 
31
31
  executor = Puppet.lookup(:bolt_executor)
32
+ # Send Analytics Report
32
33
  executor.report_function_call(self.class.name)
33
34
 
34
35
  executor.without_default_logging do
@@ -31,6 +31,7 @@ Puppet::Functions.create_function(:write_file) do
31
31
  end
32
32
 
33
33
  executor = Puppet.lookup(:bolt_executor)
34
+ # Send Analytics Report
34
35
  executor.report_function_call(self.class.name)
35
36
 
36
37
  inventory = Puppet.lookup(:bolt_inventory)
@@ -20,7 +20,9 @@ Puppet::Functions.create_function(:'ctrl::do_until') do
20
20
  end
21
21
 
22
22
  def do_until(options = { 'limit' => 0 })
23
+ # Send Analytics Report
23
24
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
25
+
24
26
  limit = options['limit']
25
27
  i = 0
26
28
  until (x = yield)
@@ -11,7 +11,9 @@ Puppet::Functions.create_function(:'ctrl::sleep') do
11
11
  end
12
12
 
13
13
  def sleeper(period)
14
+ # Send Analytics Report
14
15
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
16
+
15
17
  sleep(period)
16
18
  nil
17
19
  end
@@ -17,6 +17,7 @@ Puppet::Functions.create_function(:'file::exists', Puppet::Functions::InternalFu
17
17
  end
18
18
 
19
19
  def exists(scope, filename)
20
+ # Send Analytics Report
20
21
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
21
22
  found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
22
23
  found ? Puppet::FileSystem.exist?(found) : false
@@ -12,7 +12,9 @@ Puppet::Functions.create_function(:'file::join') do
12
12
  end
13
13
 
14
14
  def join(*paths)
15
+ # Send Analytics Report
15
16
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
17
+
16
18
  File.join(paths)
17
19
  end
18
20
  end
@@ -16,7 +16,9 @@ Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunc
16
16
  end
17
17
 
18
18
  def read(scope, filename)
19
+ # Send Analytics Report
19
20
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
21
+
20
22
  found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
21
23
  unless found && Puppet::FileSystem.exist?(found)
22
24
  raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
@@ -17,7 +17,9 @@ Puppet::Functions.create_function(:'file::readable', Puppet::Functions::Internal
17
17
  end
18
18
 
19
19
  def readable(scope, filename)
20
+ # Send Analytics Report
20
21
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
22
+
21
23
  found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
22
24
  found ? File.readable?(found) : false
23
25
  end
@@ -15,7 +15,9 @@ Puppet::Functions.create_function(:'file::write') do
15
15
  end
16
16
 
17
17
  def write(filename, content)
18
+ # Send Analytics Report
18
19
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
20
+
19
21
  File.write(filename, content)
20
22
  nil
21
23
  end
@@ -23,7 +23,9 @@ Puppet::Functions.create_function(:'out::message') do
23
23
  end
24
24
 
25
25
  executor = Puppet.lookup(:bolt_executor)
26
+ # Send Analytics Report
26
27
  executor.report_function_call(self.class.name)
28
+
27
29
  executor.publish_event(type: :message, message: message)
28
30
 
29
31
  nil
@@ -30,6 +30,7 @@ Puppet::Functions.create_function(:prompt) do
30
30
  options = options.transform_keys(&:to_sym)
31
31
 
32
32
  executor = Puppet.lookup(:bolt_executor)
33
+ # Send analytics report
33
34
  executor.report_function_call(self.class.name)
34
35
 
35
36
  response = executor.prompt(prompt, options)
@@ -12,7 +12,9 @@ Puppet::Functions.create_function(:'system::env') do
12
12
  end
13
13
 
14
14
  def env(name)
15
+ # Send analytics report
15
16
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
17
+
16
18
  ENV[name]
17
19
  end
18
20
  end
@@ -18,7 +18,6 @@ module Bolt
18
18
  pdb_client, hiera_config, max_compiles, apply_settings)
19
19
  # lazy-load expensive gem code
20
20
  require 'concurrent'
21
-
22
21
  @inventory = inventory
23
22
  @executor = executor
24
23
  @modulepath = modulepath || []
@@ -30,17 +29,6 @@ module Bolt
30
29
 
31
30
  @pool = Concurrent::ThreadPoolExecutor.new(max_threads: max_compiles)
32
31
  @logger = Logging.logger[self]
33
- @plugin_tarball = Concurrent::Delay.new do
34
- build_plugin_tarball do |mod|
35
- search_dirs = []
36
- search_dirs << mod.plugins if mod.plugins?
37
- search_dirs << mod.pluginfacts if mod.pluginfacts?
38
- search_dirs << mod.files if mod.files?
39
- type_files = "#{mod.path}/types"
40
- search_dirs << type_files if File.exist?(type_files)
41
- search_dirs
42
- end
43
- end
44
32
  end
45
33
 
46
34
  private def libexec
@@ -188,7 +176,6 @@ module Bolt
188
176
 
189
177
  def apply_ast(raw_ast, targets, options, plan_vars = {})
190
178
  ast = Puppet::Pops::Serialization::ToDataConverter.convert(raw_ast, rich_data: true, symbol_to_string: true)
191
-
192
179
  # Serialize as pcore for *Result* objects
193
180
  plan_vars = Puppet::Pops::Serialization::ToDataConverter.convert(plan_vars,
194
181
  rich_data: true,
@@ -196,19 +183,37 @@ module Bolt
196
183
  type_by_reference: true,
197
184
  local_reference: true)
198
185
 
186
+ bolt_project = @project if @project&.name
199
187
  scope = {
200
188
  code_ast: ast,
201
189
  modulepath: @modulepath,
202
- project: @project.to_h,
190
+ project: bolt_project.to_h,
203
191
  pdb_config: @pdb_client.config.to_hash,
204
192
  hiera_config: @hiera_config,
205
193
  plan_vars: plan_vars,
206
194
  # This data isn't available on the target config hash
207
195
  config: @inventory.transport_data_get
208
196
  }
209
-
210
197
  description = options[:description] || 'apply catalog'
211
198
 
199
+ required_modules = options[:required_modules].nil? ? nil : Array(options[:required_modules])
200
+ if required_modules&.any?
201
+ @logger.debug("Syncing only required modules: #{required_modules.join(',')}.")
202
+ end
203
+
204
+ @plugin_tarball = Concurrent::Delay.new do
205
+ build_plugin_tarball do |mod|
206
+ next unless required_modules.nil? || required_modules.include?(mod.name)
207
+ search_dirs = []
208
+ search_dirs << mod.plugins if mod.plugins?
209
+ search_dirs << mod.pluginfacts if mod.pluginfacts?
210
+ search_dirs << mod.files if mod.files?
211
+ type_files = "#{mod.path}/types"
212
+ search_dirs << type_files if File.exist?(type_files)
213
+ search_dirs
214
+ end
215
+ end
216
+
212
217
  r = @executor.log_action(description, targets) do
213
218
  futures = targets.map do |target|
214
219
  Concurrent::Future.execute(executor: @pool) do
@@ -235,6 +240,7 @@ module Bolt
235
240
  result
236
241
  end
237
242
  else
243
+
238
244
  arguments = {
239
245
  'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(catalog),
240
246
  'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins),
@@ -20,7 +20,7 @@ module Bolt
20
20
  error_hash['msg'] =~ /The term 'ruby.exe' is not recognized as the name of a cmdlet/)
21
21
  # Windows does not have Ruby present
22
22
  {
23
- 'msg' => "Puppet is not installed on the target in $env:ProgramFiles, please install it to enable 'apply'",
23
+ 'msg' => "Puppet was not found on the target or in $env:ProgramFiles, please install it to enable 'apply'",
24
24
  'kind' => 'bolt/apply-error'
25
25
  }
26
26
  elsif exit_code == 1 && error_hash['msg'] =~ /cannot load such file -- puppet \(LoadError\)/
@@ -10,10 +10,10 @@ module Bolt
10
10
  authentication: %w[user password password-prompt private-key host-key-check ssl ssl-verify],
11
11
  escalation: %w[run-as sudo-password sudo-password-prompt sudo-executable],
12
12
  run_context: %w[concurrency inventoryfile save-rerun cleanup],
13
- global_config_setters: %w[modulepath boltdir configfile],
14
- transports: %w[transport connect-timeout tty ssh-command copy-command],
13
+ global_config_setters: %w[modulepath project configfile],
14
+ transports: %w[transport connect-timeout tty native-ssh ssh-command copy-command],
15
15
  display: %w[format color verbose trace],
16
- global: %w[help version debug] }.freeze
16
+ global: %w[help version debug log-level] }.freeze
17
17
 
18
18
  ACTION_OPTS = OPTIONS.values.flatten.freeze
19
19
 
@@ -36,6 +36,9 @@ module Bolt
36
36
  when 'upload'
37
37
  { flags: ACTION_OPTS + %w[tmpdir],
38
38
  banner: FILE_UPLOAD_HELP }
39
+ when 'download'
40
+ { flags: ACTION_OPTS,
41
+ banner: FILE_DOWNLOAD_HELP }
39
42
  else
40
43
  { flags: OPTIONS[:global],
41
44
  banner: FILE_HELP }
@@ -218,10 +221,30 @@ module Bolt
218
221
  bolt file <action> [options]
219
222
 
220
223
  DESCRIPTION
221
- Upload a local file or directory
224
+ Copy files and directories between the controller and targets
222
225
 
223
226
  ACTIONS
224
- upload Upload a local file or directory
227
+ download Download a file or directory to the controller
228
+ upload Upload a local file or directory from the controller
229
+ HELP
230
+
231
+ FILE_DOWNLOAD_HELP = <<~HELP
232
+ NAME
233
+ download
234
+
235
+ USAGE
236
+ bolt file download <src> <dest> [options]
237
+
238
+ DESCRIPTION
239
+ Download a file or directory from one or more targets.
240
+
241
+ Downloaded files and directories are saved to the a subdirectory
242
+ matching the target's name under the destination directory. The
243
+ destination directory is expanded relative to the downloads
244
+ subdirectory of the project directory.
245
+
246
+ EXAMPLES
247
+ bolt file download /etc/ssh_config ssh_config -t all
225
248
  HELP
226
249
 
227
250
  FILE_UPLOAD_HELP = <<~HELP
@@ -422,7 +445,7 @@ module Bolt
422
445
 
423
446
  ACTIONS
424
447
  generate-types Generate type references to register in plans
425
- install Install modules from a Puppetfile into a Boltdir
448
+ install Install modules from a Puppetfile into a project
426
449
  show-modules List modules available to the Bolt project
427
450
  HELP
428
451
 
@@ -445,7 +468,7 @@ module Bolt
445
468
  bolt puppetfile install [options]
446
469
 
447
470
  DESCRIPTION
448
- Install modules from a Puppetfile into a Boltdir
471
+ Install modules from a Puppetfile into a project
449
472
  HELP
450
473
 
451
474
  PUPPETFILE_SHOWMODULES_HELP = <<~HELP
@@ -594,13 +617,13 @@ module Bolt
594
617
  bolt task show canary
595
618
  HELP
596
619
 
597
- attr_reader :warnings
620
+ attr_reader :deprecations
598
621
 
599
622
  def initialize(options)
600
623
  super()
601
624
 
602
625
  @options = options
603
- @warnings = []
626
+ @deprecations = []
604
627
 
605
628
  separator "\nINVENTORY OPTIONS"
606
629
  define('-t', '--targets TARGETS',
@@ -709,29 +732,29 @@ module Bolt
709
732
  File.expand_path(moduledir)
710
733
  end
711
734
  end
712
- define('--boltdir FILEPATH',
713
- 'Specify what Boltdir to load config from (default: autodiscovered from current working dir)') do |path|
735
+ define('--project PATH', '--boltdir PATH',
736
+ 'Specify what project to load config from (default: autodiscovered from current working dir)') do |path|
714
737
  @options[:boltdir] = path
715
738
  end
716
- define('--configfile FILEPATH',
739
+ define('--configfile PATH',
717
740
  'Specify where to load config from (default: ~/.puppetlabs/bolt/bolt.yaml).',
718
- 'Directory containing bolt.yaml will be used as the Boltdir.') do |path|
741
+ 'Directory containing bolt.yaml will be used as the project directory.') do |path|
719
742
  @options[:configfile] = path
720
743
  end
721
- define('--hiera-config FILEPATH',
744
+ define('--hiera-config PATH',
722
745
  'Specify where to load Hiera config from (default: ~/.puppetlabs/bolt/hiera.yaml)') do |path|
723
746
  @options[:'hiera-config'] = File.expand_path(path)
724
747
  end
725
- define('-i', '--inventoryfile FILEPATH',
748
+ define('-i', '--inventoryfile PATH',
726
749
  'Specify where to load inventory from (default: ~/.puppetlabs/bolt/inventory.yaml)') do |path|
727
750
  if ENV.include?(Bolt::Inventory::ENVIRONMENT_VAR)
728
751
  raise Bolt::CLIError, "Cannot pass inventory file when #{Bolt::Inventory::ENVIRONMENT_VAR} is set"
729
752
  end
730
753
  @options[:inventoryfile] = Pathname.new(File.expand_path(path))
731
754
  end
732
- define('--puppetfile FILEPATH',
755
+ define('--puppetfile PATH',
733
756
  'Specify a Puppetfile to use when installing modules. (default: ~/.puppetlabs/bolt/Puppetfile)',
734
- 'Modules are installed in the current Boltdir.') do |path|
757
+ 'Modules are installed in the current project.') do |path|
735
758
  @options[:puppetfile_path] = Pathname.new(File.expand_path(path))
736
759
  end
737
760
  define('--[no-]save-rerun', 'Whether to update the rerun file after this command.') do |save|
@@ -743,11 +766,15 @@ module Bolt
743
766
  "Specify a default transport: #{TRANSPORTS.keys.join(', ')}") do |t|
744
767
  @options[:transport] = t
745
768
  end
746
- define('--ssh-command EXEC', "Executable to use instead of the net-ssh ruby library. ",
769
+ define('--[no-]native-ssh', 'Whether to shell out to native SSH or use the net-ssh Ruby library.',
770
+ 'This option is experimental') do |bool|
771
+ @options[:'native-ssh'] = bool
772
+ end
773
+ define('--ssh-command EXEC', "Executable to use instead of the net-ssh Ruby library. ",
747
774
  "This option is experimental.") do |exec|
748
775
  @options[:'ssh-command'] = exec
749
776
  end
750
- define('--copy-command EXEC', "Command to copy files to remote hosts if using external SSH. ",
777
+ define('--copy-command EXEC', "Command to copy files to remote hosts if using native SSH. ",
751
778
  "This option is experimental.") do |exec|
752
779
  @options[:'copy-command'] = exec
753
780
  end
@@ -803,8 +830,16 @@ module Bolt
803
830
  end
804
831
  define('--debug', 'Display debug logging') do |_|
805
832
  @options[:debug] = true
833
+ # We don't actually set '--log-level debug' here, but once the options are evaluated by
834
+ # the config class the end result is the same.
835
+ msg = "Command line option '--debug' is deprecated, set '--log-level debug' instead."
836
+ @deprecations << { type: 'Using --debug instead of --log-level debug', msg: msg }
837
+ end
838
+ define('--log-level LEVEL',
839
+ "Set the log level for the console. Available options are",
840
+ "debug, info, notice, warn, error, fatal, any.") do |level|
841
+ @options[:log] = { 'console' => { 'level' => level } }
806
842
  end
807
-
808
843
  define('--plugin PLUGIN', 'Select the plugin to use') do |plug|
809
844
  @options[:plugin] = plug
810
845
  end
@@ -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)
@@ -219,7 +249,7 @@ module Bolt
219
249
  end
220
250
 
221
251
  if options[:boltdir] && options[:configfile]
222
- raise Bolt::CLIError, "Only one of '--boltdir' or '--configfile' may be specified"
252
+ raise Bolt::CLIError, "Only one of '--boltdir', '--project', or '--configfile' may be specified"
223
253
  end
224
254
 
225
255
  if options[:noop] &&
@@ -245,6 +275,10 @@ module Bolt
245
275
  !options[:object]
246
276
  raise Bolt::CLIError, "Must specify a value to #{options[:action]}"
247
277
  end
278
+
279
+ if options.key?(:debug) && options.key?(:log)
280
+ raise Bolt::CLIError, "Only one of '--debug' or '--log-level' may be specified"
281
+ end
248
282
  end
249
283
 
250
284
  def handle_parser_errors
@@ -272,12 +306,12 @@ module Bolt
272
306
  def warn_inventory_overrides_cli(opts)
273
307
  inventory_source = if ENV[Bolt::Inventory::ENVIRONMENT_VAR]
274
308
  Bolt::Inventory::ENVIRONMENT_VAR
275
- elsif @config.inventoryfile && Bolt::Util.file_stat(@config.inventoryfile)
276
- @config.inventoryfile
309
+ elsif config.inventoryfile && Bolt::Util.file_stat(config.inventoryfile)
310
+ config.inventoryfile
277
311
  else
278
312
  begin
279
- Bolt::Util.file_stat(@config.default_inventoryfile)
280
- @config.default_inventoryfile
313
+ Bolt::Util.file_stat(config.default_inventoryfile)
314
+ config.default_inventoryfile
281
315
  rescue Errno::ENOENT
282
316
  nil
283
317
  end
@@ -306,6 +340,17 @@ module Bolt
306
340
  exit!
307
341
  end
308
342
 
343
+ # Initialize inventory and targets. Errors here are better to catch early.
344
+ # options[:target_args] will contain a string/array version of the targetting options this is passed to plans
345
+ # options[:targets] will contain a resolved set of Target objects
346
+ unless options[:subcommand] == 'puppetfile' ||
347
+ options[:subcommand] == 'secret' ||
348
+ options[:subcommand] == 'project' ||
349
+ options[:action] == 'show' ||
350
+ options[:action] == 'convert'
351
+ update_targets(options)
352
+ end
353
+
309
354
  if options[:action] == 'convert'
310
355
  convert_plan(options[:object])
311
356
  return 0
@@ -334,30 +379,32 @@ module Bolt
334
379
 
335
380
  analytics.screen_view(screen, screen_view_fields)
336
381
 
337
- if options[:action] == 'show'
338
- if options[:subcommand] == 'task'
382
+ case options[:action]
383
+ when 'show'
384
+ case options[:subcommand]
385
+ when 'task'
339
386
  if options[:object]
340
387
  show_task(options[:object])
341
388
  else
342
389
  list_tasks
343
390
  end
344
- elsif options[:subcommand] == 'plan'
391
+ when 'plan'
345
392
  if options[:object]
346
393
  show_plan(options[:object])
347
394
  else
348
395
  list_plans
349
396
  end
350
- elsif options[:subcommand] == 'inventory'
397
+ when 'inventory'
351
398
  if options[:detail]
352
399
  show_targets
353
400
  else
354
401
  list_targets
355
402
  end
356
- elsif options[:subcommand] == 'group'
403
+ when 'group'
357
404
  list_groups
358
405
  end
359
406
  return 0
360
- elsif options[:action] == 'show-modules'
407
+ when 'show-modules'
361
408
  list_modules
362
409
  return 0
363
410
  end
@@ -370,18 +417,20 @@ module Bolt
370
417
 
371
418
  case options[:subcommand]
372
419
  when 'project'
373
- if options[:action] == 'init'
420
+ case options[:action]
421
+ when 'init'
374
422
  code = initialize_project
375
- elsif options[:action] == 'migrate'
423
+ when 'migrate'
376
424
  code = migrate_project
377
425
  end
378
426
  when 'plan'
379
427
  code = run_plan(options[:object], options[:task_options], options[:target_args], options)
380
428
  when 'puppetfile'
381
- if options[:action] == 'generate-types'
429
+ case options[:action]
430
+ when 'generate-types'
382
431
  code = generate_types
383
- elsif options[:action] == 'install'
384
- code = install_puppetfile(@config.puppetfile_config, @config.puppetfile, @config.modulepath)
432
+ when 'install'
433
+ code = install_puppetfile(config.puppetfile_config, config.puppetfile, config.modulepath)
385
434
  end
386
435
  when 'secret'
387
436
  code = Bolt::Secret.execute(plugins, outputter, options)
@@ -422,11 +471,22 @@ module Bolt
422
471
  src = options[:object]
423
472
  dest = options[:leftovers].first
424
473
 
474
+ if src.nil?
475
+ raise Bolt::CLIError, "A source path must be specified"
476
+ end
477
+
425
478
  if dest.nil?
426
479
  raise Bolt::CLIError, "A destination path must be specified"
427
480
  end
428
- validate_file('source file', src, true)
429
- executor.upload_file(targets, src, dest, executor_opts)
481
+
482
+ case options[:action]
483
+ when 'download'
484
+ dest = File.expand_path(dest, Dir.pwd)
485
+ executor.download_file(targets, src, dest, executor_opts)
486
+ when 'upload'
487
+ validate_file('source file', src, true)
488
+ executor.upload_file(targets, src, dest, executor_opts)
489
+ end
430
490
  end
431
491
  end
432
492
 
@@ -513,7 +573,7 @@ module Bolt
513
573
  plan_context[:description] = options[:description] if options[:description]
514
574
 
515
575
  executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop], config.modified_concurrency)
516
- if options.fetch(:format, 'human') == 'human'
576
+ if %w[human rainbow].include?(options.fetch(:format, 'human'))
517
577
  executor.subscribe(outputter)
518
578
  else
519
579
  # Only subscribe to out::message events for JSON outputter
@@ -771,14 +831,13 @@ module Bolt
771
831
  end
772
832
 
773
833
  def pal
774
- project = config.project.project_file? ? config.project : nil
775
834
  @pal ||= Bolt::PAL.new(config.modulepath,
776
835
  config.hiera_config,
777
836
  config.project.resource_types,
778
837
  config.compile_concurrency,
779
838
  config.trusted_external,
780
839
  config.apply_settings,
781
- project)
840
+ config.project)
782
841
  end
783
842
 
784
843
  def convert_plan(plan)
@@ -794,7 +853,7 @@ module Bolt
794
853
  end
795
854
 
796
855
  def rerun
797
- @rerun ||= Bolt::Rerun.new(@config.rerunfile, @config.save_rerun)
856
+ @rerun ||= Bolt::Rerun.new(config.rerunfile, config.save_rerun)
798
857
  end
799
858
 
800
859
  def outputter
@@ -857,5 +916,15 @@ module Bolt
857
916
  def incomplete_install?
858
917
  (Dir.children(Bolt::PAL::MODULES_PATH) - %w[aggregate canary puppetdb_fact]).empty?
859
918
  end
919
+
920
+ # Mimicks the output from Outputter::Human#fatal_error. This should be used to print
921
+ # errors prior to config being loaded, as the outputter relies on config being loaded.
922
+ def fatal_error(error)
923
+ if $stdout.isatty
924
+ $stdout.puts("\033[31m#{error.message}\033[0m")
925
+ else
926
+ $stdout.puts(error.message)
927
+ end
928
+ end
860
929
  end
861
930
  end