bolt 3.0.0 → 3.5.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +13 -11
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +24 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
  9. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  10. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  11. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
  12. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  13. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  14. data/lib/bolt/apply_result.rb +1 -1
  15. data/lib/bolt/bolt_option_parser.rb +6 -3
  16. data/lib/bolt/cli.rb +96 -16
  17. data/lib/bolt/config.rb +4 -0
  18. data/lib/bolt/config/options.rb +21 -3
  19. data/lib/bolt/config/transport/lxd.rb +23 -0
  20. data/lib/bolt/config/transport/options.rb +8 -1
  21. data/lib/bolt/container_result.rb +105 -0
  22. data/lib/bolt/error.rb +15 -0
  23. data/lib/bolt/executor.rb +22 -7
  24. data/lib/bolt/inventory/options.rb +9 -0
  25. data/lib/bolt/inventory/target.rb +16 -0
  26. data/lib/bolt/logger.rb +8 -0
  27. data/lib/bolt/module_installer.rb +2 -2
  28. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  29. data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
  30. data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
  31. data/lib/bolt/node/output.rb +14 -4
  32. data/lib/bolt/outputter/human.rb +106 -23
  33. data/lib/bolt/outputter/logger.rb +17 -0
  34. data/lib/bolt/pal.rb +25 -4
  35. data/lib/bolt/pal/yaml_plan.rb +1 -2
  36. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -141
  37. data/lib/bolt/pal/yaml_plan/step.rb +91 -31
  38. data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
  39. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  40. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  41. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  42. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  43. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  44. data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
  45. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  46. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  47. data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
  48. data/lib/bolt/plan_creator.rb +1 -1
  49. data/lib/bolt/project_manager.rb +1 -1
  50. data/lib/bolt/project_manager/module_migrator.rb +1 -1
  51. data/lib/bolt/result.rb +11 -15
  52. data/lib/bolt/shell.rb +16 -0
  53. data/lib/bolt/shell/bash.rb +61 -31
  54. data/lib/bolt/shell/bash/tmpdir.rb +2 -2
  55. data/lib/bolt/shell/powershell.rb +34 -12
  56. data/lib/bolt/shell/powershell/snippets.rb +30 -3
  57. data/lib/bolt/task.rb +1 -1
  58. data/lib/bolt/transport/base.rb +0 -9
  59. data/lib/bolt/transport/docker.rb +1 -125
  60. data/lib/bolt/transport/docker/connection.rb +77 -167
  61. data/lib/bolt/transport/lxd.rb +26 -0
  62. data/lib/bolt/transport/lxd/connection.rb +99 -0
  63. data/lib/bolt/transport/orch.rb +13 -5
  64. data/lib/bolt/transport/ssh/connection.rb +1 -1
  65. data/lib/bolt/transport/winrm/connection.rb +1 -1
  66. data/lib/bolt/util.rb +31 -0
  67. data/lib/bolt/version.rb +1 -1
  68. data/lib/bolt_server/transport_app.rb +61 -33
  69. data/lib/bolt_spec/bolt_context.rb +9 -4
  70. data/lib/bolt_spec/plans.rb +1 -109
  71. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  72. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  73. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  74. data/lib/bolt_spec/plans/mock_executor.rb +90 -7
  75. data/modules/puppet_connect/plans/test_input_data.pp +65 -7
  76. metadata +9 -2
@@ -11,12 +11,24 @@ Puppet::Functions.create_function(:prompt) do
11
11
  # @option options [Boolean] sensitive Disable echo back and mark the response as sensitive.
12
12
  # The returned value will be wrapped by the `Sensitive` data type. To access the raw
13
13
  # value, use the `unwrap` function (i.e. `$sensitive_value.unwrap`).
14
+ # @option options [String] default The default value to return if the user does not provide
15
+ # input or if stdin is not a tty.
14
16
  # @return The response to the prompt.
15
17
  # @example Prompt the user if plan execution should continue
16
18
  # $response = prompt('Continue executing plan? [Y\N]')
17
19
  # @example Prompt the user for sensitive information
18
20
  # $password = prompt('Enter your password', 'sensitive' => true)
19
21
  # out::message("Password is: ${password.unwrap}")
22
+ # @example Prompt the user and provide a default value
23
+ # $user = prompt('Enter your login username', 'default' => 'root')
24
+ # @example Prompt the user for sensitive information, returning a sensitive default if one is not provided
25
+ # $token = prompt('Enter token', 'default' => lookup('default_token'), 'sensitive' => true)
26
+ # out::message("Token is: ${token.unwrap}")
27
+ # @example Prompt the user and fail with a custom message if no input was provided
28
+ # $response = prompt('Enter your name', 'default' => '')
29
+ # if $response.empty {
30
+ # fail_plan('Must provide your name')
31
+ # }
20
32
  dispatch :prompt do
21
33
  param 'String', :prompt
22
34
  optional_param 'Hash[String[1], Any]', :options
@@ -30,14 +42,20 @@ Puppet::Functions.create_function(:prompt) do
30
42
  action: 'prompt')
31
43
  end
32
44
 
33
- options = options.transform_keys(&:to_sym)
34
-
45
+ options = options.transform_keys(&:to_sym)
35
46
  executor = Puppet.lookup(:bolt_executor)
47
+
36
48
  # Send analytics report
37
49
  executor.report_function_call(self.class.name)
38
50
 
51
+ # Require default to be a string value
52
+ if options.key?(:default) && !options[:default].is_a?(String)
53
+ raise Bolt::ValidationError, "Option 'default' must be a string"
54
+ end
55
+
39
56
  response = executor.prompt(prompt, options)
40
57
 
58
+ # If sensitive, wrap it
41
59
  if options[:sensitive]
42
60
  Puppet::Pops::Types::PSensitiveType::Sensitive.new(response)
43
61
  else
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # Display a menu prompt and wait for a response. Continues to prompt
6
+ # until an option from the menu is selected.
7
+ #
8
+ # > **Note:** Not available in apply block
9
+ Puppet::Functions.create_function(:'prompt::menu') do
10
+ # Select from a list of options.
11
+ # @param prompt The prompt to display.
12
+ # @param menu A list of options to choose from.
13
+ # @param options A hash of additional options.
14
+ # @option options [String] default The default option to return if the user does not provide
15
+ # input or if standard in (stdin) is not a tty. Must be an option present in the menu.
16
+ # @return The selected option.
17
+ # @example Prompt the user to select from a list of options
18
+ # $selection = prompt::menu('Select a fruit', ['apple', 'banana', 'carrot'])
19
+ # @example Prompt the user to select from a list of options with a default value
20
+ # $selection = prompt::menu('Select environment', ['development', 'production'], 'default' => 'development')
21
+ dispatch :prompt_menu_array do
22
+ param 'String', :prompt
23
+ param 'Array[Variant[Target, Data]]', :menu
24
+ optional_param 'Hash[String[1], Variant[Target, Data]]', :options
25
+ return_type 'Variant[Target, Data]'
26
+ end
27
+
28
+ # Select from a list of options with custom inputs.
29
+ # @param prompt The prompt to display.
30
+ # @param menu A hash of options to choose from, where keys are the input used to select a value.
31
+ # @param options A hash of additional options.
32
+ # @option options [String] default The default option to return if the user does not provide
33
+ # input or if standard in (stdin) is not a tty. Must be an option present in the menu.
34
+ # @return The selected option.
35
+ # @example Prompt the user to select from a list of options with custom inputs
36
+ # $menu = { 'y' => 'yes', 'n' => 'no' }
37
+ # $selection = prompt::menu('Install Puppet?', $menu)
38
+ dispatch :prompt_menu do
39
+ param 'String', :prompt
40
+ param 'Hash[String[1], Variant[Target, Data]]', :menu
41
+ optional_param 'Hash[String[1], Variant[Target, Data]]', :options
42
+ return_type 'Variant[TargetSpec, Data]'
43
+ end
44
+
45
+ def prompt_menu_array(prompt, menu, options = {})
46
+ menu_hash = menu.map.with_index { |value, index| [(index + 1).to_s, value] }.to_h
47
+ prompt_menu(prompt, menu_hash, options)
48
+ end
49
+
50
+ def prompt_menu(prompt, menu, options = {})
51
+ unless Puppet[:tasks]
52
+ raise Puppet::ParseErrorWithIssue
53
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
54
+ action: 'prompt::menu')
55
+ end
56
+
57
+ options = options.transform_keys(&:to_sym)
58
+ executor = Puppet.lookup(:bolt_executor)
59
+
60
+ # Send analytics report
61
+ executor.report_function_call(self.class.name)
62
+
63
+ # Error if there are no options
64
+ if menu.empty?
65
+ raise Bolt::ValidationError, "Menu cannot be empty"
66
+ end
67
+
68
+ # Error if the default value is not on the menu
69
+ if options.key?(:default) && !menu.value?(options[:default])
70
+ raise Bolt::ValidationError, "Default value '#{options[:default]}' is not one of the provided menu options"
71
+ end
72
+
73
+ # The first prompt should include the menu
74
+ to_prompt = format_menu(menu) + prompt
75
+
76
+ # Request input from the user until they provide a valid option
77
+ loop do
78
+ selection = executor.prompt(to_prompt, options)
79
+
80
+ return menu[selection] if menu.key?(selection)
81
+ return selection if options.key?(:default) && options[:default] == selection
82
+
83
+ # Only reprint the prompt, not the menu
84
+ to_prompt = "Invalid option, try again. #{prompt}"
85
+ end
86
+ end
87
+
88
+ # Builds the menu string. Aligns all the values by padding input keys.
89
+ #
90
+ private def format_menu(menu)
91
+ # Find the longest input and add 2 for wrapping parenthesis
92
+ key_length = menu.keys.max_by(&:length).length + 2
93
+
94
+ menu_string = +''
95
+
96
+ menu.each do |key, value|
97
+ key = "(#{key})".ljust(key_length)
98
+ menu_string << "#{key} #{value}\n"
99
+ end
100
+
101
+ menu_string
102
+ end
103
+ end
@@ -54,7 +54,7 @@ module Bolt
54
54
  unless missing_keys.empty?
55
55
  if result['_output']
56
56
  # rubocop:disable Layout/LineLength
57
- msg = "Report result contains an '_output' key. Catalog application may have printed extraneous output to stdout: #{result['_output']}"
57
+ msg = "Report result contains an '_output' key. Catalog application might have printed extraneous output to stdout: #{result['_output']}"
58
58
  # rubocop:enable Layout/LineLength
59
59
  else
60
60
  msg = "Report did not contain all expected keys missing: #{missing_keys.join(', ')}"
@@ -13,7 +13,7 @@ module Bolt
13
13
  run_context: %w[concurrency inventoryfile save-rerun cleanup],
14
14
  global_config_setters: PROJECT_PATHS + %w[modulepath],
15
15
  transports: %w[transport connect-timeout tty native-ssh ssh-command copy-command],
16
- display: %w[format color verbose trace],
16
+ display: %w[format color verbose trace stream],
17
17
  global: %w[help version log-level clear-cache] }.freeze
18
18
 
19
19
  ACTION_OPTS = OPTIONS.values.flatten.freeze
@@ -454,7 +454,7 @@ module Bolt
454
454
  DESCRIPTION
455
455
  Convert a YAML plan to a Puppet language plan and print the converted plan to stdout.
456
456
 
457
- Converting a YAML plan may result in a plan that is syntactically
457
+ Converting a YAML plan might result in a plan that is syntactically
458
458
  correct but has different behavior. Always verify a converted plan's
459
459
  functionality. Note that the converted plan is not written to a file.
460
460
 
@@ -707,7 +707,7 @@ module Bolt
707
707
  "Or read a target list from an input file '@<file>' or stdin '-'.",
708
708
  'Example: --targets localhost,target_group,ssh://nix.com:23,winrm://windows.puppet.com',
709
709
  'URI format is [protocol://]host[:port]',
710
- "SSH is the default protocol; may be #{TRANSPORTS.keys.join(', ')}",
710
+ "SSH is the default protocol; can be #{TRANSPORTS.keys.join(', ')}",
711
711
  'For Windows targets, specify the winrm:// protocol if it has not be configured',
712
712
  'For SSH, port defaults to `22`',
713
713
  'For WinRM, port defaults to `5985` or `5986` based on the --[no-]ssl setting') do |targets|
@@ -888,6 +888,9 @@ module Bolt
888
888
  define('-v', '--[no-]verbose', 'Display verbose logging') do |value|
889
889
  @options[:verbose] = value
890
890
  end
891
+ define('--stream', 'Stream output from scripts and commands to the console') do |_|
892
+ @options[:stream] = true
893
+ end
891
894
  define('--trace', 'Display error stack traces') do |_|
892
895
  @options[:trace] = true
893
896
  end
data/lib/bolt/cli.rb CHANGED
@@ -96,6 +96,48 @@ module Bolt
96
96
  finalize_setup
97
97
  end
98
98
 
99
+ # Prints a welcome message when users first install Bolt and run `bolt`, `bolt help` or `bolt --help`
100
+ def welcome_message
101
+ bolt = <<~BOLT
102
+ `.::-`
103
+ `.-:///////-.`
104
+ `-:////:. `-:///:- /ooo. .ooo/
105
+ `.-:///::///:-` `-//: ymmm- :mmmy .---.
106
+ :///:-. `.:////. -//: ymmm- :mmmy +mmm+
107
+ ://. ///. -//: ymmm--/++/- `-/++/:` :mmmy-:smmms::-
108
+ ://. ://. .://: ymmmdmmmmmmdo` .smmmmmmmmh: :mmmysmmmmmmmms
109
+ ://. ://:///:-. ymmmh/--/hmmmy -mmmd/-.:hmmm+:mmmy.-smmms--.
110
+ ://:.` .-////:-` ymmm- ymmm:hmmm- `dmmm/mmmy +mmm+
111
+ `-:///:-..:///:-.` ymmm- ommm/dmmm` hmmm+mmmy +mmm+
112
+ `.-:////:-` ymmm+ /mmmm.ommms` /mmmh:mmmy +mmmo
113
+ `-.` ymmmmmhhmmmmd: ommmmhydmmmy`:mmmy -mmmmdhd
114
+ oyyy+shddhs/` .+shddhy+- -yyyo .ohddhs
115
+
116
+
117
+ BOLT
118
+ example_cmd = if Bolt::Util.windows?
119
+ "Invoke-BoltCommand -Command 'hostname' -Targets localhost"
120
+ else
121
+ "bolt command run 'hostname' --target localhost"
122
+ end
123
+ prev_cmd = String.new("bolt")
124
+ prev_cmd << " #{@argv[0]}" unless @argv.empty?
125
+
126
+ message = <<~MSG
127
+ 🎉 Welcome to Bolt #{VERSION}
128
+ 😌 We're here to help bring order to the chaos
129
+ 📖 Find our documentation at https://bolt.guide
130
+ 🙋 Ask a question in #bolt on https://slack.puppet.com/
131
+ 🔩 Contribute at https://github.com/puppetlabs/bolt/
132
+ 💡 Not sure where to start? Try "#{example_cmd}"
133
+
134
+ We only print this message once. Run "#{prev_cmd}" again for help text.
135
+ MSG
136
+
137
+ $stdout.print "\033[36m#{bolt}\033[0m"
138
+ $stdout.print message
139
+ end
140
+
99
141
  # Parses the command and validates options. All errors that are raised here
100
142
  # are not handled by the outputter, as it relies on config being loaded.
101
143
  def parse_command
@@ -107,6 +149,16 @@ module Bolt
107
149
  # help text
108
150
  options[:subcommand] = nil unless COMMANDS.include?(options[:subcommand])
109
151
 
152
+ if Bolt::Util.first_run?
153
+ FileUtils.mkdir_p(Bolt::Util.first_runs_free.dirname)
154
+ FileUtils.touch(Bolt::Util.first_runs_free)
155
+
156
+ if options[:subcommand].nil? && $stdout.isatty
157
+ welcome_message
158
+ raise Bolt::CLIExit
159
+ end
160
+ end
161
+
110
162
  # Update the parser for the subcommand (or lack thereof)
111
163
  parser.update
112
164
  puts parser.help
@@ -175,6 +227,7 @@ module Bolt
175
227
  # Completes the setup process by configuring Bolt and log messages
176
228
  def finalize_setup
177
229
  Bolt::Logger.configure(config.log, config.color, config.disable_warnings)
230
+ Bolt::Logger.stream = config.stream
178
231
  Bolt::Logger.analytics = analytics
179
232
  Bolt::Logger.flush_queue
180
233
 
@@ -212,7 +265,7 @@ module Bolt
212
265
  target_opts = options.keys.select { |opt| %i[query rerun targets].include?(opt) }
213
266
  target_string = "'--targets', '--rerun', or '--query'"
214
267
  if target_opts.length > 1
215
- raise Bolt::CLIError, "Only one targeting option #{target_string} may be specified"
268
+ raise Bolt::CLIError, "Only one targeting option #{target_string} can be specified"
216
269
  elsif target_opts.empty? && options[:subcommand] != 'plan'
217
270
  raise Bolt::CLIError, "Command requires a targeting option: #{target_string}"
218
271
  end
@@ -249,11 +302,14 @@ module Bolt
249
302
  end
250
303
  end
251
304
 
252
- if %w[task plan].include?(options[:subcommand]) && options[:action] == 'run'
305
+ if %w[task plan script].include?(options[:subcommand]) && options[:action] == 'run'
253
306
  if options[:object].nil?
254
307
  raise Bolt::CLIError, "Must specify a #{options[:subcommand]} to run"
255
308
  end
256
- # This may mean that we parsed a parameter as the object
309
+ end
310
+
311
+ # This may mean that we parsed a parameter as the object
312
+ if %w[task plan].include?(options[:subcommand]) && options[:action] == 'run'
257
313
  unless options[:object] =~ /\A([a-z][a-z0-9_]*)?(::[a-z][a-z0-9_]*)*\Z/
258
314
  raise Bolt::CLIError,
259
315
  "Invalid #{options[:subcommand]} '#{options[:object]}'"
@@ -301,13 +357,13 @@ module Bolt
301
357
  if options[:noop] &&
302
358
  !(options[:subcommand] == 'task' && options[:action] == 'run') && options[:subcommand] != 'apply'
303
359
  raise Bolt::CLIError,
304
- "Option '--noop' may only be specified when running a task or applying manifest code"
360
+ "Option '--noop' can only be specified when running a task or applying manifest code"
305
361
  end
306
362
 
307
363
  if options[:env_vars]
308
364
  unless %w[command script].include?(options[:subcommand]) && options[:action] == 'run'
309
365
  raise Bolt::CLIError,
310
- "Option '--env-var' may only be specified when running a command or script"
366
+ "Option '--env-var' can only be specified when running a command or script"
311
367
  end
312
368
  end
313
369
  end
@@ -354,7 +410,7 @@ module Bolt
354
410
  if inventory_source && conflicting_options.any?
355
411
  Bolt::Logger.warn(
356
412
  "cli_overrides",
357
- "CLI arguments #{conflicting_options.to_a} may be overridden by Inventory: #{inventory_source}"
413
+ "CLI arguments #{conflicting_options.to_a} might be overridden by Inventory: #{inventory_source}"
358
414
  )
359
415
  end
360
416
  end
@@ -432,7 +488,7 @@ module Bolt
432
488
  return 0
433
489
  end
434
490
 
435
- message = 'There may be processes left executing on some nodes.'
491
+ message = 'There might be processes left executing on some nodes.'
436
492
 
437
493
  if %w[task plan].include?(options[:subcommand]) && options[:task_options] && !options[:params_parsed] && pal
438
494
  options[:task_options] = pal.parse_params(options[:subcommand], options[:object], options[:task_options])
@@ -501,9 +557,8 @@ module Bolt
501
557
  when 'command'
502
558
  executor.run_command(targets, options[:object], executor_opts)
503
559
  when 'script'
504
- script = options[:object]
505
- validate_file('script', script)
506
- executor.run_script(targets, script, options[:leftovers], executor_opts)
560
+ script_path = find_file(options[:object])
561
+ executor.run_script(targets, script_path, options[:leftovers], executor_opts)
507
562
  when 'task'
508
563
  pal.run_task(options[:object],
509
564
  targets,
@@ -610,7 +665,7 @@ module Bolt
610
665
  if plan_arguments['nodes'] || plan_arguments['targets']
611
666
  key = plan_arguments.include?('nodes') ? 'nodes' : 'targets'
612
667
  raise Bolt::CLIError,
613
- "A plan's '#{key}' parameter may be specified using the --#{key} option, but in that " \
668
+ "A plan's '#{key}' parameter can be specified using the --#{key} option, but in that " \
614
669
  "case it must not be specified as a separate #{key}=<value> parameter nor included " \
615
670
  "in the JSON data passed in the --params option"
616
671
  end
@@ -709,9 +764,11 @@ module Bolt
709
764
  def install_project_modules(project, config, force, resolve)
710
765
  assert_project_file(project)
711
766
 
712
- unless project.modules.any?
713
- outputter.print_message "Project configuration file #{project.project_file} does not "\
714
- "specify any module dependencies. Nothing to do."
767
+ if project.modules.empty? && resolve != false
768
+ outputter.print_message(
769
+ "Project configuration file #{project.project_file} does not "\
770
+ "specify any module dependencies. Nothing to do."
771
+ )
715
772
  return 0
716
773
  end
717
774
 
@@ -838,6 +895,28 @@ module Bolt
838
895
  Bolt::Util.validate_file(type, path, allow_dir)
839
896
  end
840
897
 
898
+ # Returns the path to a file. If the path is an absolute or relative to
899
+ # a file, and the file exists, returns the path as-is. Otherwise, checks if
900
+ # the path is a Puppet file path and looks for the file in a module's files
901
+ # directory.
902
+ #
903
+ def find_file(path)
904
+ unless File.exist?(path) || Pathname.new(path).absolute?
905
+ modulepath = Bolt::Config::Modulepath.new(config.modulepath)
906
+ modules = Bolt::Module.discover(modulepath.full_modulepath, config.project)
907
+ mod, file = path.split(File::SEPARATOR, 2)
908
+
909
+ if modules[mod]
910
+ @logger.debug("Did not find file at #{File.expand_path(path)}, checking in module '#{mod}'")
911
+ path = File.join(modules[mod].path, 'files', file)
912
+ end
913
+ end
914
+
915
+ Bolt::Util.validate_file('script', path)
916
+
917
+ path
918
+ end
919
+
841
920
  def rerun
842
921
  @rerun ||= Bolt::Rerun.new(config.rerunfile, config.save_rerun)
843
922
  end
@@ -866,7 +945,7 @@ module Bolt
866
945
  # If the bundled content directory is empty, Bolt is likely installed as a gem.
867
946
  if ENV['BOLT_GEM'].nil? && incomplete_install?
868
947
  msg = <<~MSG.chomp
869
- Bolt may be installed as a gem. To use Bolt reliably and with all of its
948
+ Bolt might be installed as a gem. To use Bolt reliably and with all of its
870
949
  dependencies, uninstall the 'bolt' gem and install Bolt as a package:
871
950
  https://puppet.com/docs/bolt/latest/bolt_installing.html
872
951
 
@@ -897,7 +976,8 @@ module Bolt
897
976
  # Gem installs include the aggregate, canary, and puppetdb_fact modules, while
898
977
  # package installs include modules listed in the Bolt repo Puppetfile
899
978
  def incomplete_install?
900
- (Dir.children(Bolt::Config::Modulepath::MODULES_PATH) - %w[aggregate canary puppetdb_fact secure_env_vars]).empty?
979
+ builtin_module_list = %w[aggregate canary puppetdb_fact secure_env_vars puppet_connect]
980
+ (Dir.children(Bolt::Config::Modulepath::MODULES_PATH) - builtin_module_list).empty?
901
981
  end
902
982
 
903
983
  # Mimicks the output from Outputter::Human#fatal_error. This should be used to print
data/lib/bolt/config.rb CHANGED
@@ -419,6 +419,10 @@ module Bolt
419
419
  @data['spinner']
420
420
  end
421
421
 
422
+ def stream
423
+ @data['stream']
424
+ end
425
+
422
426
  def inventoryfile
423
427
  @data['inventoryfile']
424
428
  end
@@ -4,6 +4,7 @@ require 'bolt/config/transport/ssh'
4
4
  require 'bolt/config/transport/winrm'
5
5
  require 'bolt/config/transport/orch'
6
6
  require 'bolt/config/transport/local'
7
+ require 'bolt/config/transport/lxd'
7
8
  require 'bolt/config/transport/docker'
8
9
  require 'bolt/config/transport/remote'
9
10
 
@@ -17,6 +18,7 @@ module Bolt
17
18
  'winrm' => Bolt::Config::Transport::WinRM,
18
19
  'pcp' => Bolt::Config::Transport::Orch,
19
20
  'local' => Bolt::Config::Transport::Local,
21
+ 'lxd' => Bolt::Config::Transport::LXD,
20
22
  'docker' => Bolt::Config::Transport::Docker,
21
23
  'remote' => Bolt::Config::Transport::Remote
22
24
  }.freeze
@@ -423,7 +425,14 @@ module Bolt
423
425
  _example: false,
424
426
  _default: true
425
427
  },
426
-
428
+ "stream" => {
429
+ description: "Whether to stream output from scripts and commands to the console. "\
430
+ "**This option is experimental**.",
431
+ type: [TrueClass, FalseClass],
432
+ _plugin: false,
433
+ _default: false,
434
+ _example: true
435
+ },
427
436
  "tasks" => {
428
437
  description: "A list of task names and glob patterns to filter the project's tasks by. This option is used "\
429
438
  "to limit the visibility of tasks for users of the project. For example, project authors "\
@@ -440,8 +449,8 @@ module Bolt
440
449
  },
441
450
  "trusted-external-command" => {
442
451
  description: "The path to an executable on the Bolt controller that can produce external trusted facts. "\
443
- "**External trusted facts are experimental in both Puppet and Bolt and this API may change or "\
444
- "be removed.**",
452
+ "**External trusted facts are experimental in both Puppet and Bolt and this API might "\
453
+ "change or be removed.**",
445
454
  type: String,
446
455
  _plugin: false,
447
456
  _example: "/etc/puppetlabs/facts/trusted_external.sh"
@@ -475,6 +484,13 @@ module Bolt
475
484
  _plugin: true,
476
485
  _example: { "cleanup" => false, "tmpdir" => "/tmp/bolt" }
477
486
  },
487
+ "lxd" => {
488
+ description: "A map of configuration options for the LXD transport. The LXD transport is "\
489
+ "experimental and might include breaking changes between minor versions.",
490
+ type: Hash,
491
+ _plugin: true,
492
+ _example: { cleanup: false }
493
+ },
478
494
  "pcp" => {
479
495
  description: "A map of configuration options for the pcp transport.",
480
496
  type: Hash,
@@ -530,6 +546,7 @@ module Bolt
530
546
  puppetdb
531
547
  save-rerun
532
548
  spinner
549
+ stream
533
550
  ].freeze
534
551
 
535
552
  # Options that are available in a bolt-project.yaml file
@@ -553,6 +570,7 @@ module Bolt
553
570
  puppetdb
554
571
  save-rerun
555
572
  spinner
573
+ stream
556
574
  tasks
557
575
  trusted-external-command
558
576
  ].freeze