bolt 2.29.0 → 2.33.2

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +15 -14
  3. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +6 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +1 -1
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +1 -1
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +1 -1
  10. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +2 -2
  12. data/bolt-modules/out/lib/puppet/functions/out/message.rb +44 -1
  13. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +3 -0
  14. data/guides/logging.txt +18 -0
  15. data/guides/module.txt +19 -0
  16. data/guides/modulepath.txt +25 -0
  17. data/lib/bolt/bolt_option_parser.rb +48 -9
  18. data/lib/bolt/catalog.rb +1 -1
  19. data/lib/bolt/cli.rb +154 -116
  20. data/lib/bolt/config.rb +13 -1
  21. data/lib/bolt/config/modulepath.rb +30 -0
  22. data/lib/bolt/config/options.rb +32 -13
  23. data/lib/bolt/config/transport/options.rb +2 -2
  24. data/lib/bolt/error.rb +4 -0
  25. data/lib/bolt/executor.rb +13 -13
  26. data/lib/bolt/inventory.rb +10 -9
  27. data/lib/bolt/module_installer.rb +198 -0
  28. data/lib/bolt/{puppetfile → module_installer}/installer.rb +3 -2
  29. data/lib/bolt/module_installer/puppetfile.rb +117 -0
  30. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  31. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  32. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  33. data/lib/bolt/module_installer/resolver.rb +76 -0
  34. data/lib/bolt/module_installer/specs.rb +93 -0
  35. data/lib/bolt/module_installer/specs/forge_spec.rb +85 -0
  36. data/lib/bolt/module_installer/specs/git_spec.rb +179 -0
  37. data/lib/bolt/outputter.rb +2 -45
  38. data/lib/bolt/outputter/human.rb +78 -18
  39. data/lib/bolt/outputter/json.rb +22 -7
  40. data/lib/bolt/outputter/logger.rb +2 -2
  41. data/lib/bolt/pal.rb +55 -45
  42. data/lib/bolt/pal/yaml_plan.rb +4 -2
  43. data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
  44. data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
  45. data/lib/bolt/plugin.rb +1 -1
  46. data/lib/bolt/plugin/module.rb +1 -1
  47. data/lib/bolt/project.rb +32 -21
  48. data/lib/bolt/project_migrator.rb +80 -0
  49. data/lib/bolt/project_migrator/base.rb +39 -0
  50. data/lib/bolt/project_migrator/config.rb +67 -0
  51. data/lib/bolt/project_migrator/inventory.rb +67 -0
  52. data/lib/bolt/project_migrator/modules.rb +200 -0
  53. data/lib/bolt/result.rb +23 -11
  54. data/lib/bolt/shell/bash.rb +15 -9
  55. data/lib/bolt/shell/powershell.rb +11 -6
  56. data/lib/bolt/transport/base.rb +18 -18
  57. data/lib/bolt/transport/docker.rb +23 -6
  58. data/lib/bolt/transport/orch.rb +23 -14
  59. data/lib/bolt/transport/remote.rb +2 -2
  60. data/lib/bolt/transport/simple.rb +6 -6
  61. data/lib/bolt/transport/ssh/connection.rb +1 -1
  62. data/lib/bolt/util.rb +22 -0
  63. data/lib/bolt/version.rb +1 -1
  64. data/lib/bolt_server/acl.rb +2 -2
  65. data/lib/bolt_server/base_config.rb +3 -3
  66. data/lib/bolt_server/schemas/partials/task.json +17 -2
  67. data/lib/bolt_server/transport_app.rb +92 -12
  68. data/lib/bolt_spec/bolt_context.rb +4 -2
  69. data/lib/bolt_spec/plans.rb +1 -1
  70. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
  71. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
  72. data/lib/bolt_spec/plans/mock_executor.rb +6 -6
  73. data/lib/bolt_spec/run.rb +1 -1
  74. metadata +29 -10
  75. data/lib/bolt/project_migrate.rb +0 -138
  76. data/lib/bolt/puppetfile.rb +0 -160
  77. data/lib/bolt/puppetfile/module.rb +0 -89
  78. data/lib/bolt_server/pe/pal.rb +0 -67
  79. data/modules/secure_env_vars/plans/init.pp +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec167b23fc4ceec9d9b3048ae64582a3fcf2baa1eceb8100fb26cc3c6df95cb6
4
- data.tar.gz: 4621d274f8c14f4986dae819755c0db162cd056fbd9412275a0bcffc2169edee
3
+ metadata.gz: 956382104b8c11e17fdafa4349b50ec3d7c5ce6b03eaa57ce429bbfad768f8d3
4
+ data.tar.gz: dd838e10c07c8f0f87266ff5edf3af4f5595fab45e6aab0ebf82e381226d3897
5
5
  SHA512:
6
- metadata.gz: 1af9a8df65a1a95a7404a0b94f74bfeb9086e3a97ba03e1f19a53acbb626757249b69d812f7afe87e2da05a06d698f515b05313018a649621d98269eabfe2f93
7
- data.tar.gz: 24f0b0f56451570d5e97d74ddb66556b48fd8c14a79fd7a0c09f10a4ef1c8590684ccee1675c4dd4126cdbcbc781bad6bf83ba2aa4d8ada0cd13279c15f53876
6
+ metadata.gz: 6c2b97a1c268d0fd93f1086efe623c740b74d9d1318596b9a74720e0e944455e244bfc2be2d02ba18634ed543a6ad87f6f27b88dc19189b22cd8fa495b75de02
7
+ data.tar.gz: cff4bdac98aad6247866f989f9f3814dd11497fd73d90b08ecf15824ef0ee2b36c73fffeb296ecb8a6cae19eb2e71ec7b834dfe461fdbadd06d46b05b560a758
data/Puppetfile CHANGED
@@ -6,35 +6,37 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
8
  mod 'puppetlabs-service', '1.3.0'
9
- mod 'puppetlabs-puppet_agent', '4.1.1'
10
- mod 'puppetlabs-facts', '1.0.0'
9
+ mod 'puppetlabs-puppet_agent', '4.2.0'
10
+ mod 'puppetlabs-facts', '1.2.0'
11
11
 
12
12
  # Core types and providers for Puppet 6
13
- mod 'puppetlabs-augeas_core', '1.0.5'
13
+ mod 'puppetlabs-augeas_core', '1.1.1'
14
14
  mod 'puppetlabs-host_core', '1.0.3'
15
- mod 'puppetlabs-scheduled_task', '2.0.1'
16
- mod 'puppetlabs-sshkeys_core', '1.0.3'
17
- mod 'puppetlabs-zfs_core', '1.0.4'
18
- mod 'puppetlabs-cron_core', '1.0.3'
15
+ mod 'puppetlabs-scheduled_task', '2.2.1'
16
+ mod 'puppetlabs-sshkeys_core', '2.2.0'
17
+ mod 'puppetlabs-zfs_core', '1.2.0'
18
+ mod 'puppetlabs-cron_core', '1.0.5'
19
19
  mod 'puppetlabs-mount_core', '1.0.4'
20
20
  mod 'puppetlabs-selinux_core', '1.0.4'
21
- mod 'puppetlabs-yumrepo_core', '1.0.6'
21
+ mod 'puppetlabs-yumrepo_core', '1.0.7'
22
22
  mod 'puppetlabs-zone_core', '1.0.3'
23
23
 
24
24
  # Useful additional modules
25
- mod 'puppetlabs-package', '1.1.0'
25
+ mod 'puppetlabs-package', '1.3.0'
26
26
  mod 'puppetlabs-puppet_conf', '0.6.0'
27
27
  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
+ mod 'puppetlabs-stdlib', '6.5.0'
32
32
 
33
33
  # Plugin modules
34
- mod 'puppetlabs-aws_inventory', '0.5.0'
35
- mod 'puppetlabs-azure_inventory', '0.3.0'
36
- mod 'puppetlabs-gcloud_inventory', '0.1.1'
34
+ mod 'puppetlabs-aws_inventory', '0.5.2'
35
+ mod 'puppetlabs-azure_inventory', '0.4.1'
36
+ mod 'puppetlabs-gcloud_inventory', '0.1.3'
37
+ mod 'puppetlabs-http_request', '0.2.0'
37
38
  mod 'puppetlabs-pkcs7', '0.1.1'
39
+ mod 'puppetlabs-secure_env_vars', '0.1.0'
38
40
  mod 'puppetlabs-terraform', '0.5.0'
39
41
  mod 'puppetlabs-vault', '0.3.0'
40
42
  mod 'puppetlabs-yaml', '0.2.0'
@@ -43,4 +45,3 @@ mod 'puppetlabs-yaml', '0.2.0'
43
45
  mod 'canary', local: true
44
46
  mod 'aggregate', local: true
45
47
  mod 'puppetdb_fact', local: true
46
- mod 'secure_env_vars', local: true
@@ -112,7 +112,7 @@ Puppet::Functions.create_function(:download_file, Puppet::Functions::InternalFun
112
112
  call_function('debug', "Simulating file download of '#{source}' - no targets given - no action taken")
113
113
  r = Bolt::ResultSet.new([])
114
114
  else
115
- r = executor.download_file(targets, source, destination, options)
115
+ r = executor.download_file(targets, source, destination, options, Puppet::Pops::PuppetStack.top_of_stack)
116
116
  end
117
117
 
118
118
  if !r.ok && !options[:catch_errors]
@@ -3,6 +3,12 @@
3
3
  require 'bolt/error'
4
4
 
5
5
  # Returns the facts hash for a target.
6
+ #
7
+ # Using the `facts` function does not automatically collect facts for a target,
8
+ # and will only return facts that are currently set in the inventory. To collect
9
+ # facts from a target and set them in the inventory, run the
10
+ # [facts](writing_plans.md#collect-facts-from-targets) plan or
11
+ # [puppetdb_fact](writing_plans.md#collect-facts-from-puppetdb) plan.
6
12
  Puppet::Functions.create_function(:facts) do
7
13
  # @param target A target.
8
14
  # @return The target's facts.
@@ -2,12 +2,12 @@
2
2
 
3
3
  require 'bolt/error'
4
4
 
5
- # Makes a query to {https://puppet.com/docs/puppetdb/latest/index.html puppetdb}
5
+ # Makes a query to [puppetdb](https://puppet.com/docs/puppetdb/latest/index.html)
6
6
  # using Bolt's PuppetDB client.
7
7
  Puppet::Functions.create_function(:puppetdb_query) do
8
8
  # rubocop:disable Layout/LineLength
9
9
  # @param query A PQL query.
10
- # {https://puppet.com/docs/puppetdb/latest/api/query/tutorial-pql.html Learn more about Puppet's query language, PQL}
10
+ # Learn more about [Puppet's query language](https://puppet.com/docs/puppetdb/latest/api/query/tutorial-pql.html), PQL.
11
11
  # @return Results of the PuppetDB query.
12
12
  # @example Request certnames for all nodes
13
13
  # puppetdb_query('nodes[certname] {}')
@@ -69,7 +69,7 @@ Puppet::Functions.create_function(:run_command) do
69
69
  call_function('debug', "Simulating run_command('#{command}') - no targets given - no action taken")
70
70
  r = Bolt::ResultSet.new([])
71
71
  else
72
- r = executor.run_command(targets, command, options)
72
+ r = executor.run_command(targets, command, options, Puppet::Pops::PuppetStack.top_of_stack)
73
73
  end
74
74
 
75
75
  if !r.ok && !options[:catch_errors]
@@ -87,7 +87,7 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
87
87
  r = if targets.empty?
88
88
  Bolt::ResultSet.new([])
89
89
  else
90
- executor.run_script(targets, found, arguments, options)
90
+ executor.run_script(targets, found, arguments, options, Puppet::Pops::PuppetStack.top_of_stack)
91
91
  end
92
92
 
93
93
  if !r.ok && !options[:catch_errors]
@@ -133,7 +133,7 @@ Puppet::Functions.create_function(:run_task) do
133
133
  if targets.empty?
134
134
  Bolt::ResultSet.new([])
135
135
  else
136
- result = executor.run_task(targets, task, params, options)
136
+ result = executor.run_task(targets, task, params, options, Puppet::Pops::PuppetStack.top_of_stack)
137
137
  if !result.ok && !options[:catch_errors]
138
138
  raise Bolt::RunFailure.new(result, 'run_task', task_name)
139
139
  end
@@ -180,7 +180,7 @@ Puppet::Functions.create_function(:run_task_with) do
180
180
  else
181
181
  # Combine the results from the task run with any failing results that were
182
182
  # generated earlier when creating the target mapping
183
- task_result = executor.run_task_with(target_mapping, task, options)
183
+ task_result = executor.run_task_with(target_mapping, task, options, Puppet::Pops::PuppetStack.top_of_stack)
184
184
  result = Bolt::ResultSet.new(task_result.results + error_set)
185
185
 
186
186
  if !result.ok && !options[:catch_errors]
@@ -83,7 +83,7 @@ Puppet::Functions.create_function(:upload_file, Puppet::Functions::InternalFunct
83
83
  call_function('debug', "Simulating file upload of '#{found}' - no targets given - no action taken")
84
84
  r = Bolt::ResultSet.new([])
85
85
  else
86
- r = executor.upload_file(targets, found, destination, options)
86
+ r = executor.upload_file(targets, found, destination, options, Puppet::Pops::PuppetStack.top_of_stack)
87
87
  end
88
88
 
89
89
  if !r.ok && !options[:catch_errors]
@@ -6,15 +6,15 @@ require 'tempfile'
6
6
  #
7
7
  # > **Note:** Not available in apply block
8
8
  Puppet::Functions.create_function(:write_file) do
9
- # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
10
9
  # @param content File content to write.
11
10
  # @param destination An absolute path on the target(s).
11
+ # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
12
12
  # @option options [Boolean] _catch_errors Whether to catch raised errors.
13
13
  # @option options [String] _run_as User to run as using privilege escalation.
14
14
  # @return A list of results, one entry per target.
15
15
  # @example Write a file to a target
16
16
  # $content = 'Hello, world!'
17
- # write_file($targets, $content, '/Users/me/hello.txt')
17
+ # write_file($content, '/Users/me/hello.txt', $targets)
18
18
  dispatch :write_file do
19
19
  required_param 'String', :content
20
20
  required_param 'String[1]', :destination
@@ -26,8 +26,51 @@ Puppet::Functions.create_function(:'out::message') do
26
26
  # Send Analytics Report
27
27
  executor.report_function_call(self.class.name)
28
28
 
29
- executor.publish_event(type: :message, message: message)
29
+ executor.publish_event(type: :message, message: stringify(message))
30
30
 
31
31
  nil
32
32
  end
33
+
34
+ def stringify(message)
35
+ formatted = format_message(message)
36
+ if formatted.is_a?(Hash) || formatted.is_a?(Array)
37
+ ::JSON.pretty_generate(formatted)
38
+ else
39
+ formatted
40
+ end
41
+ end
42
+
43
+ def format_message(message)
44
+ case message
45
+ when Array
46
+ message.map { |item| format_message(item) }
47
+ when Bolt::ApplyResult
48
+ format_apply_result(message)
49
+ when Bolt::Result, Bolt::ResultSet
50
+ # This is equivalent to to_s, but formattable
51
+ message.to_data
52
+ when Bolt::RunFailure
53
+ formatted_resultset = message.result_set.to_data
54
+ message.to_h.merge('result_set' => formatted_resultset)
55
+ when Hash
56
+ message.each_with_object({}) do |(k, v), h|
57
+ h[format_message(k)] = format_message(v)
58
+ end
59
+ when Integer, Float, NilClass
60
+ message
61
+ else
62
+ message.to_s
63
+ end
64
+ end
65
+
66
+ def format_apply_result(result)
67
+ logs = result.resource_logs&.map do |log|
68
+ # Omit low-level info/debug messages
69
+ next if %w[info debug].include?(log['level'])
70
+ indent(2, format_log(log))
71
+ end
72
+ hash = result.to_data
73
+ hash['logs'] = logs unless logs.empty?
74
+ hash
75
+ end
33
76
  end
@@ -9,11 +9,14 @@ Puppet::Functions.create_function(:prompt) do
9
9
  # @param prompt The prompt to display.
10
10
  # @param options A hash of additional options.
11
11
  # @option options [Boolean] sensitive Disable echo back and mark the response as sensitive.
12
+ # The returned value will be wrapped by the `Sensitive` data type. To access the raw
13
+ # value, use the `unwrap` function (i.e. `$sensitive_value.unwrap`).
12
14
  # @return The response to the prompt.
13
15
  # @example Prompt the user if plan execution should continue
14
16
  # $response = prompt('Continue executing plan? [Y\N]')
15
17
  # @example Prompt the user for sensitive information
16
18
  # $password = prompt('Enter your password', 'sensitive' => true)
19
+ # out::message("Password is: ${password.unwrap}")
17
20
  dispatch :prompt do
18
21
  param 'String', :prompt
19
22
  optional_param 'Hash[String[1], Any]', :options
@@ -0,0 +1,18 @@
1
+ TOPIC
2
+ logging
3
+
4
+ DESCRIPTION
5
+ Bolt prints messages both to the console and to log files. Messages can
6
+ either come from Bolt's 'outputter', which logs user-facing messages like
7
+ progress and results, or from the 'logger', which logs warnings, errors, and
8
+ log-structured output to log files. Both of these message streams are
9
+ configurable.
10
+
11
+ By default, Bolt logs to the console at 'warn' level and writes a log file to
12
+ '<project>/bolt-debug.log' at 'debug' level. Unless you are running a plan,
13
+ Bolt runs in verbose mode by default.
14
+
15
+ To learn more about projects, see the 'project' guide.
16
+
17
+ DOCUMENTATION
18
+ https://pup.pt/bolt-logging
@@ -0,0 +1,19 @@
1
+ TOPIC
2
+ module
3
+
4
+ DESCRIPTION
5
+ Modules are shareable, reusable packages of Puppet content. They can include
6
+ tasks, plans, functions, and other types of content that you can use in your
7
+ project. You can download and install modules to your project from the
8
+ Puppet Forge or write your own modules. Bolt also ships with several helpful
9
+ modules pre-installed that are available to all of your projects.
10
+
11
+ Bolt makes it easy to manage the modules that your project depends on. You
12
+ can use Bolt commands to install a project's modules, add new modules to a
13
+ project, and view the modules that are available to the project.
14
+
15
+ To learn more about managing modules in a project, see the documentation.
16
+ To learn how modules are loaded by Bolt, see the 'modulepath' guide.
17
+
18
+ DOCUMENTATION
19
+ https://pup.pt/bolt-modules
@@ -0,0 +1,25 @@
1
+ TOPIC
2
+ modulepath
3
+
4
+ DESCRIPTION
5
+ The modulepath is an ordered list of directories that Bolt loads modules
6
+ from. When Bolt runs a command, it automatically loads modules from the
7
+ modulepath.
8
+
9
+ While Bolt has a default modulepath, you can also configure your own
10
+ modulepath, which can include directories within the project or directories
11
+ elsewhere on your system. Regardless of whether your project uses a default
12
+ or configured modulepath, Bolt automatically adds directories to the
13
+ modulepath. This includes modules containing core Bolt content, which is
14
+ added to the beginning of the modulepath, and bundled content, which is
15
+ added to the end of the modulepath.
16
+
17
+ Modules loaded from a directory listed earlier in the modulepath take
18
+ precedence over modules with the same name loaded from a directory later in
19
+ the modulepath. Bolt will not warn or error when two modules share a name
20
+ and instead will ignore modules with a lower precedence.
21
+
22
+ To learn more about modules, see the 'module' guide.
23
+
24
+ DOCUMENTATION
25
+ https://pup.pt/bolt-project-reference#modulepath
@@ -66,15 +66,18 @@ module Bolt
66
66
  banner: GUIDE_HELP }
67
67
  when 'module'
68
68
  case action
69
+ when 'add'
70
+ { flags: OPTIONS[:global] + %w[configfile project],
71
+ banner: MODULE_ADD_HELP }
72
+ when 'generate-types'
73
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
74
+ banner: MODULE_GENERATETYPES_HELP }
69
75
  when 'install'
70
- { flags: OPTIONS[:global] + %w[configfile force project],
76
+ { flags: OPTIONS[:global] + %w[configfile force project resolve],
71
77
  banner: MODULE_INSTALL_HELP }
72
78
  when 'show'
73
79
  { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
74
80
  banner: MODULE_SHOW_HELP }
75
- when 'generate-types'
76
- { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
77
- banner: MODULE_GENERATETYPES_HELP }
78
81
  else
79
82
  { flags: OPTIONS[:global],
80
83
  banner: MODULE_HELP }
@@ -184,6 +187,7 @@ module Bolt
184
187
  group Show the list of groups in the inventory
185
188
  guide View guides for Bolt concepts and features
186
189
  inventory Show the list of targets an action would run on
190
+ module Manage Bolt project modules
187
191
  plan Convert, create, show, and run Bolt plans
188
192
  project Create and migrate Bolt projects
189
193
  puppetfile Install and list modules and generate type references
@@ -364,27 +368,34 @@ module Bolt
364
368
  bolt module <action> [options]
365
369
 
366
370
  DESCRIPTION
367
- Install and list modules and generate type references
371
+ Manage Bolt project modules
372
+
373
+ The module command is only supported when a project is configured
374
+ with the 'modules' key.
368
375
 
369
376
  ACTIONS
377
+ add Add a module to the project
370
378
  generate-types Generate type references to register in plans
371
379
  install Install the project's modules
372
380
  show List modules available to the Bolt project
373
381
  HELP
374
382
 
375
- MODULE_INSTALL_HELP = <<~HELP
383
+ MODULE_ADD_HELP = <<~HELP
376
384
  NAME
377
- install
385
+ add
378
386
 
379
387
  USAGE
380
- bolt module install [options]
388
+ bolt module add <module> [options]
381
389
 
382
390
  DESCRIPTION
383
- Install the project's modules.
391
+ Add a module to the project.
384
392
 
385
393
  Module declarations are loaded from the project's configuration
386
394
  file. Bolt will automatically resolve all module dependencies,
387
395
  generate a Puppetfile, and install the modules.
396
+
397
+ The module command is only supported when a project is configured
398
+ with the 'modules' key.
388
399
  HELP
389
400
 
390
401
  MODULE_GENERATETYPES_HELP = <<~HELP
@@ -396,6 +407,24 @@ module Bolt
396
407
 
397
408
  DESCRIPTION
398
409
  Generate type references to register in plans.
410
+
411
+ The module command is only supported when a project is configured
412
+ with the 'modules' key.
413
+ HELP
414
+
415
+ MODULE_INSTALL_HELP = <<~HELP
416
+ NAME
417
+ install
418
+
419
+ USAGE
420
+ bolt module install [options]
421
+
422
+ DESCRIPTION
423
+ Install the project's modules.
424
+
425
+ Module declarations are loaded from the project's configuration
426
+ file. Bolt will automatically resolve all module dependencies,
427
+ generate a Puppetfile, and install the modules.
399
428
  HELP
400
429
 
401
430
  MODULE_SHOW_HELP = <<~HELP
@@ -407,6 +436,9 @@ module Bolt
407
436
 
408
437
  DESCRIPTION
409
438
  List modules available to the Bolt project.
439
+
440
+ The module command is only supported when a project is configured
441
+ with the 'modules' key.
410
442
  HELP
411
443
 
412
444
  PLAN_HELP = <<~HELP
@@ -906,6 +938,13 @@ module Bolt
906
938
  @options[:tmpdir] = tmpdir
907
939
  end
908
940
 
941
+ separator "\nMODULE OPTIONS"
942
+ define('--[no-]resolve',
943
+ 'Use --no-resolve to install modules listed in the Puppetfile without resolving modules configured',
944
+ 'in Bolt project configuration') do |resolve|
945
+ @options[:resolve] = resolve
946
+ end
947
+
909
948
  separator "\nDISPLAY OPTIONS"
910
949
  define('--filter FILTER', 'Filter tasks and plans by a matching substring') do |filter|
911
950
  unless /^[a-z0-9_:]+$/.match(filter)
@@ -97,7 +97,7 @@ module Bolt
97
97
  }
98
98
 
99
99
  with_puppet_settings(puppet_settings) do
100
- Puppet::Pal.in_tmp_environment('bolt_catalog', env_conf) do |pal|
100
+ Puppet::Pal.in_tmp_environment('bolt_catalog', **env_conf) do |pal|
101
101
  Puppet.override(puppet_overrides) do
102
102
  Puppet.lookup(:pal_current_node).trusted_data = target['trusted']
103
103
  pal.with_catalog_compiler do |compiler|
@@ -20,28 +20,31 @@ require 'bolt/logger'
20
20
  require 'bolt/outputter'
21
21
  require 'bolt/puppetdb'
22
22
  require 'bolt/plugin'
23
- require 'bolt/project_migrate'
23
+ require 'bolt/project_migrator'
24
24
  require 'bolt/pal'
25
25
  require 'bolt/target'
26
26
  require 'bolt/version'
27
27
  require 'bolt/secret'
28
+ require 'bolt/module_installer'
28
29
 
29
30
  module Bolt
30
31
  class CLIExit < StandardError; end
31
32
  class CLI
32
- COMMANDS = { 'command' => %w[run],
33
- 'script' => %w[run],
34
- 'task' => %w[show run],
35
- 'plan' => %w[show run convert new],
36
- 'file' => %w[download upload],
37
- 'puppetfile' => %w[install show-modules generate-types],
38
- 'secret' => %w[encrypt decrypt createkeys],
39
- 'inventory' => %w[show],
40
- 'group' => %w[show],
41
- 'project' => %w[init migrate],
42
- 'apply' => %w[],
43
- 'guide' => %w[],
44
- 'module' => %w[install show generate-types] }.freeze
33
+ COMMANDS = {
34
+ 'command' => %w[run],
35
+ 'script' => %w[run],
36
+ 'task' => %w[show run],
37
+ 'plan' => %w[show run convert new],
38
+ 'file' => %w[download upload],
39
+ 'puppetfile' => %w[install show-modules generate-types],
40
+ 'secret' => %w[encrypt decrypt createkeys],
41
+ 'inventory' => %w[show],
42
+ 'group' => %w[show],
43
+ 'project' => %w[init migrate],
44
+ 'module' => %w[add generate-types install show],
45
+ 'apply' => %w[],
46
+ 'guide' => %w[]
47
+ }.freeze
45
48
 
46
49
  attr_reader :config, :options
47
50
 
@@ -99,6 +102,10 @@ module Bolt
99
102
  # This part aims to handle both `bolt <mode> --help` and `bolt help <mode>`.
100
103
  remaining = handle_parser_errors { parser.permute(@argv) } unless @argv.empty?
101
104
  if @argv.empty? || help?(remaining)
105
+ # If the subcommand is not enabled, display the default
106
+ # help text
107
+ options[:subcommand] = nil unless COMMANDS.include?(options[:subcommand])
108
+
102
109
  # Update the parser for the subcommand (or lack thereof)
103
110
  parser.update
104
111
  puts parser.help
@@ -189,6 +196,10 @@ module Bolt
189
196
 
190
197
  warn_inventory_overrides_cli(options)
191
198
 
199
+ # Assert whether the puppetfile/module commands are available depending
200
+ # on whether 'modules' is configured.
201
+ assert_puppetfile_or_module_command(config.project.modules)
202
+
192
203
  options
193
204
  rescue Bolt::Error => e
194
205
  outputter.fatal_error(e)
@@ -216,14 +227,10 @@ module Bolt
216
227
  end
217
228
 
218
229
  def validate(options)
219
- # Disables the 'module' subcommand unless the module feature flag is set.
220
- commands = COMMANDS.dup
221
- commands.delete('module') unless ENV['BOLT_MODULE_FEATURE']
222
-
223
- unless commands.include?(options[:subcommand])
230
+ unless COMMANDS.include?(options[:subcommand])
224
231
  raise Bolt::CLIError,
225
232
  "Expected subcommand '#{options[:subcommand]}' to be one of " \
226
- "#{commands.keys.join(', ')}"
233
+ "#{COMMANDS.keys.join(', ')}"
227
234
  end
228
235
 
229
236
  actions = COMMANDS[options[:subcommand]]
@@ -240,12 +247,6 @@ module Bolt
240
247
  end
241
248
  end
242
249
 
243
- if options[:subcommand] != 'file' && options[:subcommand] != 'script' &&
244
- !options[:leftovers].empty?
245
- raise Bolt::CLIError,
246
- "Unknown argument(s) #{options[:leftovers].join(', ')}"
247
- end
248
-
249
250
  if %w[task plan].include?(options[:subcommand]) && options[:action] == 'run'
250
251
  if options[:object].nil?
251
252
  raise Bolt::CLIError, "Must specify a #{options[:subcommand]} to run"
@@ -257,23 +258,6 @@ module Bolt
257
258
  end
258
259
  end
259
260
 
260
- if options[:boltdir] && options[:configfile]
261
- raise Bolt::CLIError, "Only one of '--boltdir', '--project', or '--configfile' may be specified"
262
- end
263
-
264
- if options[:noop] &&
265
- !(options[:subcommand] == 'task' && options[:action] == 'run') && options[:subcommand] != 'apply'
266
- raise Bolt::CLIError,
267
- "Option '--noop' may only be specified when running a task or applying manifest code"
268
- end
269
-
270
- if options[:env_vars]
271
- unless %w[command script].include?(options[:subcommand]) && options[:action] == 'run'
272
- raise Bolt::CLIError,
273
- "Option '--env-var' may only be specified when running a command or script"
274
- end
275
- end
276
-
277
261
  if options[:subcommand] == 'apply' && (options[:object] && options[:code])
278
262
  raise Bolt::CLIError, "--execute is unsupported when specifying a manifest file"
279
263
  end
@@ -296,6 +280,38 @@ module Bolt
296
280
  raise Bolt::CLIError, "Must specify a plan name."
297
281
  end
298
282
 
283
+ if options[:subcommand] == 'module' && options[:action] == 'add' && !options[:object]
284
+ raise Bolt::CLIError, "Must specify a module name."
285
+ end
286
+
287
+ if options[:subcommand] == 'module' && options[:action] == 'install' && options[:object]
288
+ raise Bolt::CLIError, "Invalid argument '#{options[:object]}'. To add a new module to "\
289
+ "the project, run 'bolt module add #{options[:object]}'."
290
+ end
291
+
292
+ if options[:subcommand] != 'file' && options[:subcommand] != 'script' &&
293
+ !options[:leftovers].empty?
294
+ raise Bolt::CLIError,
295
+ "Unknown argument(s) #{options[:leftovers].join(', ')}"
296
+ end
297
+
298
+ if options[:boltdir] && options[:configfile]
299
+ raise Bolt::CLIError, "Only one of '--boltdir', '--project', or '--configfile' may be specified"
300
+ end
301
+
302
+ if options[:noop] &&
303
+ !(options[:subcommand] == 'task' && options[:action] == 'run') && options[:subcommand] != 'apply'
304
+ raise Bolt::CLIError,
305
+ "Option '--noop' may only be specified when running a task or applying manifest code"
306
+ end
307
+
308
+ if options[:env_vars]
309
+ unless %w[command script].include?(options[:subcommand]) && options[:action] == 'run'
310
+ raise Bolt::CLIError,
311
+ "Option '--env-var' may only be specified when running a command or script"
312
+ end
313
+ end
314
+
299
315
  if options.key?(:debug) && options.key?(:log)
300
316
  raise Bolt::CLIError, "Only one of '--debug' or '--log-level' may be specified"
301
317
  end
@@ -389,7 +405,7 @@ module Bolt
389
405
  inventory_version: inventory.version)
390
406
  end
391
407
 
392
- analytics.screen_view(screen, screen_view_fields)
408
+ analytics.screen_view(screen, **screen_view_fields)
393
409
 
394
410
  case options[:action]
395
411
  when 'show'
@@ -444,9 +460,7 @@ module Bolt
444
460
  when 'init'
445
461
  code = initialize_project
446
462
  when 'migrate'
447
- inv = config.inventoryfile
448
- path = config.project.path
449
- code = Bolt::ProjectMigrate.new(path, outputter, inv).migrate_project
463
+ code = Bolt::ProjectMigrator.new(config, outputter).migrate
450
464
  end
451
465
  when 'plan'
452
466
  case options[:action]
@@ -457,8 +471,10 @@ module Bolt
457
471
  end
458
472
  when 'module'
459
473
  case options[:action]
474
+ when 'add'
475
+ code = add_project_module(options[:object], config.project)
460
476
  when 'install'
461
- code = install_project_modules
477
+ code = install_project_modules(config.project, options[:force], options[:resolve])
462
478
  when 'generate-types'
463
479
  code = generate_types
464
480
  end
@@ -467,7 +483,11 @@ module Bolt
467
483
  when 'generate-types'
468
484
  code = generate_types
469
485
  when 'install'
470
- code = install_puppetfile(config.puppetfile_config, config.puppetfile, config.modulepath.first)
486
+ code = install_puppetfile(
487
+ config.puppetfile_config,
488
+ config.puppetfile,
489
+ config.modulepath.first
490
+ )
471
491
  end
472
492
  when 'secret'
473
493
  code = Bolt::Secret.execute(plugins, outputter, options)
@@ -567,8 +587,24 @@ module Bolt
567
587
  end
568
588
 
569
589
  def list_targets
590
+ inventoryfile = config.inventoryfile || config.default_inventoryfile
591
+
592
+ # Retrieve the known group and target names. This needs to be done before
593
+ # updating targets, as that will add adhoc targets to the inventory.
594
+ known_names = inventory.target_names
595
+
570
596
  update_targets(options)
571
- outputter.print_targets(options[:targets])
597
+
598
+ inventory_targets, adhoc_targets = options[:targets].partition do |target|
599
+ known_names.include?(target.name)
600
+ end
601
+
602
+ target_list = {
603
+ inventory: inventory_targets,
604
+ adhoc: adhoc_targets
605
+ }
606
+
607
+ outputter.print_targets(target_list, inventoryfile)
572
608
  end
573
609
 
574
610
  def show_targets
@@ -597,10 +633,10 @@ module Bolt
597
633
  message = <<~MESSAGE.chomp
598
634
  Invalid plan name '#{plan_name}'. Plan names are composed of one or more name segments
599
635
  separated by double colons '::'.
600
-
636
+
601
637
  Each name segment must begin with a lowercase letter, and may only include lowercase
602
638
  letters, digits, and underscores.
603
-
639
+
604
640
  Examples of valid plan names:
605
641
  - #{config.project.name}
606
642
  - #{config.project.name}::my_plan
@@ -842,7 +878,8 @@ module Bolt
842
878
  "project with modules."
843
879
  end
844
880
 
845
- install_modules(puppetfile, {}, moduledir, options[:modules])
881
+ installer = Bolt::ModuleInstaller.new(outputter, pal)
882
+ installer.install(options[:modules], puppetfile, moduledir)
846
883
  end
847
884
 
848
885
  # If either bolt.yaml or bolt-project.yaml exist, the user has already
@@ -863,85 +900,86 @@ module Bolt
863
900
 
864
901
  # Installs modules declared in the project configuration file.
865
902
  #
866
- def install_project_modules
867
- if config.project.modules.nil?
868
- outputter.print_message "Project configuration file '#{config.project.project_file}' "\
869
- "does not specify any module dependencies. Nothing to do."
903
+ def install_project_modules(project, force, resolve)
904
+ assert_project_file(project)
905
+
906
+ unless project.modules
907
+ outputter.print_message "Project configuration file #{project.project_file} does not "\
908
+ "specify any module dependencies. Nothing to do."
870
909
  return 0
871
910
  end
872
911
 
873
- install_modules(
874
- config.puppetfile,
875
- config.puppetfile_config,
876
- config.project.path + '.modules',
877
- config.project.modules
878
- )
912
+ installer = Bolt::ModuleInstaller.new(outputter, pal)
913
+
914
+ ok = installer.install(project.modules,
915
+ project.puppetfile,
916
+ project.managed_moduledir,
917
+ force: force,
918
+ resolve: resolve)
919
+ ok ? 0 : 1
879
920
  end
880
921
 
881
- # Installs modules declared in the project configuration file.
922
+ # Adds a single module to the project.
882
923
  #
883
- def install_modules(puppetfile_path, config, moduledir, modules)
884
- require 'bolt/puppetfile'
885
- require 'bolt/puppetfile/installer'
886
-
887
- puppetfile = Bolt::Puppetfile.new(modules)
888
-
889
- # If the Puppetfile exists, check if it includes specs for each declared
890
- # module, erroring if there are any missing. Otherwise, resolve the
891
- # module dependencies and write a new Puppetfile. Users can forcibly
892
- # overwrite an existing Puppetfile with the '--force' option.
893
- if puppetfile_path.exist? && !options[:force]
894
- outputter.print_message "Parsing existing Puppetfile at #{puppetfile_path}"
895
- existing = Bolt::Puppetfile.parse(puppetfile_path)
896
-
897
- unless existing.modules.superset? puppetfile.modules
898
- missing_modules = puppetfile.modules - existing.modules
899
-
900
- message = <<~MESSAGE.chomp
901
- Puppetfile #{puppetfile_path} is missing specifications for the following
902
- module declarations:
903
-
904
- #{missing_modules.map(&:to_hash).to_yaml.lines.drop(1).join.chomp}
905
-
906
- This may not be a Puppetfile managed by Bolt. To forcibly overwrite the
907
- Puppetfile, run 'bolt module install --force'.
908
- MESSAGE
909
-
910
- raise Bolt::Error.new(message, 'bolt/missing-module-specs')
911
- end
912
- else
913
- outputter.print_message "Resolving module dependencies, this may take a moment"
914
- puppetfile.resolve
915
- outputter.print_message "Writing Puppetfile at #{puppetfile_path}"
916
- puppetfile.write(puppetfile_path, force: true)
917
- end
924
+ def add_project_module(name, project)
925
+ assert_project_file(project)
918
926
 
919
- outputter.print_message "Syncing modules from #{puppetfile_path} to #{moduledir}"
920
- ok = Bolt::Puppetfile::Installer.new(config).install(puppetfile_path, moduledir)
927
+ modules = project.modules || []
928
+ installer = Bolt::ModuleInstaller.new(outputter, pal)
921
929
 
922
- # Automatically generate types after installing modules.
923
- pal.generate_types
924
-
925
- outputter.print_puppetfile_result(ok, puppetfile_path, moduledir)
930
+ ok = installer.add(name,
931
+ modules,
932
+ project.puppetfile,
933
+ project.managed_moduledir,
934
+ project.project_file)
926
935
  ok ? 0 : 1
927
936
  end
928
937
 
929
- # Loads a Puppetfile and installs its modules.
938
+ # Asserts that there is a project configuration file.
930
939
  #
931
- def install_puppetfile(config, puppetfile, moduledir)
932
- require 'bolt/puppetfile/installer'
933
-
934
- ok = Bolt::Puppetfile::Installer.new(config).install(puppetfile, moduledir)
940
+ def assert_project_file(project)
941
+ unless project.project_file?
942
+ msg = if project.config_file.exist?
943
+ "Detected Bolt configuration file #{project.config_file}, unable to install "\
944
+ "modules. To update to a project configuration file, run 'bolt project migrate'."
945
+ else
946
+ "Could not find project configuration file #{project.project_file}, unable "\
947
+ "to install modules. To create a Bolt project, run 'bolt project init'."
948
+ end
935
949
 
936
- # Automatically generate types after installing modules.
937
- pal.generate_types
950
+ raise Bolt::Error.new(msg, 'bolt/missing-project-config-error')
951
+ end
952
+ end
938
953
 
939
- outputter.print_puppetfile_result(ok, puppetfile, moduledir)
954
+ # Loads a Puppetfile and installs its modules.
955
+ #
956
+ def install_puppetfile(config, puppetfile, moduledir)
957
+ outputter.print_message("Installing modules from Puppetfile")
958
+ installer = Bolt::ModuleInstaller.new(outputter, pal)
959
+ ok = installer.install_puppetfile(puppetfile, moduledir, config)
940
960
  ok ? 0 : 1
941
961
  end
942
962
 
963
+ # Raises an error if the 'puppetfile install' command is deprecated due to
964
+ # modules being configured.
965
+ #
966
+ def assert_puppetfile_or_module_command(modules)
967
+ if modules && options[:subcommand] == 'puppetfile'
968
+ raise Bolt::CLIError,
969
+ "Unable to use command 'bolt puppetfile #{options[:action]}' when "\
970
+ "'modules' is configured in bolt-project.yaml. Use the 'module' command "\
971
+ "instead. For a list of available actions for the 'module' command, run "\
972
+ "'bolt module --help'."
973
+ elsif modules.nil? && options[:subcommand] == 'module'
974
+ raise Bolt::CLIError,
975
+ "Unable to use command 'bolt module #{options[:action]}'. To use "\
976
+ "this command, update your project configuration to manage module "\
977
+ "dependencies."
978
+ end
979
+ end
980
+
943
981
  def pal
944
- @pal ||= Bolt::PAL.new(config.modulepath,
982
+ @pal ||= Bolt::PAL.new(Bolt::Config::Modulepath.new(config.modulepath),
945
983
  config.hiera_config,
946
984
  config.project.resource_types,
947
985
  config.compile_concurrency,
@@ -1040,7 +1078,7 @@ module Bolt
1040
1078
  'Task' => [],
1041
1079
  'Plugin' => Bolt::Plugin::BUILTIN_PLUGINS }
1042
1080
  if %w[plan task].include?(options[:subcommand]) && options[:action] == 'run'
1043
- default_content = Bolt::PAL.new([], nil, nil)
1081
+ default_content = Bolt::PAL.new(Bolt::Config::Modulepath.new([]), nil, nil)
1044
1082
  content['Plan'] = default_content.list_plans.each_with_object([]) do |iter, col|
1045
1083
  col << iter&.first
1046
1084
  end
@@ -1055,7 +1093,7 @@ module Bolt
1055
1093
  # Gem installs include the aggregate, canary, and puppetdb_fact modules, while
1056
1094
  # package installs include modules listed in the Bolt repo Puppetfile
1057
1095
  def incomplete_install?
1058
- (Dir.children(Bolt::PAL::MODULES_PATH) - %w[aggregate canary puppetdb_fact secure_env_vars]).empty?
1096
+ (Dir.children(Bolt::Config::Modulepath::MODULES_PATH) - %w[aggregate canary puppetdb_fact secure_env_vars]).empty?
1059
1097
  end
1060
1098
 
1061
1099
  # Mimicks the output from Outputter::Human#fatal_error. This should be used to print