bolt 2.6.0 → 2.11.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +4 -3
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +2 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +27 -0
  5. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -0
  6. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +2 -0
  7. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +4 -3
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +192 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +122 -0
  11. data/bolt-modules/boltlib/types/planresult.pp +12 -1
  12. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +3 -1
  13. data/bolt-modules/file/lib/puppet/functions/file/join.rb +1 -1
  14. data/bolt-modules/file/lib/puppet/functions/file/read.rb +2 -1
  15. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
  16. data/bolt-modules/file/lib/puppet/functions/file/write.rb +3 -1
  17. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +43 -0
  18. data/lib/bolt/analytics.rb +1 -1
  19. data/lib/bolt/applicator.rb +3 -2
  20. data/lib/bolt/apply_inventory.rb +1 -1
  21. data/lib/bolt/apply_result.rb +1 -1
  22. data/lib/bolt/apply_target.rb +11 -2
  23. data/lib/bolt/bolt_option_parser.rb +27 -7
  24. data/lib/bolt/catalog.rb +32 -3
  25. data/lib/bolt/cli.rb +52 -22
  26. data/lib/bolt/config.rb +51 -27
  27. data/lib/bolt/config/transport/base.rb +3 -3
  28. data/lib/bolt/config/transport/docker.rb +7 -1
  29. data/lib/bolt/config/transport/local.rb +9 -1
  30. data/lib/bolt/config/transport/orch.rb +4 -2
  31. data/lib/bolt/config/transport/remote.rb +2 -0
  32. data/lib/bolt/config/transport/ssh.rb +81 -3
  33. data/lib/bolt/config/transport/winrm.rb +6 -1
  34. data/lib/bolt/executor.rb +38 -0
  35. data/lib/bolt/inventory.rb +2 -1
  36. data/lib/bolt/inventory/group.rb +1 -0
  37. data/lib/bolt/inventory/inventory.rb +9 -0
  38. data/lib/bolt/inventory/target.rb +17 -1
  39. data/lib/bolt/node/output.rb +1 -1
  40. data/lib/bolt/outputter/human.rb +5 -4
  41. data/lib/bolt/outputter/json.rb +1 -1
  42. data/lib/bolt/pal.rb +32 -14
  43. data/lib/bolt/pal/yaml_plan.rb +1 -0
  44. data/lib/bolt/plugin.rb +14 -8
  45. data/lib/bolt/plugin/env_var.rb +2 -1
  46. data/lib/bolt/plugin/module.rb +40 -7
  47. data/lib/bolt/plugin/prompt.rb +1 -1
  48. data/lib/bolt/plugin/puppetdb.rb +5 -2
  49. data/lib/bolt/project.rb +135 -0
  50. data/lib/bolt/puppetdb/config.rb +16 -28
  51. data/lib/bolt/rerun.rb +1 -1
  52. data/lib/bolt/resource_instance.rb +126 -0
  53. data/lib/bolt/result.rb +46 -23
  54. data/lib/bolt/result_set.rb +2 -5
  55. data/lib/bolt/secret.rb +20 -4
  56. data/lib/bolt/shell/bash.rb +27 -14
  57. data/lib/bolt/shell/bash/tmpdir.rb +1 -1
  58. data/lib/bolt/shell/powershell.rb +43 -15
  59. data/lib/bolt/shell/powershell/snippets.rb +1 -1
  60. data/lib/bolt/target.rb +18 -2
  61. data/lib/bolt/transport/base.rb +24 -8
  62. data/lib/bolt/transport/docker.rb +3 -3
  63. data/lib/bolt/transport/docker/connection.rb +11 -7
  64. data/lib/bolt/transport/local/connection.rb +13 -7
  65. data/lib/bolt/transport/orch.rb +5 -1
  66. data/lib/bolt/transport/ssh.rb +6 -2
  67. data/lib/bolt/transport/ssh/connection.rb +26 -1
  68. data/lib/bolt/transport/ssh/exec_connection.rb +110 -0
  69. data/lib/bolt/transport/winrm/connection.rb +10 -2
  70. data/lib/bolt/version.rb +1 -1
  71. data/lib/bolt_server/pe/pal.rb +1 -38
  72. data/lib/bolt_server/transport_app.rb +7 -7
  73. data/lib/bolt_spec/bolt_context.rb +3 -6
  74. data/lib/bolt_spec/plans.rb +78 -8
  75. data/lib/bolt_spec/plans/action_stubs.rb +37 -7
  76. data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +55 -0
  77. data/lib/bolt_spec/plans/mock_executor.rb +62 -2
  78. data/lib/bolt_spec/run.rb +10 -13
  79. metadata +26 -7
  80. data/lib/bolt/boltdir.rb +0 -54
  81. data/lib/bolt/plugin/pkcs7.rb +0 -104
  82. data/lib/bolt/secret/base.rb +0 -41
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Check if a file exists.
3
+ # Check if a local file exists using Puppet's
4
+ # `Puppet::Parser::Files.find_file()` function. This will only check files that
5
+ # are on the machine Bolt is run on.
4
6
  Puppet::Functions.create_function(:'file::exists', Puppet::Functions::InternalFunction) do
5
7
  # @param filename Absolute path or Puppet file path.
6
8
  # @return Whether the file exists.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Join file paths.
3
+ # Join file paths using ruby's `File.join()` function.
4
4
  Puppet::Functions.create_function(:'file::join') do
5
5
  # @param paths The paths to join.
6
6
  # @return The joined file path.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Read a file and return its contents.
3
+ # Read a file on localhost and return its contents using ruby's `File.read`. This will
4
+ # only read files on the machine you run Bolt on.
4
5
  Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunction) do
5
6
  # @param filename Absolute path or Puppet file path.
6
7
  # @return The file's contents.
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Check if a file is readable.
3
+ # Check if a local file is readable using Puppet's
4
+ # `Puppet::Parser::Files.find_file()` function. This will only check files on the
5
+ # machine you run Bolt on.
4
6
  Puppet::Functions.create_function(:'file::readable', Puppet::Functions::InternalFunction) do
5
7
  # @param filename Absolute path or Puppet file path.
6
8
  # @return Whether the file is readable.
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Write a string to a file.
3
+ # Write a string to a file on localhost using ruby's `File.write`. This will
4
+ # only write files to the machine you run Bolt on. Use `write_file()` to write
5
+ # to remote targets.
4
6
  Puppet::Functions.create_function(:'file::write') do
5
7
  # @param filename Absolute path.
6
8
  # @param content File content to write.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # Display a prompt and wait for a response.
6
+ #
7
+ # > **Note:** Not available in apply block
8
+ Puppet::Functions.create_function(:prompt) do
9
+ # @param prompt The prompt to display.
10
+ # @param options A hash of additional options.
11
+ # @option options [Boolean] sensitive Disable echo back and mark the response as sensitive.
12
+ # @return The response to the prompt.
13
+ # @example Prompt the user if plan execution should continue
14
+ # $response = prompt('Continue executing plan? [Y\N]')
15
+ # @example Prompt the user for sensitive information
16
+ # $password = prompt('Enter your password', 'sensitive' => true)
17
+ dispatch :prompt do
18
+ param 'String', :prompt
19
+ optional_param 'Hash[String[1], Any]', :options
20
+ return_type 'Variant[String, Sensitive]'
21
+ end
22
+
23
+ def prompt(prompt, options = {})
24
+ unless Puppet[:tasks]
25
+ raise Puppet::ParseErrorWithIssue
26
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
27
+ action: 'prompt')
28
+ end
29
+
30
+ options = options.transform_keys(&:to_sym)
31
+
32
+ executor = Puppet.lookup(:bolt_executor)
33
+ executor.report_function_call(self.class.name)
34
+
35
+ response = executor.prompt(prompt, options)
36
+
37
+ if options[:sensitive]
38
+ Puppet::Pops::Types::PSensitiveType::Sensitive.new(response)
39
+ else
40
+ response
41
+ end
42
+ end
43
+ end
@@ -63,7 +63,7 @@ module Bolt
63
63
  disabled: true
64
64
 
65
65
  Read more about what data Bolt collects and why here:
66
- https://puppet.com/docs/bolt/latest/bolt_installing.html#concept-8242
66
+ https://puppet.com/docs/bolt/latest/bolt_installing.html#analytics-data-collection
67
67
  ANALYTICS
68
68
  end
69
69
 
@@ -34,6 +34,8 @@ module Bolt
34
34
  search_dirs << mod.plugins if mod.plugins?
35
35
  search_dirs << mod.pluginfacts if mod.pluginfacts?
36
36
  search_dirs << mod.files if mod.files?
37
+ type_files = "#{mod.path}/types"
38
+ search_dirs << type_files if File.exist?(type_files)
37
39
  search_dirs
38
40
  end
39
41
  end
@@ -155,7 +157,6 @@ module Bolt
155
157
  end
156
158
 
157
159
  plan_vars = scope.to_hash(true, true)
158
- %w[trusted server_facts facts].each { |k| plan_vars.delete(k) }
159
160
 
160
161
  targets = @inventory.get_targets(args[0])
161
162
 
@@ -182,7 +183,7 @@ module Bolt
182
183
  rich_data: true,
183
184
  symbol_as_string: true,
184
185
  type_by_reference: true,
185
- local_reference: false)
186
+ local_reference: true)
186
187
 
187
188
  scope = {
188
189
  code_ast: ast,
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bolt/boltdir'
3
+ require 'bolt/project'
4
4
  require 'bolt/config'
5
5
  require 'bolt/error'
6
6
 
@@ -57,7 +57,7 @@ module Bolt
57
57
  msg = "Report result contains an '_output' key. Catalog application may have printed extraneous output to stdout: #{result['_output']}"
58
58
  # rubocop:enable Layout/LineLength
59
59
  else
60
- msg = "Report did not contain all expected keys missing: #{missing_keys.join(' ,')}"
60
+ msg = "Report did not contain all expected keys missing: #{missing_keys.join(', ')}"
61
61
  end
62
62
 
63
63
  { 'msg' => msg,
@@ -3,7 +3,7 @@
3
3
  module Bolt
4
4
  class ApplyTarget
5
5
  ATTRIBUTES = %i[uri name target_alias config vars facts features
6
- plugin_hooks safe_name].freeze
6
+ plugin_hooks resources safe_name].freeze
7
7
  COMPUTED = %i[host password port protocol user].freeze
8
8
 
9
9
  attr_reader(*ATTRIBUTES)
@@ -24,7 +24,8 @@ module Bolt
24
24
  facts = nil,
25
25
  vars = nil,
26
26
  features = nil,
27
- plugin_hooks = nil)
27
+ plugin_hooks = nil,
28
+ resources = nil)
28
29
  raise Bolt::Error.new("Target objects cannot be instantiated inside apply blocks", 'bolt/apply-error')
29
30
  end
30
31
  # rubocop:enable Lint/UnusedMethodArgument
@@ -57,6 +58,10 @@ module Bolt
57
58
  @user = Addressable::URI.unencode_component(uri_obj.user) || t_conf['user']
58
59
  end
59
60
 
61
+ def to_s
62
+ @safe_name
63
+ end
64
+
60
65
  def parse_uri(string)
61
66
  require 'addressable/uri'
62
67
  if string.nil?
@@ -73,5 +78,9 @@ module Bolt
73
78
  rescue Addressable::URI::InvalidURIError => e
74
79
  raise Bolt::ParseError, "Could not parse target URI: #{e.message}"
75
80
  end
81
+
82
+ def hash
83
+ @name.hash
84
+ end
76
85
  end
77
86
  end
@@ -9,9 +9,9 @@ module Bolt
9
9
  OPTIONS = { inventory: %w[targets query rerun description],
10
10
  authentication: %w[user password password-prompt private-key host-key-check ssl ssl-verify],
11
11
  escalation: %w[run-as sudo-password sudo-password-prompt sudo-executable],
12
- run_context: %w[concurrency inventoryfile save-rerun],
12
+ run_context: %w[concurrency inventoryfile save-rerun cleanup],
13
13
  global_config_setters: %w[modulepath boltdir configfile],
14
- transports: %w[transport connect-timeout tty],
14
+ transports: %w[transport connect-timeout tty ssh-command copy-command],
15
15
  display: %w[format color verbose trace],
16
16
  global: %w[help version debug] }.freeze
17
17
 
@@ -64,7 +64,7 @@ module Bolt
64
64
  { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
65
65
  banner: PLAN_CONVERT_HELP }
66
66
  when 'run'
67
- { flags: ACTION_OPTS + %w[params compile-concurrency tmpdir],
67
+ { flags: ACTION_OPTS + %w[params compile-concurrency tmpdir hiera-config],
68
68
  banner: PLAN_RUN_HELP }
69
69
  when 'show'
70
70
  { flags: OPTIONS[:global] + OPTIONS[:global_config_setters] + %w[filter format],
@@ -112,7 +112,7 @@ module Bolt
112
112
  when 'secret'
113
113
  case action
114
114
  when 'createkeys'
115
- { flags: OPTIONS[:global] + OPTIONS[:global_config_setters] + %w[plugin],
115
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters] + %w[plugin force],
116
116
  banner: SECRET_CREATEKEYS_HELP }
117
117
  when 'decrypt'
118
118
  { flags: OPTIONS[:global] + OPTIONS[:global_config_setters] + %w[plugin],
@@ -594,6 +594,7 @@ module Bolt
594
594
  HELP
595
595
 
596
596
  attr_reader :warnings
597
+
597
598
  def initialize(options)
598
599
  super()
599
600
 
@@ -655,8 +656,8 @@ module Bolt
655
656
  @options[:password] = STDIN.noecho(&:gets).chomp
656
657
  STDERR.puts
657
658
  end
658
- define('--private-key KEY', 'Private ssh key to authenticate with') do |key|
659
- @options[:'private-key'] = key
659
+ define('--private-key KEY', 'Path to private ssh key to authenticate with') do |key|
660
+ @options[:'private-key'] = File.expand_path(key)
660
661
  end
661
662
  define('--[no-]host-key-check', 'Check host keys with SSH') do |host_key_check|
662
663
  @options[:'host-key-check'] = host_key_check
@@ -688,13 +689,17 @@ module Bolt
688
689
 
689
690
  separator "\nRUN CONTEXT OPTIONS"
690
691
  define('-c', '--concurrency CONCURRENCY', Integer,
691
- 'Maximum number of simultaneous connections (default: 100)') do |concurrency|
692
+ 'Maximum number of simultaneous connections') do |concurrency|
692
693
  @options[:concurrency] = concurrency
693
694
  end
694
695
  define('--compile-concurrency CONCURRENCY', Integer,
695
696
  'Maximum number of simultaneous manifest block compiles (default: number of cores)') do |concurrency|
696
697
  @options[:'compile-concurrency'] = concurrency
697
698
  end
699
+ define('--[no-]cleanup',
700
+ 'Whether to clean up temporary files created on targets') do |cleanup|
701
+ @options[:cleanup] = cleanup
702
+ end
698
703
  define('-m', '--modulepath MODULES',
699
704
  "List of directories containing modules, separated by '#{File::PATH_SEPARATOR}'",
700
705
  'Directories are case-sensitive') do |modulepath|
@@ -712,6 +717,10 @@ module Bolt
712
717
  'Directory containing bolt.yaml will be used as the Boltdir.') do |path|
713
718
  @options[:configfile] = path
714
719
  end
720
+ define('--hiera-config FILEPATH',
721
+ 'Specify where to load Hiera config from (default: ~/.puppetlabs/bolt/hiera.yaml)') do |path|
722
+ @options[:'hiera-config'] = File.expand_path(path)
723
+ end
715
724
  define('-i', '--inventoryfile FILEPATH',
716
725
  'Specify where to load inventory from (default: ~/.puppetlabs/bolt/inventory.yaml)') do |path|
717
726
  if ENV.include?(Bolt::Inventory::ENVIRONMENT_VAR)
@@ -733,6 +742,14 @@ module Bolt
733
742
  "Specify a default transport: #{TRANSPORTS.keys.join(', ')}") do |t|
734
743
  @options[:transport] = t
735
744
  end
745
+ define('--ssh-command EXEC', "Executable to use instead of the net-ssh ruby library. ",
746
+ "This option is experimental.") do |exec|
747
+ @options[:'ssh-command'] = exec
748
+ end
749
+ define('--copy-command EXEC', "Command to copy files to remote hosts if using external SSH. ",
750
+ "This option is experimental.") do |exec|
751
+ @options[:'copy-command'] = exec
752
+ end
736
753
  define('--connect-timeout TIMEOUT', Integer, 'Connection timeout (defaults vary)') do |timeout|
737
754
  @options[:'connect-timeout'] = timeout
738
755
  end
@@ -771,6 +788,9 @@ module Bolt
771
788
  'when initializing a project. Resolves and installs all dependencies.') do |modules|
772
789
  @options[:modules] = modules.split(',')
773
790
  end
791
+ define('--force', 'Overwrite existing key pairs') do |_force|
792
+ @options[:force] = true
793
+ end
774
794
 
775
795
  separator "\nGLOBAL OPTIONS"
776
796
  define('-h', '--help', 'Display help') do |_|
@@ -70,9 +70,38 @@ module Bolt
70
70
  bolt_inventory: inv) do
71
71
  Puppet.lookup(:pal_current_node).trusted_data = target['trusted']
72
72
  pal.with_catalog_compiler do |compiler|
73
- # This needs to happen inside the catalog compiler so loaders are initialized for loading
74
- vars = Puppet::Pops::Serialization::FromDataConverter.convert(request['plan_vars'])
75
- pal.send(:add_variables, compiler.send(:topscope), target['variables'].merge(vars))
73
+ # Deserializing needs to happen inside the catalog compiler so
74
+ # loaders are initialized for loading
75
+ plan_vars = Puppet::Pops::Serialization::FromDataConverter.convert(request['plan_vars'])
76
+
77
+ # Facts will be set by the catalog compiler, so we need to ensure
78
+ # that any plan or target variables with the same name are not
79
+ # passed into the apply block to avoid a redefinition error.
80
+ # Filter out plan and target vars separately and raise a Puppet
81
+ # warning if there are any collisions for either. Puppet warning
82
+ # is the only way to log a message that will make it back to Bolt
83
+ # to be printed.
84
+ pv_collisions, pv_filtered = plan_vars.partition do |k, _|
85
+ target['facts'].keys.include?(k)
86
+ end.map(&:to_h)
87
+ unless pv_collisions.empty?
88
+ print_pv = pv_collisions.keys.map { |k| "$#{k}" }.join(', ')
89
+ plural = pv_collisions.keys.length == 1 ? '' : 's'
90
+ Puppet.warning("Plan variable#{plural} #{print_pv} will be overridden by fact#{plural} " \
91
+ "of the same name in the apply block")
92
+ end
93
+
94
+ tv_collisions, tv_filtered = target['variables'].partition do |k, _|
95
+ target['facts'].keys.include?(k)
96
+ end.map(&:to_h)
97
+ unless tv_collisions.empty?
98
+ print_tv = tv_collisions.keys.map { |k| "$#{k}" }.join(', ')
99
+ plural = tv_collisions.keys.length == 1 ? '' : 's'
100
+ Puppet.warning("Target variable#{plural} #{print_tv} " \
101
+ "will be overridden by fact#{plural} of the same name in the apply block")
102
+ end
103
+
104
+ pal.send(:add_variables, compiler.send(:topscope), tv_filtered.merge(pv_filtered))
76
105
 
77
106
  # Configure language strictness in the CatalogCompiler. We want Bolt to be able
78
107
  # to compile most Puppet 4+ manifests, so we default to allowing deprecated functions.
@@ -114,18 +114,19 @@ module Bolt
114
114
  @config = if options[:configfile]
115
115
  Bolt::Config.from_file(options[:configfile], options)
116
116
  else
117
- boltdir = if options[:boltdir]
118
- Bolt::Boltdir.new(options[:boltdir])
117
+ project = if options[:boltdir]
118
+ Bolt::Project.new(options[:boltdir])
119
119
  else
120
- Bolt::Boltdir.find_boltdir(Dir.pwd)
120
+ Bolt::Project.find_boltdir(Dir.pwd)
121
121
  end
122
- Bolt::Config.from_boltdir(boltdir, options)
122
+ Bolt::Config.from_project(project, options)
123
123
  end
124
124
 
125
125
  Bolt::Logger.configure(config.log, config.color)
126
126
 
127
- # Logger must be configured before checking path case, otherwise warnings will not display
127
+ # Logger must be configured before checking path case and project file, otherwise warnings will not display
128
128
  @config.check_path_case('modulepath', @config.modulepath)
129
+ @config.project.check_deprecated_file
129
130
 
130
131
  # Log the file paths for loaded config files
131
132
  config_loaded
@@ -238,6 +239,12 @@ module Bolt
238
239
  if options[:subcommand] == 'command' && (!options[:object] || options[:object].empty?)
239
240
  raise Bolt::CLIError, "Must specify a command to run"
240
241
  end
242
+
243
+ if options[:subcommand] == 'secret' &&
244
+ (options[:action] == 'decrypt' || options[:action] == 'encrypt') &&
245
+ !options[:object]
246
+ raise Bolt::CLIError, "Must specify a value to #{options[:action]}"
247
+ end
241
248
  end
242
249
 
243
250
  def handle_parser_errors
@@ -251,13 +258,11 @@ module Bolt
251
258
  end
252
259
 
253
260
  def puppetdb_client
254
- return @puppetdb_client if @puppetdb_client
255
- puppetdb_config = Bolt::PuppetDB::Config.load_config(nil, config.puppetdb, config.boltdir.path)
256
- @puppetdb_client = Bolt::PuppetDB::Client.new(puppetdb_config)
261
+ plugins.puppetdb_client
257
262
  end
258
263
 
259
264
  def plugins
260
- @plugins ||= Bolt::Plugin.setup(config, pal, puppetdb_client, analytics)
265
+ @plugins ||= Bolt::Plugin.setup(config, pal, analytics)
261
266
  end
262
267
 
263
268
  def query_puppetdb_nodes(query)
@@ -314,7 +319,8 @@ module Bolt
314
319
 
315
320
  screen_view_fields = {
316
321
  output_format: config.format,
317
- boltdir_type: config.boltdir.type
322
+ # For continuity
323
+ boltdir_type: config.project.type
318
324
  }
319
325
 
320
326
  # Only include target and inventory info for commands that take a targets
@@ -447,6 +453,7 @@ module Bolt
447
453
  def list_tasks
448
454
  tasks = pal.list_tasks
449
455
  tasks.select! { |task| task.first.include?(options[:filter]) } if options[:filter]
456
+ tasks.select! { |task| config.project.tasks.include?(task.first) } unless config.project.tasks.nil?
450
457
  outputter.print_tasks(tasks, pal.list_modulepath)
451
458
  end
452
459
 
@@ -457,6 +464,7 @@ module Bolt
457
464
  def list_plans
458
465
  plans = pal.list_plans
459
466
  plans.select! { |plan| plan.first.include?(options[:filter]) } if options[:filter]
467
+ plans.select! { |plan| config.project.plans.include?(plan.first) } unless config.project.plans.nil?
460
468
  outputter.print_plans(plans, pal.list_modulepath)
461
469
  end
462
470
 
@@ -567,10 +575,10 @@ module Bolt
567
575
  # Initializes a specified directory as a Bolt project and installs any modules
568
576
  # specified by the user, along with their dependencies
569
577
  def initialize_project
570
- boltdir = Pathname.new(File.expand_path(options[:object] || Dir.pwd))
571
- config = boltdir + 'bolt.yaml'
572
- puppetfile = boltdir + 'Puppetfile'
573
- modulepath = [boltdir + 'modules']
578
+ project = Pathname.new(File.expand_path(options[:object] || Dir.pwd))
579
+ config = project + 'bolt.yaml'
580
+ puppetfile = project + 'Puppetfile'
581
+ modulepath = [project + 'modules']
574
582
 
575
583
  # If modules were specified, first check if there is already a Puppetfile at the project
576
584
  # directory, erroring if there is. If there is no Puppetfile, generate the Puppetfile
@@ -590,20 +598,20 @@ module Bolt
590
598
  # Warn the user if the project directory already exists. We don't error here since users
591
599
  # might not have installed any modules yet.
592
600
  if config.exist?
593
- @logger.warn "Found existing project directory at #{boltdir}"
601
+ @logger.warn "Found existing project directory at #{project}"
594
602
  end
595
603
 
596
604
  # Create the project directory
597
- FileUtils.mkdir_p(boltdir)
605
+ FileUtils.mkdir_p(project)
598
606
 
599
- # Bless the project directory as a boltdir
607
+ # Bless the project directory as a...wait for it...project
600
608
  if FileUtils.touch(config)
601
- outputter.print_message "Successfully created Bolt project at #{boltdir}"
609
+ outputter.print_message "Successfully created Bolt project at #{project}"
602
610
  else
603
- raise Bolt::FileError.new("Could not create Bolt project directory at #{boltdir}", nil)
611
+ raise Bolt::FileError.new("Could not create Bolt project directory at #{project}", nil)
604
612
  end
605
613
 
606
- # Write the generated Puppetfile to the fancy new boltdir
614
+ # Write the generated Puppetfile to the fancy new project
607
615
  if puppetfile_specs
608
616
  File.write(puppetfile, puppetfile_specs.join("\n"))
609
617
  outputter.print_message "Successfully created Puppetfile at #{puppetfile}"
@@ -752,12 +760,14 @@ module Bolt
752
760
  end
753
761
 
754
762
  def pal
763
+ project = config.project.load_as_module? ? config.project : nil
755
764
  @pal ||= Bolt::PAL.new(config.modulepath,
756
765
  config.hiera_config,
757
- config.boltdir.resource_types,
766
+ config.project.resource_types,
758
767
  config.compile_concurrency,
759
768
  config.trusted_external,
760
- config.apply_settings)
769
+ config.apply_settings,
770
+ project)
761
771
  end
762
772
 
763
773
  def convert_plan(plan)
@@ -793,6 +803,20 @@ module Bolt
793
803
  end
794
804
 
795
805
  def bundled_content
806
+ # If the bundled content directory is empty, Bolt is likely installed as a gem.
807
+ if ENV['BOLT_GEM'].nil? && incomplete_install?
808
+ msg = <<~MSG.chomp
809
+ Bolt may be installed as a gem. To use Bolt reliably and with all of its
810
+ dependencies, uninstall the 'bolt' gem and install Bolt as a package:
811
+ https://puppet.com/docs/bolt/latest/bolt_installing.html
812
+
813
+ If you meant to install Bolt as a gem and want to disable this warning,
814
+ set the BOLT_GEM environment variable.
815
+ MSG
816
+
817
+ @logger.warn(msg)
818
+ end
819
+
796
820
  # We only need to enumerate bundled content when running a task or plan
797
821
  content = { 'Plan' => [],
798
822
  'Task' => [],
@@ -816,5 +840,11 @@ module Bolt
816
840
  MSG
817
841
  @logger.debug(msg)
818
842
  end
843
+
844
+ # Gem installs include the aggregate, canary, and puppetdb_fact modules, while
845
+ # package installs include modules listed in the Bolt repo Puppetfile
846
+ def incomplete_install?
847
+ (Dir.children(Bolt::PAL::MODULES_PATH) - %w[aggregate canary puppetdb_fact]).empty?
848
+ end
819
849
  end
820
850
  end