bolt 2.19.0 → 2.24.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +3 -1
  3. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +123 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +6 -0
  5. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +12 -6
  6. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
  7. data/bolt-modules/out/lib/puppet/functions/out/message.rb +1 -1
  8. data/exe/bolt +1 -0
  9. data/guides/inventory.txt +19 -0
  10. data/guides/project.txt +22 -0
  11. data/lib/bolt/analytics.rb +5 -5
  12. data/lib/bolt/applicator.rb +4 -3
  13. data/lib/bolt/bolt_option_parser.rb +100 -27
  14. data/lib/bolt/catalog.rb +12 -3
  15. data/lib/bolt/cli.rb +356 -156
  16. data/lib/bolt/config.rb +2 -2
  17. data/lib/bolt/config/options.rb +18 -4
  18. data/lib/bolt/executor.rb +30 -7
  19. data/lib/bolt/inventory/group.rb +6 -5
  20. data/lib/bolt/inventory/inventory.rb +4 -3
  21. data/lib/bolt/logger.rb +3 -4
  22. data/lib/bolt/module.rb +2 -1
  23. data/lib/bolt/outputter.rb +56 -0
  24. data/lib/bolt/outputter/human.rb +10 -9
  25. data/lib/bolt/outputter/json.rb +11 -4
  26. data/lib/bolt/outputter/logger.rb +2 -2
  27. data/lib/bolt/outputter/rainbow.rb +18 -2
  28. data/lib/bolt/pal.rb +13 -11
  29. data/lib/bolt/pal/yaml_plan/evaluator.rb +22 -1
  30. data/lib/bolt/pal/yaml_plan/step.rb +24 -2
  31. data/lib/bolt/pal/yaml_plan/step/download.rb +38 -0
  32. data/lib/bolt/pal/yaml_plan/step/message.rb +30 -0
  33. data/lib/bolt/pal/yaml_plan/step/upload.rb +3 -3
  34. data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
  35. data/lib/bolt/plugin/prompt.rb +3 -3
  36. data/lib/bolt/plugin/puppetdb.rb +3 -2
  37. data/lib/bolt/project.rb +7 -4
  38. data/lib/bolt/project_migrate.rb +138 -0
  39. data/lib/bolt/puppetdb/client.rb +2 -0
  40. data/lib/bolt/puppetdb/config.rb +16 -0
  41. data/lib/bolt/result.rb +7 -0
  42. data/lib/bolt/shell/bash.rb +31 -11
  43. data/lib/bolt/shell/powershell.rb +10 -4
  44. data/lib/bolt/transport/base.rb +24 -0
  45. data/lib/bolt/transport/docker.rb +8 -0
  46. data/lib/bolt/transport/docker/connection.rb +28 -10
  47. data/lib/bolt/transport/local/connection.rb +15 -2
  48. data/lib/bolt/transport/orch.rb +15 -3
  49. data/lib/bolt/transport/simple.rb +6 -0
  50. data/lib/bolt/transport/ssh/connection.rb +13 -5
  51. data/lib/bolt/transport/ssh/exec_connection.rb +24 -3
  52. data/lib/bolt/transport/winrm/connection.rb +125 -15
  53. data/lib/bolt/util.rb +27 -12
  54. data/lib/bolt/util/puppet_log_level.rb +4 -3
  55. data/lib/bolt/version.rb +1 -1
  56. data/lib/bolt_server/base_config.rb +1 -1
  57. data/lib/bolt_server/pe/pal.rb +1 -1
  58. data/lib/bolt_server/transport_app.rb +79 -2
  59. data/lib/bolt_spec/bolt_context.rb +7 -2
  60. data/lib/bolt_spec/plans.rb +16 -3
  61. data/lib/bolt_spec/plans/action_stubs.rb +3 -2
  62. data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
  63. data/lib/bolt_spec/plans/mock_executor.rb +14 -1
  64. data/lib/bolt_spec/run.rb +22 -0
  65. data/libexec/apply_catalog.rb +2 -2
  66. data/libexec/bolt_catalog +4 -3
  67. data/libexec/custom_facts.rb +1 -1
  68. data/libexec/query_resources.rb +1 -1
  69. data/modules/secure_env_vars/plans/init.pp +20 -0
  70. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84c1deb7ddf2b30daf415a36f7949544d3113db8ae05cf927516cf0ecfb21ca8
4
- data.tar.gz: 233d6da93d218e63e50287cbcb5ea863a16f15aa8ae6b02c62f26e87d1d43197
3
+ metadata.gz: 1e572a2c5aec9f127f48764b6a2fe903c91566c8aa0f3a1db33e40347bd1630b
4
+ data.tar.gz: 1445543fb941e0b8c12ec60900975a45e368a08e593ebe75c87ee946317f57b7
5
5
  SHA512:
6
- metadata.gz: a9432c3f9d79ae864971ed4e37cd258fe884b5f96addb7dac19688b9b0a8e9b8044a686321b8f6dbaecf0fdfd41ba962ae9b67d0dbc88744357acd8fafa07e15
7
- data.tar.gz: 0a62d1499e578c06900855c5cca20e834a73a7a572f81feb602e60fe72775c02cfba97c39a224477b69098948bf2d40c4dc4229ff4e43d3cb784d4d787dc2d85
6
+ metadata.gz: 4cdee0a056e3c248a72ea3a4cb1872453611ca241d19ad3c98f5e0e89c550368ab19e34999e121f0e98c5e5cac353fed32eb174fb54fd149bd4558bd84e5706e
7
+ data.tar.gz: d505e8baf7c6ce5b21c20e27d7979c39c871e5bc69ea7bcf5d898ac94261850afea03028569ed60e192196d215837a2b339fef24231ea9491bc4901e93cba3dd
data/Puppetfile CHANGED
@@ -5,7 +5,7 @@ forge "http://forge.puppetlabs.com"
5
5
  moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
- mod 'puppetlabs-service', '1.2.0'
8
+ mod 'puppetlabs-service', '1.3.0'
9
9
  mod 'puppetlabs-puppet_agent', '3.2.0'
10
10
  mod 'puppetlabs-facts', '1.0.0'
11
11
 
@@ -28,6 +28,7 @@ mod 'puppetlabs-python_task_helper', '0.4.3'
28
28
  mod 'puppetlabs-reboot', '3.0.0'
29
29
  mod 'puppetlabs-ruby_task_helper', '0.5.1'
30
30
  mod 'puppetlabs-ruby_plugin_helper', '0.1.0'
31
+ mod 'puppetlabs-stdlib', '6.3.0'
31
32
 
32
33
  # Plugin modules
33
34
  mod 'puppetlabs-aws_inventory', '0.5.0'
@@ -42,3 +43,4 @@ mod 'puppetlabs-yaml', '0.2.0'
42
43
  mod 'canary', local: true
43
44
  mod 'aggregate', local: true
44
45
  mod 'puppetdb_fact', local: true
46
+ mod 'secure_env_vars', local: true
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'bolt/error'
5
+
6
+ # Downloads the given file or directory from the given set of targets and saves it to a directory
7
+ # matching the target's name under the given destination directory. Returns the result from each
8
+ # download. This does nothing if the list of targets is empty.
9
+ #
10
+ # > **Note:** Existing content in the destination directory is deleted before downloading from
11
+ # > the targets.
12
+ #
13
+ # > **Note:** Not available in apply block
14
+ Puppet::Functions.create_function(:download_file, Puppet::Functions::InternalFunction) do
15
+ # Download a file or directory.
16
+ # @param source The absolute path to the file or directory on the target(s).
17
+ # @param destination The relative path to the destination directory on the local system. Expands
18
+ # relative to `<project>/downloads/`.
19
+ # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
20
+ # @param options A hash of additional options.
21
+ # @option options [Boolean] _catch_errors Whether to catch raised errors.
22
+ # @option options [String] _run_as User to run as using privilege escalation.
23
+ # @return A list of results, one entry per target, with the path to the downloaded file under the
24
+ # `path` key.
25
+ # @example Download a file from multiple Linux targets to a destination directory
26
+ # download_file('/etc/ssh/ssh_config', '~/Downloads', $targets)
27
+ # @example Download a directory from multiple Linux targets to a project downloads directory
28
+ # download_file('/etc/ssh', 'ssh', $targets)
29
+ # @example Download a file from multiple Linux targets and compare its contents to a local file
30
+ # $results = download_file($source, $destination, $targets)
31
+ #
32
+ # $local_content = file::read($source)
33
+ #
34
+ # $mismatched_files = $results.filter |$result| {
35
+ # $remote_content = file::read($result['path'])
36
+ # $remote_content == $local_content
37
+ # }
38
+ dispatch :download_file do
39
+ param 'String[1]', :source
40
+ param 'String[1]', :destination
41
+ param 'Boltlib::TargetSpec', :targets
42
+ optional_param 'Hash[String[1], Any]', :options
43
+ return_type 'ResultSet'
44
+ end
45
+
46
+ # Download a file or directory, logging the provided description.
47
+ # @param source The absolute path to the file or directory on the target(s).
48
+ # @param destination The relative path to the destination directory on the local system. Expands
49
+ # relative to `<project>/downloads/`.
50
+ # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
51
+ # @param description A description to be output when calling this function.
52
+ # @param options A hash of additional options.
53
+ # @option options [Boolean] _catch_errors Whether to catch raised errors.
54
+ # @option options [String] _run_as User to run as using privilege escalation.
55
+ # @return A list of results, one entry per target, with the path to the downloaded file under the
56
+ # `path` key.
57
+ # @example Download a file from multiple Linux targets to a destination directory
58
+ # download_file('/etc/ssh/ssh_config', '~/Downloads', $targets, 'Downloading remote SSH config')
59
+ dispatch :download_file_with_description do
60
+ param 'String[1]', :source
61
+ param 'String[1]', :destination
62
+ param 'Boltlib::TargetSpec', :targets
63
+ param 'String', :description
64
+ optional_param 'Hash[String[1], Any]', :options
65
+ return_type 'ResultSet'
66
+ end
67
+
68
+ def download_file(source, destination, targets, options = {})
69
+ download_file_with_description(source, destination, targets, nil, options)
70
+ end
71
+
72
+ def download_file_with_description(source, destination, targets, description = nil, options = {})
73
+ unless Puppet[:tasks]
74
+ raise Puppet::ParseErrorWithIssue
75
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'download_file')
76
+ end
77
+
78
+ options = options.select { |opt| opt.start_with?('_') }.transform_keys { |k| k.sub(/^_/, '').to_sym }
79
+ options[:description] = description if description
80
+
81
+ executor = Puppet.lookup(:bolt_executor)
82
+ inventory = Puppet.lookup(:bolt_inventory)
83
+
84
+ if (destination = destination.strip).empty?
85
+ raise Bolt::ValidationError, "Destination cannot be an empty string"
86
+ end
87
+
88
+ if (destination = Pathname.new(destination)).absolute?
89
+ raise Bolt::ValidationError, "Destination must be a relative path, received absolute path #{destination}"
90
+ end
91
+
92
+ # Prevent path traversal so downloads can't be saved outside of the project downloads directory
93
+ if (destination.each_filename.to_a & %w[. ..]).any?
94
+ raise Bolt::ValidationError, "Destination must not include path traversal, received #{destination}"
95
+ end
96
+
97
+ # Paths expand relative to the default downloads directory for the project
98
+ # e.g. ~/.puppetlabs/bolt/downloads/
99
+ destination = Puppet.lookup(:bolt_project_data).downloads + destination
100
+
101
+ # If the destination directory already exists, delete any existing contents
102
+ if Dir.exist?(destination)
103
+ FileUtils.rm_r(Dir.glob(destination + '*'), secure: true)
104
+ end
105
+
106
+ # Send Analytics Report
107
+ executor.report_function_call(self.class.name)
108
+
109
+ # Ensure that that given targets are all Target instances
110
+ targets = inventory.get_targets(targets)
111
+ if targets.empty?
112
+ call_function('debug', "Simulating file download of '#{source}' - no targets given - no action taken")
113
+ r = Bolt::ResultSet.new([])
114
+ else
115
+ r = executor.download_file(targets, source, destination, options)
116
+ end
117
+
118
+ if !r.ok && !options[:catch_errors]
119
+ raise Bolt::RunFailure.new(r, 'download_file', source)
120
+ end
121
+ r
122
+ end
123
+ end
@@ -11,6 +11,9 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
11
11
  # @param args A hash of arguments to the plan. Can also include additional options.
12
12
  # @option args [Boolean] _catch_errors Whether to catch raised errors.
13
13
  # @option args [String] _run_as User to run as using privilege escalation.
14
+ # This option sets the [run-as user](privilege_escalation.md) for all
15
+ # targets whenever Bolt connects to a target. This is set for all functions
16
+ # in the called plan, including `run_plan()`.
14
17
  # @return [PlanResult] The result of running the plan. Undef if plan does not explicitly return results.
15
18
  # @example Run a plan
16
19
  # run_plan('canary', 'command' => 'false', 'targets' => $targets, '_catch_errors' => true)
@@ -31,6 +34,9 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
31
34
  # @param args A hash of arguments to the plan. Can also include additional options.
32
35
  # @option args [Boolean] _catch_errors Whether to catch raised errors.
33
36
  # @option args [String] _run_as User to run as using privilege escalation.
37
+ # This option sets the [run-as user](privilege_escalation.md) for all
38
+ # targets whenever Bolt connects to a target. This is set for all functions
39
+ # in the called plan, including `run_plan()`.
34
40
  # @return [PlanResult] The result of running the plan. Undef if plan does not explicitly return results.
35
41
  # @example Run a plan
36
42
  # run_plan('canary', $targets, 'command' => 'false')
@@ -4,30 +4,36 @@
4
4
  Puppet::Functions.create_function(:'ctrl::do_until') do
5
5
  # @param options A hash of additional options.
6
6
  # @option options [Numeric] limit The number of times to repeat the block.
7
+ # @option options [Numeric] interval The number of seconds to wait before repeating the block.
7
8
  # @example Run a task until it succeeds
8
9
  # ctrl::do_until() || {
9
- # run_task('test', $target, _catch_errors => true).ok()
10
+ # run_task('test', $target, '_catch_errors' => true).ok()
10
11
  # }
11
- #
12
12
  # @example Run a task until it succeeds or fails 10 times
13
13
  # ctrl::do_until('limit' => 10) || {
14
- # run_task('test', $target, _catch_errors => true).ok()
14
+ # run_task('test', $target, '_catch_errors' => true).ok()
15
+ # }
16
+ # @example Run a task and wait 10 seconds before running it again
17
+ # ctrl::do_until('interval' => 10) || {
18
+ # run_task('test', $target, '_catch_errors' => true).ok()
15
19
  # }
16
- #
17
20
  dispatch :do_until do
18
21
  optional_param 'Hash[String[1], Any]', :options
19
22
  block_param
20
23
  end
21
24
 
22
- def do_until(options = { 'limit' => 0 })
25
+ def do_until(options = {})
23
26
  # Send Analytics Report
24
27
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
25
28
 
26
- limit = options['limit']
29
+ limit = options['limit'] || 0
30
+ interval = options['interval']
31
+
27
32
  i = 0
28
33
  until (x = yield)
29
34
  i += 1
30
35
  break if limit != 0 && i >= limit
36
+ Kernel.sleep(interval) if interval
31
37
  end
32
38
  x
33
39
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ # Returns an array containing all of the filenames except for "." and ".." in the given directory.
6
+ Puppet::Functions.create_function(:'dir::children', Puppet::Functions::InternalFunction) do
7
+ # @param dirname Absolute path or Puppet module name.
8
+ # @return Array of files in the given directory.
9
+ # @example List filenames from an absolute path.
10
+ # dir::children('/home/user/subdir/')
11
+ # @example List filenames from a Puppet file path.
12
+ # dir::children('puppet_agent')
13
+ dispatch :children do
14
+ scope_param
15
+ required_param 'String', :dirname
16
+ return_type 'Array'
17
+ end
18
+
19
+ def children(scope, dirname)
20
+ # Send Analytics Report
21
+ Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
22
+ modname, subpath = dirname.split(File::SEPARATOR, 2)
23
+ mod_path = scope.compiler.environment.module(modname)&.path
24
+
25
+ full_mod_path = File.join(mod_path, subpath || '') if mod_path
26
+
27
+ # Expand relative to the project directory if path is relative
28
+ project = Puppet.lookup(:bolt_project_data)
29
+ pathname = Pathname.new(dirname)
30
+ full_dir = pathname.absolute? ? dirname : File.expand_path(File.join(project.path, dirname))
31
+
32
+ # Sort for testability
33
+ Dir.children(full_mod_path || full_dir).sort
34
+ end
35
+ end
@@ -12,7 +12,7 @@ Puppet::Functions.create_function(:'out::message') do
12
12
  # @example Print a message
13
13
  # out::message('Something went wrong')
14
14
  dispatch :output_message do
15
- param 'String', :message
15
+ param 'Any', :message
16
16
  return_type 'Undef'
17
17
  end
18
18
 
data/exe/bolt CHANGED
@@ -4,6 +4,7 @@
4
4
  require 'bolt'
5
5
  require 'bolt/cli'
6
6
 
7
+ Thread.current[:name] ||= 'main'
7
8
  cli = Bolt::CLI.new(ARGV)
8
9
  begin
9
10
  opts = cli.parse
@@ -0,0 +1,19 @@
1
+ TOPIC
2
+ inventory
3
+
4
+ DESCRIPTION
5
+ The inventory describes the targets that you run Bolt commands on, along
6
+ with any data and configuration for the targets. Targets in an inventory can
7
+ belong to one or more groups, allowing you to share data and configuration
8
+ across multiple targets and to specify multiple targets for your Bolt
9
+ commands without the need to list each target individually.
10
+
11
+ In most cases, Bolt loads the inventory from an inventory file in your Bolt
12
+ project. The inventory file is a YAML file named 'inventory.yaml'. Because
13
+ Bolt loads the inventory file from a Bolt project, you must have an existing
14
+ project configuration file named 'bolt-project.yaml' alongside the inventory
15
+ file.
16
+
17
+ DOCUMENTATION
18
+ https://pup.pt/bolt-inventory
19
+ https://pup.pt/bolt-inventory-reference
@@ -0,0 +1,22 @@
1
+ TOPIC
2
+ project
3
+
4
+ DESCRIPTION
5
+ A Bolt project is a directory that serves as the launching point for Bolt
6
+ and allows you to create a shareable orchestration application. Projects
7
+ typically include a project configuration file, an inventory file, and any
8
+ content you use in your project workflow, such as tasks and plans.
9
+
10
+ When you run Bolt, it runs in the context of a project. If the directory you
11
+ run Bolt from is not a project, Bolt attempts to find a project by
12
+ traversing the parent directories. If Bolt is unable to find a project, it
13
+ runs from the default project, located at '~/.puppetlabs/bolt'.
14
+
15
+ A directory is only considered a Bolt project when it has a project
16
+ configuration file named 'bolt-project.yaml'. Bolt doesn't load project data
17
+ and content, including inventory files, unless the data and content are part
18
+ of a project.
19
+
20
+ DOCUMENTATION
21
+ https://pup.pt/bolt-projects
22
+ https://pup.pt/bolt-project-reference
@@ -72,7 +72,7 @@ module Bolt
72
72
 
73
73
  def self.load_config(filename, logger)
74
74
  if File.exist?(filename)
75
- YAML.load_file(filename)
75
+ Bolt::Util.read_optional_yaml_hash(filename, 'analytics')
76
76
  else
77
77
  unless ENV['BOLT_DISABLE_ANALYTICS']
78
78
  logger.warn <<~ANALYTICS
@@ -161,9 +161,9 @@ module Bolt
161
161
  # Handle analytics submission in the background to avoid blocking the
162
162
  # app or polluting the log with errors
163
163
  Concurrent::Future.execute(executor: @executor) do
164
- @logger.debug "Submitting analytics: #{JSON.pretty_generate(params)}"
164
+ @logger.trace "Submitting analytics: #{JSON.pretty_generate(params)}"
165
165
  @http.post(TRACKING_URL, params)
166
- @logger.debug "Completed analytics submission"
166
+ @logger.trace "Completed analytics submission"
167
167
  end
168
168
  end
169
169
 
@@ -215,13 +215,13 @@ module Bolt
215
215
  end
216
216
 
217
217
  def screen_view(screen, **_kwargs)
218
- @logger.debug "Skipping submission of '#{screen}' screenview because analytics is disabled"
218
+ @logger.trace "Skipping submission of '#{screen}' screenview because analytics is disabled"
219
219
  end
220
220
 
221
221
  def report_bundled_content(mode, name); end
222
222
 
223
223
  def event(category, action, **_kwargs)
224
- @logger.debug "Skipping submission of '#{category} #{action}' event because analytics is disabled"
224
+ @logger.trace "Skipping submission of '#{category} #{action}' event because analytics is disabled"
225
225
  end
226
226
 
227
227
  def finish; end
@@ -27,7 +27,7 @@ module Bolt
27
27
  @hiera_config = hiera_config ? validate_hiera_config(hiera_config) : nil
28
28
  @apply_settings = apply_settings || {}
29
29
 
30
- @pool = Concurrent::ThreadPoolExecutor.new(max_threads: max_compiles)
30
+ @pool = Concurrent::ThreadPoolExecutor.new(name: 'apply', max_threads: max_compiles)
31
31
  @logger = Logging.logger[self]
32
32
  end
33
33
 
@@ -217,6 +217,7 @@ module Bolt
217
217
  r = @executor.log_action(description, targets) do
218
218
  futures = targets.map do |target|
219
219
  Concurrent::Future.execute(executor: @pool) do
220
+ Thread.current[:name] ||= Thread.current.name
220
221
  @executor.with_node_logging("Compiling manifest block", [target]) do
221
222
  compile(target, scope)
222
223
  end
@@ -300,7 +301,7 @@ module Bolt
300
301
 
301
302
  files.each do |file|
302
303
  tar_path = Pathname.new(file).relative_path_from(parent)
303
- @logger.debug("Packing plugin #{file} to #{tar_path}")
304
+ @logger.trace("Packing plugin #{file} to #{tar_path}")
304
305
  stat = File.stat(file)
305
306
  content = File.binread(file)
306
307
  output.tar.add_file_simple(
@@ -314,7 +315,7 @@ module Bolt
314
315
  end
315
316
 
316
317
  duration = Time.now - start_time
317
- @logger.debug("Packed plugins in #{duration * 1000} ms")
318
+ @logger.trace("Packed plugins in #{duration * 1000} ms")
318
319
 
319
320
  output.close
320
321
  Base64.encode64(sio.string)
@@ -25,7 +25,7 @@ module Bolt
25
25
  when 'command'
26
26
  case action
27
27
  when 'run'
28
- { flags: ACTION_OPTS,
28
+ { flags: ACTION_OPTS + %w[env-var],
29
29
  banner: COMMAND_RUN_HELP }
30
30
  else
31
31
  { flags: OPTIONS[:global],
@@ -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 }
@@ -58,11 +61,17 @@ module Bolt
58
61
  { flags: OPTIONS[:global],
59
62
  banner: GROUP_HELP }
60
63
  end
64
+ when 'guide'
65
+ { flags: OPTIONS[:global] + %w[format],
66
+ banner: GUIDE_HELP }
61
67
  when 'plan'
62
68
  case action
63
69
  when 'convert'
64
70
  { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
65
71
  banner: PLAN_CONVERT_HELP }
72
+ when 'new'
73
+ { flags: OPTIONS[:global] + %w[configfile project],
74
+ banner: PLAN_NEW_HELP }
66
75
  when 'run'
67
76
  { flags: ACTION_OPTS + %w[params compile-concurrency tmpdir hiera-config],
68
77
  banner: PLAN_RUN_HELP }
@@ -79,7 +88,7 @@ module Bolt
79
88
  { flags: OPTIONS[:global] + %w[modules],
80
89
  banner: PROJECT_INIT_HELP }
81
90
  when 'migrate'
82
- { flags: OPTIONS[:global] + %w[inventoryfile boltdir configfile],
91
+ { flags: OPTIONS[:global] + %w[inventoryfile project configfile],
83
92
  banner: PROJECT_MIGRATE_HELP }
84
93
  else
85
94
  { flags: OPTIONS[:global],
@@ -103,7 +112,7 @@ module Bolt
103
112
  when 'script'
104
113
  case action
105
114
  when 'run'
106
- { flags: ACTION_OPTS + %w[tmpdir],
115
+ { flags: ACTION_OPTS + %w[tmpdir env-var],
107
116
  banner: SCRIPT_RUN_HELP }
108
117
  else
109
118
  { flags: OPTIONS[:global],
@@ -156,15 +165,19 @@ module Bolt
156
165
  SUBCOMMANDS
157
166
  apply Apply Puppet manifest code
158
167
  command Run a command remotely
159
- file Upload a local file or directory
168
+ file Copy files between the controller and targets
160
169
  group Show the list of groups in the inventory
170
+ guide View guides for Bolt concepts and features
161
171
  inventory Show the list of targets an action would run on
162
- plan Convert, show, and run Bolt plans
172
+ plan Convert, create, show, and run Bolt plans
163
173
  project Create and migrate Bolt projects
164
174
  puppetfile Install and list modules and generate type references
165
175
  script Upload a local script and run it remotely
166
176
  secret Create encryption keys and encrypt and decrypt values
167
177
  task Show and run Bolt tasks
178
+
179
+ GUIDES
180
+ For a list of guides on Bolt's concepts and features, run 'bolt guide'.
168
181
  HELP
169
182
 
170
183
  APPLY_HELP = <<~HELP
@@ -218,10 +231,30 @@ module Bolt
218
231
  bolt file <action> [options]
219
232
 
220
233
  DESCRIPTION
221
- Upload a local file or directory
234
+ Copy files and directories between the controller and targets
222
235
 
223
236
  ACTIONS
224
- upload Upload a local file or directory
237
+ download Download a file or directory to the controller
238
+ upload Upload a local file or directory from the controller
239
+ HELP
240
+
241
+ FILE_DOWNLOAD_HELP = <<~HELP
242
+ NAME
243
+ download
244
+
245
+ USAGE
246
+ bolt file download <src> <dest> [options]
247
+
248
+ DESCRIPTION
249
+ Download a file or directory from one or more targets.
250
+
251
+ Downloaded files and directories are saved to the a subdirectory
252
+ matching the target's name under the destination directory. The
253
+ destination directory is expanded relative to the downloads
254
+ subdirectory of the project directory.
255
+
256
+ EXAMPLES
257
+ bolt file download /etc/ssh_config ssh_config -t all
225
258
  HELP
226
259
 
227
260
  FILE_UPLOAD_HELP = <<~HELP
@@ -263,6 +296,26 @@ module Bolt
263
296
  Show the list of groups in the inventory.
264
297
  HELP
265
298
 
299
+ GUIDE_HELP = <<~HELP
300
+ NAME
301
+ guide
302
+
303
+ USAGE
304
+ bolt guide [topic] [options]
305
+
306
+ DESCRIPTION
307
+ View guides for Bolt's concepts and features.
308
+
309
+ Omitting a topic will display a list of available guides,
310
+ while providing a topic will display the relevant guide.
311
+
312
+ EXAMPLES
313
+ View a list of available guides
314
+ bolt guide
315
+ View the 'project' guide page
316
+ bolt guide project
317
+ HELP
318
+
266
319
  INVENTORY_HELP = <<~HELP
267
320
  NAME
268
321
  inventory
@@ -296,10 +349,11 @@ module Bolt
296
349
  bolt plan <action> [parameters] [options]
297
350
 
298
351
  DESCRIPTION
299
- Convert, show, and run Bolt plans.
352
+ Convert, create, show, and run Bolt plans.
300
353
 
301
354
  ACTIONS
302
355
  convert Convert a YAML plan to a Bolt plan
356
+ new Create a new plan in the current project
303
357
  run Run a plan on the specified targets
304
358
  show Show available plans and plan documentation
305
359
  HELP
@@ -322,6 +376,20 @@ module Bolt
322
376
  bolt plan convert path/to/plan/myplan.yaml
323
377
  HELP
324
378
 
379
+ PLAN_NEW_HELP = <<~HELP
380
+ NAME
381
+ new
382
+
383
+ USAGE
384
+ bolt plan new <plan> [options]
385
+
386
+ DESCRIPTION
387
+ Create a new plan in the current project.
388
+
389
+ EXAMPLES
390
+ bolt plan new myproject::myplan
391
+ HELP
392
+
325
393
  PLAN_RUN_HELP = <<~HELP
326
394
  NAME
327
395
  run
@@ -379,19 +447,18 @@ module Bolt
379
447
  init
380
448
 
381
449
  USAGE
382
- bolt project init [directory] [options]
450
+ bolt project init [name] [options]
383
451
 
384
452
  DESCRIPTION
385
- Create a new Bolt project.
453
+ Create a new Bolt project in the current working directory.
386
454
 
387
- Specify a directory to create a Bolt project in. Defaults to the
388
- curent working directory.
455
+ Specify a name for the Bolt project. Defaults to the basename of the current working directory.
389
456
 
390
457
  EXAMPLES
391
- Create a new Bolt project in the current working directory.
458
+ Create a new Bolt project using the directory as the project name.
392
459
  bolt project init
393
- Create a new Bolt project at a specified path.
394
- bolt project init ~/path/to/project
460
+ Create a new Bolt project with a specified name.
461
+ bolt project init myproject
395
462
  Create a new Bolt project with existing modules.
396
463
  bolt project init --modules puppetlabs-apt,puppetlabs-ntp
397
464
  HELP
@@ -404,10 +471,7 @@ module Bolt
404
471
  bolt project migrate [options]
405
472
 
406
473
  DESCRIPTION
407
- Migrate a Bolt project to the latest version.
408
-
409
- Loads a Bolt project's inventory file and migrates it to the latest version. The
410
- inventory file is modified in place and will not preserve comments or formatting.
474
+ Migrate a Bolt project to use current best practices and the latest version of configuration files.
411
475
  HELP
412
476
 
413
477
  PUPPETFILE_HELP = <<~HELP
@@ -653,9 +717,9 @@ module Bolt
653
717
  @options[:password] = password
654
718
  end
655
719
  define('--password-prompt', 'Prompt for user to input password') do |_password|
656
- STDERR.print "Please enter your password: "
657
- @options[:password] = STDIN.noecho(&:gets).chomp
658
- STDERR.puts
720
+ $stderr.print "Please enter your password: "
721
+ @options[:password] = $stdin.noecho(&:gets).chomp
722
+ $stderr.puts
659
723
  end
660
724
  define('--private-key KEY', 'Path to private ssh key to authenticate with') do |key|
661
725
  @options[:'private-key'] = File.expand_path(key)
@@ -679,9 +743,9 @@ module Bolt
679
743
  @options[:'sudo-password'] = password
680
744
  end
681
745
  define('--sudo-password-prompt', 'Prompt for user to input escalation password') do |_password|
682
- STDERR.print "Please enter your privilege escalation password: "
683
- @options[:'sudo-password'] = STDIN.noecho(&:gets).chomp
684
- STDERR.puts
746
+ $stderr.print "Please enter your privilege escalation password: "
747
+ @options[:'sudo-password'] = $stdin.noecho(&:gets).chomp
748
+ $stderr.puts
685
749
  end
686
750
  define('--sudo-executable EXEC', "Specify an executable for running as another user.",
687
751
  "This option is experimental.") do |exec|
@@ -738,6 +802,15 @@ module Bolt
738
802
  @options[:'save-rerun'] = save
739
803
  end
740
804
 
805
+ separator "\nREMOTE ENVIRONMENT OPTIONS"
806
+ define('--env-var ENVIRONMENT_VARIABLES', 'Environment variables to set on the target') do |envvar|
807
+ unless envvar.include?('=')
808
+ raise Bolt::CLIError, "Environment variables must be specified using 'myenvvar=key' format"
809
+ end
810
+ @options[:env_vars] ||= {}
811
+ @options[:env_vars].store(*envvar.split('=', 2))
812
+ end
813
+
741
814
  separator "\nTRANSPORT OPTIONS"
742
815
  define('--transport TRANSPORT', TRANSPORTS.keys.map(&:to_s),
743
816
  "Specify a default transport: #{TRANSPORTS.keys.join(', ')}") do |t|
@@ -814,7 +887,7 @@ module Bolt
814
887
  end
815
888
  define('--log-level LEVEL',
816
889
  "Set the log level for the console. Available options are",
817
- "debug, info, notice, warn, error, fatal, any.") do |level|
890
+ "trace, debug, info, warn, error, fatal, any.") do |level|
818
891
  @options[:log] = { 'console' => { 'level' => level } }
819
892
  end
820
893
  define('--plugin PLUGIN', 'Select the plugin to use') do |plug|
@@ -855,7 +928,7 @@ module Bolt
855
928
  file = value.sub(/^@/, '')
856
929
  read_arg_file(file)
857
930
  elsif value == '-'
858
- STDIN.read
931
+ $stdin.read
859
932
  else
860
933
  value
861
934
  end