bolt 2.27.0 → 2.32.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +13 -12
  3. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +2 -2
  4. data/bolt-modules/out/lib/puppet/functions/out/message.rb +44 -1
  5. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +3 -0
  6. data/guides/module.txt +19 -0
  7. data/guides/modulepath.txt +25 -0
  8. data/lib/bolt/applicator.rb +14 -14
  9. data/lib/bolt/bolt_option_parser.rb +74 -22
  10. data/lib/bolt/catalog.rb +1 -1
  11. data/lib/bolt/cli.rb +178 -127
  12. data/lib/bolt/config.rb +13 -1
  13. data/lib/bolt/config/modulepath.rb +30 -0
  14. data/lib/bolt/config/options.rb +38 -9
  15. data/lib/bolt/config/transport/options.rb +1 -1
  16. data/lib/bolt/executor.rb +1 -1
  17. data/lib/bolt/inventory.rb +11 -10
  18. data/lib/bolt/logger.rb +26 -19
  19. data/lib/bolt/module_installer.rb +197 -0
  20. data/lib/bolt/{puppetfile → module_installer}/installer.rb +3 -2
  21. data/lib/bolt/module_installer/puppetfile.rb +117 -0
  22. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  23. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  24. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  25. data/lib/bolt/module_installer/resolver.rb +76 -0
  26. data/lib/bolt/module_installer/specs.rb +93 -0
  27. data/lib/bolt/module_installer/specs/forge_spec.rb +84 -0
  28. data/lib/bolt/module_installer/specs/git_spec.rb +178 -0
  29. data/lib/bolt/outputter.rb +2 -45
  30. data/lib/bolt/outputter/human.rb +78 -18
  31. data/lib/bolt/outputter/json.rb +22 -7
  32. data/lib/bolt/outputter/logger.rb +2 -2
  33. data/lib/bolt/pal.rb +29 -25
  34. data/lib/bolt/plugin.rb +1 -1
  35. data/lib/bolt/plugin/module.rb +1 -1
  36. data/lib/bolt/project.rb +32 -22
  37. data/lib/bolt/project_migrator.rb +80 -0
  38. data/lib/bolt/project_migrator/base.rb +39 -0
  39. data/lib/bolt/project_migrator/config.rb +67 -0
  40. data/lib/bolt/project_migrator/inventory.rb +67 -0
  41. data/lib/bolt/project_migrator/modules.rb +200 -0
  42. data/lib/bolt/shell/bash.rb +4 -3
  43. data/lib/bolt/transport/base.rb +4 -4
  44. data/lib/bolt/transport/ssh/connection.rb +1 -1
  45. data/lib/bolt/util.rb +51 -10
  46. data/lib/bolt/version.rb +1 -1
  47. data/lib/bolt_server/acl.rb +2 -2
  48. data/lib/bolt_server/base_config.rb +3 -3
  49. data/lib/bolt_server/file_cache.rb +11 -11
  50. data/lib/bolt_server/schemas/partials/task.json +17 -2
  51. data/lib/bolt_server/transport_app.rb +93 -13
  52. data/lib/bolt_spec/bolt_context.rb +8 -6
  53. data/lib/bolt_spec/plans.rb +1 -1
  54. data/lib/bolt_spec/plans/mock_executor.rb +1 -1
  55. data/lib/bolt_spec/run.rb +1 -1
  56. metadata +30 -11
  57. data/lib/bolt/project_migrate.rb +0 -138
  58. data/lib/bolt/puppetfile.rb +0 -160
  59. data/lib/bolt/puppetfile/module.rb +0 -66
  60. data/lib/bolt_server/pe/pal.rb +0 -67
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 851cd71cf3bbdd91a522f123db238205ca21875c7d1c9bf1eefe4850399ffb5d
4
- data.tar.gz: 4ec2ca689f05d37ccbc1cd3962edae53dfcda79c0a58facd9a2f228af5df914c
3
+ metadata.gz: 5b010e9146d3269d88005be58db6b788f57ea3046f96f28756641b0c7266eec2
4
+ data.tar.gz: ce8d15031143acabc664a7025ef004c69211c25316f9691783388ba74e61dec9
5
5
  SHA512:
6
- metadata.gz: 3d595d36833a70860c7c997fff4cc55ce682dd3b3b9c4fc5e7b36067cbb73a665e65d473097993963f8409eb5a06a25b5b834de9dff6f42ad8e244972ab0ac4e
7
- data.tar.gz: d136cd117711e3a103480b7cae52661e8ad3beef5023552f3bbaa8235e8f795410e13a1955290c7313fb6f7fced1485b275b8430a0d5c81297e11e1ff911f458
6
+ metadata.gz: b45657eb2b985f8e97c59ff4603ad049fd17ee6585292463a57bde3b5c7ca55451883b5e88cb0f61e55c05f1d5415cbde3874320ae446124d646a3d3e8ffa812
7
+ data.tar.gz: 673e3f3310bf4f22602153bf8300851fa3a832db8e73abc9352874bea7c0b1fd50a5baa42d18474885caea7ec9fad4aa50c25f44aa27974097e10ea03b31e07e
data/Puppetfile CHANGED
@@ -7,33 +7,34 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
7
7
  # Core modules used by 'apply'
8
8
  mod 'puppetlabs-service', '1.3.0'
9
9
  mod 'puppetlabs-puppet_agent', '4.1.1'
10
- mod 'puppetlabs-facts', '1.0.0'
10
+ mod 'puppetlabs-facts', '1.1.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.1.0'
17
+ mod 'puppetlabs-zfs_core', '1.1.0'
18
+ mod 'puppetlabs-cron_core', '1.0.4'
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'
38
39
  mod 'puppetlabs-terraform', '0.5.0'
39
40
  mod 'puppetlabs-vault', '0.3.0'
@@ -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,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
@@ -50,15 +50,15 @@ module Bolt
50
50
 
51
51
  def catalog_apply_task
52
52
  @catalog_apply_task ||= begin
53
- path = File.join(libexec, 'apply_catalog.rb')
54
- file = { 'name' => 'apply_catalog.rb', 'path' => path }
55
- metadata = { 'supports_noop' => true, 'input_method' => 'stdin',
56
- 'implementations' => [
57
- { 'name' => 'apply_catalog.rb' },
58
- { 'name' => 'apply_catalog.rb', 'remote' => true }
59
- ] }
60
- Bolt::Task.new('apply_helpers::apply_catalog', metadata, [file])
61
- end
53
+ path = File.join(libexec, 'apply_catalog.rb')
54
+ file = { 'name' => 'apply_catalog.rb', 'path' => path }
55
+ metadata = { 'supports_noop' => true, 'input_method' => 'stdin',
56
+ 'implementations' => [
57
+ { 'name' => 'apply_catalog.rb' },
58
+ { 'name' => 'apply_catalog.rb', 'remote' => true }
59
+ ] }
60
+ Bolt::Task.new('apply_helpers::apply_catalog', metadata, [file])
61
+ end
62
62
  end
63
63
 
64
64
  def query_resources_task
@@ -99,11 +99,11 @@ module Bolt
99
99
  # see what happened
100
100
  print_logs = stat.success?
101
101
  result = begin
102
- JSON.parse(out)
103
- rescue JSON::ParserError
104
- print_logs = true
105
- { 'message' => "Something's gone terribly wrong! STDERR is logged." }
106
- end
102
+ JSON.parse(out)
103
+ rescue JSON::ParserError
104
+ print_logs = true
105
+ { 'message' => "Something's gone terribly wrong! STDERR is logged." }
106
+ end
107
107
 
108
108
  # Any messages logged by Puppet will be on stderr as JSON hashes, so we
109
109
  # parse those and store them here. Any message on stderr that is not
@@ -66,9 +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 }
78
+ when 'show'
79
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
80
+ banner: MODULE_SHOW_HELP }
72
81
  else
73
82
  { flags: OPTIONS[:global],
74
83
  banner: MODULE_HELP }
@@ -178,6 +187,7 @@ module Bolt
178
187
  group Show the list of groups in the inventory
179
188
  guide View guides for Bolt concepts and features
180
189
  inventory Show the list of targets an action would run on
190
+ module Manage Bolt project modules
181
191
  plan Convert, create, show, and run Bolt plans
182
192
  project Create and migrate Bolt projects
183
193
  puppetfile Install and list modules and generate type references
@@ -358,10 +368,48 @@ module Bolt
358
368
  bolt module <action> [options]
359
369
 
360
370
  DESCRIPTION
361
- Install the project's modules
371
+ Manage Bolt project modules
372
+
373
+ The module command is only supported when a project is configured
374
+ with the 'modules' key.
362
375
 
363
376
  ACTIONS
364
- install Install the project's modules
377
+ add Add a module to the project
378
+ generate-types Generate type references to register in plans
379
+ install Install the project's modules
380
+ show List modules available to the Bolt project
381
+ HELP
382
+
383
+ MODULE_ADD_HELP = <<~HELP
384
+ NAME
385
+ add
386
+
387
+ USAGE
388
+ bolt module add <module> [options]
389
+
390
+ DESCRIPTION
391
+ Add a module to the project.
392
+
393
+ Module declarations are loaded from the project's configuration
394
+ file. Bolt will automatically resolve all module dependencies,
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.
399
+ HELP
400
+
401
+ MODULE_GENERATETYPES_HELP = <<~HELP
402
+ NAME
403
+ generate-types
404
+
405
+ USAGE
406
+ bolt module generate-types [options]
407
+
408
+ DESCRIPTION
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.
365
413
  HELP
366
414
 
367
415
  MODULE_INSTALL_HELP = <<~HELP
@@ -379,6 +427,20 @@ module Bolt
379
427
  generate a Puppetfile, and install the modules.
380
428
  HELP
381
429
 
430
+ MODULE_SHOW_HELP = <<~HELP
431
+ NAME
432
+ show
433
+
434
+ USAGE
435
+ bolt module show [options]
436
+
437
+ DESCRIPTION
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.
442
+ HELP
443
+
382
444
  PLAN_HELP = <<~HELP
383
445
  NAME
384
446
  plan
@@ -716,7 +778,7 @@ module Bolt
716
778
  'For SSH, port defaults to `22`',
717
779
  'For WinRM, port defaults to `5985` or `5986` based on the --[no-]ssl setting') do |targets|
718
780
  @options[:targets] ||= []
719
- @options[:targets] << get_arg_input(targets)
781
+ @options[:targets] << Bolt::Util.get_arg_input(targets)
720
782
  end
721
783
  define('-q', '--query QUERY', 'Query PuppetDB to determine the targets') do |query|
722
784
  @options[:query] = query
@@ -876,6 +938,13 @@ module Bolt
876
938
  @options[:tmpdir] = tmpdir
877
939
  end
878
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
+
879
948
  separator "\nDISPLAY OPTIONS"
880
949
  define('--filter FILTER', 'Filter tasks and plans by a matching substring') do |filter|
881
950
  unless /^[a-z0-9_:]+$/.match(filter)
@@ -955,27 +1024,10 @@ module Bolt
955
1024
  end
956
1025
 
957
1026
  def parse_params(params)
958
- json = get_arg_input(params)
1027
+ json = Bolt::Util.get_arg_input(params)
959
1028
  JSON.parse(json)
960
1029
  rescue JSON::ParserError => e
961
1030
  raise Bolt::CLIError, "Unable to parse --params value as JSON: #{e}"
962
1031
  end
963
-
964
- def get_arg_input(value)
965
- if value.start_with?('@')
966
- file = value.sub(/^@/, '')
967
- read_arg_file(file)
968
- elsif value == '-'
969
- $stdin.read
970
- else
971
- value
972
- end
973
- end
974
-
975
- def read_arg_file(file)
976
- File.read(File.expand_path(file))
977
- rescue StandardError => e
978
- raise Bolt::FileError.new("Error attempting to read #{file}: #{e}", file)
979
- end
980
1032
  end
981
1033
  end
@@ -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] }.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
@@ -107,6 +114,11 @@ module Bolt
107
114
 
108
115
  options[:object] = remaining.shift
109
116
 
117
+ # Handle reading a command from a file
118
+ if options[:subcommand] == 'command' && options[:object]
119
+ options[:object] = Bolt::Util.get_arg_input(options[:object])
120
+ end
121
+
110
122
  # Only parse task_options for task or plan
111
123
  if %w[task plan].include?(options[:subcommand])
112
124
  task_options, remaining = remaining.partition { |s| s =~ /.+=/ }
@@ -184,6 +196,10 @@ module Bolt
184
196
 
185
197
  warn_inventory_overrides_cli(options)
186
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
+
187
203
  options
188
204
  rescue Bolt::Error => e
189
205
  outputter.fatal_error(e)
@@ -211,14 +227,10 @@ module Bolt
211
227
  end
212
228
 
213
229
  def validate(options)
214
- # Disables the 'module' subcommand unless the module feature flag is set.
215
- commands = COMMANDS.dup
216
- commands.delete('module') unless ENV['BOLT_MODULE_FEATURE']
217
-
218
- unless commands.include?(options[:subcommand])
230
+ unless COMMANDS.include?(options[:subcommand])
219
231
  raise Bolt::CLIError,
220
232
  "Expected subcommand '#{options[:subcommand]}' to be one of " \
221
- "#{commands.keys.join(', ')}"
233
+ "#{COMMANDS.keys.join(', ')}"
222
234
  end
223
235
 
224
236
  actions = COMMANDS[options[:subcommand]]
@@ -235,12 +247,6 @@ module Bolt
235
247
  end
236
248
  end
237
249
 
238
- if options[:subcommand] != 'file' && options[:subcommand] != 'script' &&
239
- !options[:leftovers].empty?
240
- raise Bolt::CLIError,
241
- "Unknown argument(s) #{options[:leftovers].join(', ')}"
242
- end
243
-
244
250
  if %w[task plan].include?(options[:subcommand]) && options[:action] == 'run'
245
251
  if options[:object].nil?
246
252
  raise Bolt::CLIError, "Must specify a #{options[:subcommand]} to run"
@@ -252,23 +258,6 @@ module Bolt
252
258
  end
253
259
  end
254
260
 
255
- if options[:boltdir] && options[:configfile]
256
- raise Bolt::CLIError, "Only one of '--boltdir', '--project', or '--configfile' may be specified"
257
- end
258
-
259
- if options[:noop] &&
260
- !(options[:subcommand] == 'task' && options[:action] == 'run') && options[:subcommand] != 'apply'
261
- raise Bolt::CLIError,
262
- "Option '--noop' may only be specified when running a task or applying manifest code"
263
- end
264
-
265
- if options[:env_vars]
266
- unless %w[command script].include?(options[:subcommand]) && options[:action] == 'run'
267
- raise Bolt::CLIError,
268
- "Option '--env-var' may only be specified when running a command or script"
269
- end
270
- end
271
-
272
261
  if options[:subcommand] == 'apply' && (options[:object] && options[:code])
273
262
  raise Bolt::CLIError, "--execute is unsupported when specifying a manifest file"
274
263
  end
@@ -291,6 +280,38 @@ module Bolt
291
280
  raise Bolt::CLIError, "Must specify a plan name."
292
281
  end
293
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
+
294
315
  if options.key?(:debug) && options.key?(:log)
295
316
  raise Bolt::CLIError, "Only one of '--debug' or '--log-level' may be specified"
296
317
  end
@@ -384,7 +405,7 @@ module Bolt
384
405
  inventory_version: inventory.version)
385
406
  end
386
407
 
387
- analytics.screen_view(screen, screen_view_fields)
408
+ analytics.screen_view(screen, **screen_view_fields)
388
409
 
389
410
  case options[:action]
390
411
  when 'show'
@@ -409,6 +430,8 @@ module Bolt
409
430
  end
410
431
  when 'group'
411
432
  list_groups
433
+ when 'module'
434
+ list_modules
412
435
  end
413
436
  return 0
414
437
  when 'show-modules'
@@ -437,9 +460,7 @@ module Bolt
437
460
  when 'init'
438
461
  code = initialize_project
439
462
  when 'migrate'
440
- inv = config.inventoryfile
441
- path = config.project.path
442
- code = Bolt::ProjectMigrate.new(path, outputter, inv).migrate_project
463
+ code = Bolt::ProjectMigrator.new(config, outputter).migrate
443
464
  end
444
465
  when 'plan'
445
466
  case options[:action]
@@ -450,15 +471,23 @@ module Bolt
450
471
  end
451
472
  when 'module'
452
473
  case options[:action]
474
+ when 'add'
475
+ code = add_project_module(options[:object], config.project)
453
476
  when 'install'
454
- code = install_project_modules
477
+ code = install_project_modules(config.project, options[:force], options[:resolve])
478
+ when 'generate-types'
479
+ code = generate_types
455
480
  end
456
481
  when 'puppetfile'
457
482
  case options[:action]
458
483
  when 'generate-types'
459
484
  code = generate_types
460
485
  when 'install'
461
- 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
+ )
462
491
  end
463
492
  when 'secret'
464
493
  code = Bolt::Secret.execute(plugins, outputter, options)
@@ -558,8 +587,24 @@ module Bolt
558
587
  end
559
588
 
560
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
+
561
596
  update_targets(options)
562
- 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)
563
608
  end
564
609
 
565
610
  def show_targets
@@ -588,10 +633,10 @@ module Bolt
588
633
  message = <<~MESSAGE.chomp
589
634
  Invalid plan name '#{plan_name}'. Plan names are composed of one or more name segments
590
635
  separated by double colons '::'.
591
-
636
+
592
637
  Each name segment must begin with a lowercase letter, and may only include lowercase
593
638
  letters, digits, and underscores.
594
-
639
+
595
640
  Examples of valid plan names:
596
641
  - #{config.project.name}
597
642
  - #{config.project.name}::my_plan
@@ -833,7 +878,8 @@ module Bolt
833
878
  "project with modules."
834
879
  end
835
880
 
836
- install_modules(puppetfile, {}, moduledir, options[:modules])
881
+ installer = Bolt::ModuleInstaller.new(outputter, pal)
882
+ installer.install(options[:modules], puppetfile, moduledir)
837
883
  end
838
884
 
839
885
  # If either bolt.yaml or bolt-project.yaml exist, the user has already
@@ -854,81 +900,86 @@ module Bolt
854
900
 
855
901
  # Installs modules declared in the project configuration file.
856
902
  #
857
- def install_project_modules
858
- if config.project.modules.nil?
859
- outputter.print_message "Project configuration file '#{config.project.project_file}' "\
860
- "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."
861
909
  return 0
862
910
  end
863
911
 
864
- install_modules(
865
- config.puppetfile,
866
- config.puppetfile_config,
867
- config.project.path + '.modules',
868
- config.project.modules
869
- )
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
870
920
  end
871
921
 
872
- # Installs modules declared in the project configuration file.
922
+ # Adds a single module to the project.
873
923
  #
874
- def install_modules(puppetfile_path, config, moduledir, modules)
875
- require 'bolt/puppetfile'
876
- require 'bolt/puppetfile/installer'
877
-
878
- puppetfile = Bolt::Puppetfile.new(modules)
879
-
880
- # If the Puppetfile exists, check if it includes specs for each declared
881
- # module, erroring if there are any missing. Otherwise, resolve the
882
- # module dependencies and write a new Puppetfile. Users can forcibly
883
- # overwrite an existing Puppetfile with the '--force' option.
884
- if puppetfile_path.exist? && !options[:force]
885
- outputter.print_message "Parsing existing Puppetfile at #{puppetfile_path}"
886
- existing = Bolt::Puppetfile.parse(puppetfile_path)
887
-
888
- unless existing.modules.superset? puppetfile.modules
889
- missing_modules = puppetfile.modules - existing.modules
890
-
891
- raise Bolt::Error.new(
892
- "Puppetfile #{puppetfile_path} is missing specifications for modules: "\
893
- "#{missing_modules.map(&:title).join(', ')}. This may not be a Puppetfile "\
894
- "managed by Bolt. To forcibly overwrite the Puppetfile, run with the "\
895
- "'--force' option.",
896
- 'bolt/missing-module-specs'
897
- )
898
- end
899
- else
900
- outputter.print_message "Resolving module dependencies, this may take a moment"
901
- puppetfile.resolve
902
- outputter.print_message "Writing Puppetfile at #{puppetfile_path}"
903
- puppetfile.write(puppetfile_path, force: true)
904
- end
905
-
906
- outputter.print_message "Syncing modules from #{puppetfile_path} to #{moduledir}"
907
- ok = Bolt::Puppetfile::Installer.new(config).install(puppetfile_path, moduledir)
924
+ def add_project_module(name, project)
925
+ assert_project_file(project)
908
926
 
909
- # Automatically generate types after installing modules.
910
- pal.generate_types
927
+ modules = project.modules || []
928
+ installer = Bolt::ModuleInstaller.new(outputter, pal)
911
929
 
912
- 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)
913
935
  ok ? 0 : 1
914
936
  end
915
937
 
916
- # Loads a Puppetfile and installs its modules.
938
+ # Asserts that there is a project configuration file.
917
939
  #
918
- def install_puppetfile(config, puppetfile, moduledir)
919
- require 'bolt/puppetfile/installer'
920
-
921
- 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
922
949
 
923
- # Automatically generate types after installing modules.
924
- pal.generate_types
950
+ raise Bolt::Error.new(msg, 'bolt/missing-project-config-error')
951
+ end
952
+ end
925
953
 
926
- 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)
927
960
  ok ? 0 : 1
928
961
  end
929
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
+
930
981
  def pal
931
- @pal ||= Bolt::PAL.new(config.modulepath,
982
+ @pal ||= Bolt::PAL.new(Bolt::Config::Modulepath.new(config.modulepath),
932
983
  config.hiera_config,
933
984
  config.project.resource_types,
934
985
  config.compile_concurrency,
@@ -940,17 +991,17 @@ module Bolt
940
991
  # Collects the list of Bolt guides and maps them to their topics.
941
992
  def guides
942
993
  @guides ||= begin
943
- root_path = File.expand_path(File.join(__dir__, '..', '..', 'guides'))
944
- files = Dir.children(root_path).sort
945
-
946
- files.each_with_object({}) do |file, guides|
947
- next if file !~ /\.txt\z/
948
- topic = File.basename(file, '.txt')
949
- guides[topic] = File.join(root_path, file)
950
- end
951
- rescue SystemCallError => e
952
- raise Bolt::FileError.new("#{e.message}: unable to load guides directory", root_path)
953
- end
994
+ root_path = File.expand_path(File.join(__dir__, '..', '..', 'guides'))
995
+ files = Dir.children(root_path).sort
996
+
997
+ files.each_with_object({}) do |file, guides|
998
+ next if file !~ /\.txt\z/
999
+ topic = File.basename(file, '.txt')
1000
+ guides[topic] = File.join(root_path, file)
1001
+ end
1002
+ rescue SystemCallError => e
1003
+ raise Bolt::FileError.new("#{e.message}: unable to load guides directory", root_path)
1004
+ end
954
1005
  end
955
1006
 
956
1007
  # Display the list of available Bolt guides.
@@ -1001,10 +1052,10 @@ module Bolt
1001
1052
 
1002
1053
  def analytics
1003
1054
  @analytics ||= begin
1004
- client = Bolt::Analytics.build_client
1005
- client.bundled_content = bundled_content
1006
- client
1007
- end
1055
+ client = Bolt::Analytics.build_client
1056
+ client.bundled_content = bundled_content
1057
+ client
1058
+ end
1008
1059
  end
1009
1060
 
1010
1061
  def bundled_content
@@ -1027,7 +1078,7 @@ module Bolt
1027
1078
  'Task' => [],
1028
1079
  'Plugin' => Bolt::Plugin::BUILTIN_PLUGINS }
1029
1080
  if %w[plan task].include?(options[:subcommand]) && options[:action] == 'run'
1030
- default_content = Bolt::PAL.new([], nil, nil)
1081
+ default_content = Bolt::PAL.new(Bolt::Config::Modulepath.new([]), nil, nil)
1031
1082
  content['Plan'] = default_content.list_plans.each_with_object([]) do |iter, col|
1032
1083
  col << iter&.first
1033
1084
  end
@@ -1042,7 +1093,7 @@ module Bolt
1042
1093
  # Gem installs include the aggregate, canary, and puppetdb_fact modules, while
1043
1094
  # package installs include modules listed in the Bolt repo Puppetfile
1044
1095
  def incomplete_install?
1045
- (Dir.children(Bolt::PAL::MODULES_PATH) - %w[aggregate canary puppetdb_fact]).empty?
1096
+ (Dir.children(Bolt::Config::Modulepath::MODULES_PATH) - %w[aggregate canary puppetdb_fact secure_env_vars]).empty?
1046
1097
  end
1047
1098
 
1048
1099
  # Mimicks the output from Outputter::Human#fatal_error. This should be used to print