bolt 2.36.0 → 2.42.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +8 -8
  3. data/lib/bolt/bolt_option_parser.rb +7 -3
  4. data/lib/bolt/cli.rb +67 -23
  5. data/lib/bolt/config.rb +70 -45
  6. data/lib/bolt/config/options.rb +104 -79
  7. data/lib/bolt/config/transport/base.rb +2 -2
  8. data/lib/bolt/config/transport/local.rb +1 -0
  9. data/lib/bolt/config/transport/options.rb +11 -68
  10. data/lib/bolt/config/transport/ssh.rb +0 -5
  11. data/lib/bolt/inventory.rb +26 -0
  12. data/lib/bolt/inventory/group.rb +29 -9
  13. data/lib/bolt/inventory/inventory.rb +1 -1
  14. data/lib/bolt/inventory/options.rb +130 -0
  15. data/lib/bolt/inventory/target.rb +10 -11
  16. data/lib/bolt/module.rb +10 -2
  17. data/lib/bolt/module_installer.rb +21 -13
  18. data/lib/bolt/module_installer/resolver.rb +13 -5
  19. data/lib/bolt/outputter.rb +19 -5
  20. data/lib/bolt/outputter/human.rb +20 -1
  21. data/lib/bolt/outputter/json.rb +1 -1
  22. data/lib/bolt/outputter/logger.rb +1 -1
  23. data/lib/bolt/outputter/rainbow.rb +12 -1
  24. data/lib/bolt/pal/yaml_plan/transpiler.rb +5 -1
  25. data/lib/bolt/plugin.rb +42 -6
  26. data/lib/bolt/plugin/cache.rb +76 -0
  27. data/lib/bolt/plugin/module.rb +4 -4
  28. data/lib/bolt/plugin/puppetdb.rb +1 -1
  29. data/lib/bolt/project.rb +38 -13
  30. data/lib/bolt/project_manager.rb +2 -0
  31. data/lib/bolt/project_manager/config_migrator.rb +9 -1
  32. data/lib/bolt/project_manager/module_migrator.rb +2 -0
  33. data/lib/bolt/puppetdb/client.rb +8 -0
  34. data/lib/bolt/rerun.rb +1 -5
  35. data/lib/bolt/shell/bash.rb +7 -1
  36. data/lib/bolt/shell/powershell.rb +21 -3
  37. data/lib/bolt/target.rb +4 -0
  38. data/lib/bolt/transport/local.rb +13 -0
  39. data/lib/bolt/util.rb +22 -0
  40. data/lib/bolt/validator.rb +227 -0
  41. data/lib/bolt/version.rb +1 -1
  42. data/lib/bolt_server/plugin.rb +13 -0
  43. data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
  44. data/lib/bolt_server/schemas/connect-data.json +22 -0
  45. data/lib/bolt_server/schemas/partials/task.json +1 -1
  46. data/lib/bolt_server/transport_app.rb +64 -36
  47. metadata +24 -5
  48. data/lib/bolt/config/validator.rb +0 -231
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1881e4097a89e606b9326f93e2e6fd9d934a7db1eb8609e6f9a09ffa322ac7c4
4
- data.tar.gz: '02509b1919e57f137bf4e868332be3b73238d22d83251680dc29c7c93ef90a19'
3
+ metadata.gz: d520aad8f699a4a52849ac189a6368b799238996a0c4dae22e75f2abfbe43eff
4
+ data.tar.gz: befcf8835708c4ec4605e560dfd060680a6d680b7614bfd3d4d4baba7fb7cfa5
5
5
  SHA512:
6
- metadata.gz: 1fc1215430ac99b765c24c87ee210afb03a247a6c0baac9af3b00e34d56cfb58bacb2140dcfafbe197fd655602713463e2697c2df5083c259b66c8d8cd7bc112
7
- data.tar.gz: 445a5f77a5beaf8a1a3c4cb2193e973ada23cb4a7a5f3a8caba2ccc47be4fc8a8fdef3ba797e082c0df3b9553c2f2e67c06084f2c2b2684347d2b9a86e45bde0
6
+ metadata.gz: 0f25f959178fe587c518723cdc1dffd7bd420ea1d943ff1dcfda410cffd0ad592d3cd5714f5d4707dc9a4aa6fec0eb3103382a0266bf56f0625c2c06bc325f98
7
+ data.tar.gz: 7b05c93e0f507429bd5c43d5a0d0ed51f42d8ac1c0621c1c125ac1905a193a09e6802bb898f90650f7b95d8b0a2c5c513adbf93b3626d109936a447dcfaaf3dd
data/Puppetfile CHANGED
@@ -5,14 +5,14 @@ forge "http://forge.puppetlabs.com"
5
5
  moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
- mod 'puppetlabs-service', '1.3.0'
9
- mod 'puppetlabs-puppet_agent', '4.2.0'
10
- mod 'puppetlabs-facts', '1.2.0'
8
+ mod 'puppetlabs-service', '1.4.0'
9
+ mod 'puppetlabs-puppet_agent', '4.3.0'
10
+ mod 'puppetlabs-facts', '1.3.0'
11
11
 
12
12
  # Core types and providers for Puppet 6
13
13
  mod 'puppetlabs-augeas_core', '1.1.1'
14
14
  mod 'puppetlabs-host_core', '1.0.3'
15
- mod 'puppetlabs-scheduled_task', '2.2.1'
15
+ mod 'puppetlabs-scheduled_task', '2.3.1'
16
16
  mod 'puppetlabs-sshkeys_core', '2.2.0'
17
17
  mod 'puppetlabs-zfs_core', '1.2.0'
18
18
  mod 'puppetlabs-cron_core', '1.0.5'
@@ -22,10 +22,10 @@ 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.3.0'
26
- mod 'puppetlabs-puppet_conf', '0.6.0'
25
+ mod 'puppetlabs-package', '1.4.0'
26
+ mod 'puppetlabs-puppet_conf', '0.8.0'
27
27
  mod 'puppetlabs-python_task_helper', '0.4.3'
28
- mod 'puppetlabs-reboot', '3.0.0'
28
+ mod 'puppetlabs-reboot', '3.1.0'
29
29
  mod 'puppetlabs-ruby_task_helper', '0.5.1'
30
30
  mod 'puppetlabs-ruby_plugin_helper', '0.1.0'
31
31
  mod 'puppetlabs-stdlib', '6.5.0'
@@ -34,7 +34,7 @@ mod 'puppetlabs-stdlib', '6.5.0'
34
34
  mod 'puppetlabs-aws_inventory', '0.5.2'
35
35
  mod 'puppetlabs-azure_inventory', '0.4.1'
36
36
  mod 'puppetlabs-gcloud_inventory', '0.1.3'
37
- mod 'puppetlabs-http_request', '0.2.0'
37
+ mod 'puppetlabs-http_request', '0.2.1'
38
38
  mod 'puppetlabs-pkcs7', '0.1.1'
39
39
  mod 'puppetlabs-secure_env_vars', '0.1.0'
40
40
  mod 'puppetlabs-terraform', '0.5.0'
@@ -14,7 +14,7 @@ module Bolt
14
14
  global_config_setters: PROJECT_PATHS + %w[modulepath],
15
15
  transports: %w[transport connect-timeout tty native-ssh ssh-command copy-command],
16
16
  display: %w[format color verbose trace],
17
- global: %w[help version debug log-level] }.freeze
17
+ global: %w[help version debug log-level clear-cache] }.freeze
18
18
 
19
19
  ACTION_OPTS = OPTIONS.values.flatten.freeze
20
20
 
@@ -969,8 +969,8 @@ module Bolt
969
969
  end
970
970
 
971
971
  separator "\nPLAN OPTIONS"
972
- define('--pp', 'Create a new Puppet language plan.') do |pp|
973
- @options[:puppet] = pp
972
+ define('--pp', 'Create a new Puppet language plan.') do |_|
973
+ @options[:puppet] = true
974
974
  end
975
975
 
976
976
  separator "\nDISPLAY OPTIONS"
@@ -1025,6 +1025,10 @@ module Bolt
1025
1025
  "trace, debug, info, warn, error, fatal, any.") do |level|
1026
1026
  @options[:log] = { 'console' => { 'level' => level } }
1027
1027
  end
1028
+ define('--clear-cache',
1029
+ "Clear plugin cache before executing") do |_|
1030
+ @options[:clear_cache] = true
1031
+ end
1028
1032
  define('--plugin PLUGIN', 'Select the plugin to use') do |plug|
1029
1033
  @options[:plugin] = plug
1030
1034
  end
@@ -197,7 +197,12 @@ module Bolt
197
197
  @parser_deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
198
198
  config.deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
199
199
 
200
+ if options[:clear_cache] && File.exist?(config.project.cache_file)
201
+ FileUtils.rm(config.project.cache_file)
202
+ end
203
+
200
204
  warn_inventory_overrides_cli(options)
205
+ validate_ps_version
201
206
 
202
207
  options
203
208
  rescue Bolt::Error => e
@@ -205,6 +210,20 @@ module Bolt
205
210
  raise e
206
211
  end
207
212
 
213
+ private def validate_ps_version
214
+ if Bolt::Util.powershell?
215
+ command = "powershell.exe -NoProfile -NonInteractive -NoLogo -ExecutionPolicy "\
216
+ "Bypass -Command $PSVersionTable.PSVersion.Major"
217
+ stdout, _stderr, _status = Open3.capture3(command)
218
+
219
+ return unless !stdout.empty? && stdout.to_i < 3
220
+
221
+ msg = "Detected PowerShell 2 on controller. PowerShell 2 is deprecated and "\
222
+ "support will be removed in Bolt 3.0."
223
+ Bolt::Logger.deprecation_warning("PowerShell 2 controller", msg)
224
+ end
225
+ end
226
+
208
227
  def update_targets(options)
209
228
  target_opts = options.keys.select { |opt| %i[query rerun targets].include?(opt) }
210
229
  target_string = "'--targets', '--rerun', or '--query'"
@@ -475,9 +494,9 @@ module Bolt
475
494
  when 'module'
476
495
  case options[:action]
477
496
  when 'add'
478
- code = add_project_module(options[:object], config.project)
497
+ code = add_project_module(options[:object], config.project, config.module_install)
479
498
  when 'install'
480
- code = install_project_modules(config.project, options[:force], options[:resolve])
499
+ code = install_project_modules(config.project, config.module_install, options[:force], options[:resolve])
481
500
  when 'generate-types'
482
501
  code = generate_types
483
502
  end
@@ -522,12 +541,15 @@ module Bolt
522
541
  validate_file('script', script)
523
542
  executor.run_script(targets, script, options[:leftovers], executor_opts)
524
543
  when 'task'
525
- pal.run_task(options[:object],
526
- targets,
527
- options[:task_options],
528
- executor,
529
- inventory,
530
- options[:description])
544
+ r = outputter.spin do
545
+ pal.run_task(options[:object],
546
+ targets,
547
+ options[:task_options],
548
+ executor,
549
+ inventory,
550
+ options[:description])
551
+ end
552
+ r
531
553
  when 'file'
532
554
  src = options[:object]
533
555
  dest = options[:leftovers].first
@@ -662,7 +684,9 @@ module Bolt
662
684
 
663
685
  executor.subscribe(log_outputter)
664
686
  executor.start_plan(plan_context)
665
- result = pal.run_plan(plan_name, plan_arguments, executor, inventory, puppetdb_client)
687
+ result = outputter.spin do
688
+ pal.run_plan(plan_name, plan_arguments, executor, inventory, puppetdb_client)
689
+ end
666
690
 
667
691
  # If a non-bolt exception bubbles up the plan won't get finished
668
692
  executor.finish_plan(result)
@@ -727,7 +751,7 @@ module Bolt
727
751
 
728
752
  # Installs modules declared in the project configuration file.
729
753
  #
730
- def install_project_modules(project, force, resolve)
754
+ def install_project_modules(project, config, force, resolve)
731
755
  assert_project_file(project)
732
756
  assert_puppetfile_or_module_command(project.modules)
733
757
 
@@ -737,30 +761,39 @@ module Bolt
737
761
  return 0
738
762
  end
739
763
 
764
+ modules = project.modules || []
740
765
  installer = Bolt::ModuleInstaller.new(outputter, pal)
741
766
 
742
- ok = installer.install(project.modules,
743
- project.puppetfile,
744
- project.managed_moduledir,
745
- force: force,
746
- resolve: resolve)
767
+ ok = outputter.spin do
768
+ installer.install(modules,
769
+ project.puppetfile,
770
+ project.managed_moduledir,
771
+ config,
772
+ force: force,
773
+ resolve: resolve)
774
+ end
775
+
747
776
  ok ? 0 : 1
748
777
  end
749
778
 
750
779
  # Adds a single module to the project.
751
780
  #
752
- def add_project_module(name, project)
781
+ def add_project_module(name, project, config)
753
782
  assert_project_file(project)
754
783
  assert_puppetfile_or_module_command(project.modules)
755
784
 
756
785
  modules = project.modules || []
757
786
  installer = Bolt::ModuleInstaller.new(outputter, pal)
758
787
 
759
- ok = installer.add(name,
760
- modules,
761
- project.puppetfile,
762
- project.managed_moduledir,
763
- project.project_file)
788
+ ok = outputter.spin do
789
+ installer.add(name,
790
+ modules,
791
+ project.puppetfile,
792
+ project.managed_moduledir,
793
+ project.project_file,
794
+ config)
795
+ end
796
+
764
797
  ok ? 0 : 1
765
798
  end
766
799
 
@@ -789,7 +822,10 @@ module Bolt
789
822
 
790
823
  outputter.print_message("Installing modules from Puppetfile")
791
824
  installer = Bolt::ModuleInstaller.new(outputter, pal)
792
- ok = installer.install_puppetfile(puppetfile, moduledir, puppetfile_config)
825
+ ok = outputter.spin do
826
+ installer.install_puppetfile(puppetfile, moduledir, puppetfile_config)
827
+ end
828
+
793
829
  ok ? 0 : 1
794
830
  end
795
831
 
@@ -822,6 +858,10 @@ module Bolt
822
858
  raise Bolt::CLIError,
823
859
  "Unable to use command '#{old_command}' when 'modules' is configured in "\
824
860
  "bolt-project.yaml. Use '#{new_command}' instead."
861
+ elsif modules.nil? && options[:subcommand] == 'puppetfile'
862
+ msg = "Command '#{old_command}' is deprecated and will be removed in Bolt 3.0. Update your project to use "\
863
+ "the module management feature. For more information, see https://pup.pt/bolt-module-migrate."
864
+ Bolt::Logger.deprecation_warning('puppetfile command', msg)
825
865
  elsif modules.nil? && options[:subcommand] == 'module'
826
866
  msg = "Unable to use command '#{new_command}' when 'modules' is not configured in "\
827
867
  "bolt-project.yaml. "
@@ -895,7 +935,11 @@ module Bolt
895
935
  end
896
936
 
897
937
  def outputter
898
- @outputter ||= Bolt::Outputter.for_format(config.format, config.color, options[:verbose], config.trace)
938
+ @outputter ||= Bolt::Outputter.for_format(config.format,
939
+ config.color,
940
+ options[:verbose],
941
+ config.trace,
942
+ config.spinner)
899
943
  end
900
944
 
901
945
  def log_outputter
@@ -7,7 +7,7 @@ require 'bolt/project'
7
7
  require 'bolt/logger'
8
8
  require 'bolt/util'
9
9
  require 'bolt/config/options'
10
- require 'bolt/config/validator'
10
+ require 'bolt/validator'
11
11
 
12
12
  module Bolt
13
13
  class UnknownTransportError < Bolt::Error
@@ -43,7 +43,7 @@ module Bolt
43
43
 
44
44
  # Validate the config against the schema. This will raise a single error
45
45
  # with all validation errors.
46
- Validator.new.tap do |validator|
46
+ Bolt::Validator.new.tap do |validator|
47
47
  validator.validate(c, bolt_schema, project.config_file.to_s)
48
48
 
49
49
  validator.warnings.each { |warning| logs << { warn: warning } }
@@ -56,7 +56,6 @@ module Bolt
56
56
  logs << { debug: "Loaded configuration from #{project.config_file}" } if File.exist?(project.config_file)
57
57
  c
58
58
  end
59
-
60
59
  data = load_defaults(project).push(
61
60
  filepath: project.config_file,
62
61
  data: conf,
@@ -79,7 +78,7 @@ module Bolt
79
78
 
80
79
  # Validate the config against the schema. This will raise a single error
81
80
  # with all validation errors.
82
- Validator.new.tap do |validator|
81
+ Bolt::Validator.new.tap do |validator|
83
82
  validator.validate(c, bolt_schema, project.config_file.to_s)
84
83
 
85
84
  validator.warnings.each { |warning| logs << { warn: warning } }
@@ -103,22 +102,36 @@ module Bolt
103
102
  new(project, data, overrides)
104
103
  end
105
104
 
106
- def self.defaults_schema
107
- base = OPTIONS.slice(*BOLT_DEFAULTS_OPTIONS)
108
- inventory = INVENTORY_OPTIONS.each_with_object({}) do |(option, definition), acc|
105
+ # Builds a hash of definitions for transport configuration.
106
+ #
107
+ def self.transport_definitions
108
+ INVENTORY_OPTIONS.each_with_object({}) do |(option, definition), acc|
109
109
  acc[option] = TRANSPORT_CONFIG.key?(option) ? definition.merge(TRANSPORT_CONFIG[option].schema) : definition
110
110
  end
111
+ end
112
+
113
+ # Builds the schema for bolt-defaults.yaml used by the validator.
114
+ #
115
+ def self.defaults_schema
116
+ schema = {
117
+ type: Hash,
118
+ properties: BOLT_DEFAULTS_OPTIONS.map { |opt| [opt, _ref: opt] }.to_h,
119
+ definitions: OPTIONS.merge(transport_definitions)
120
+ }
121
+
122
+ schema[:definitions]['inventory-config'][:properties] = transport_definitions
111
123
 
112
- base['inventory-config'][:properties] = inventory
113
- base
124
+ schema
114
125
  end
115
126
 
127
+ # Builds the schema for bolt.yaml used by the validator.
128
+ #
116
129
  def self.bolt_schema
117
- inventory = INVENTORY_OPTIONS.each_with_object({}) do |(option, definition), acc|
118
- acc[option] = TRANSPORT_CONFIG.key?(option) ? definition.merge(TRANSPORT_CONFIG[option].schema) : definition
119
- end
120
-
121
- OPTIONS.slice(*BOLT_OPTIONS).merge(inventory)
130
+ {
131
+ type: Hash,
132
+ properties: (BOLT_OPTIONS + INVENTORY_OPTIONS.keys).map { |opt| [opt, _ref: opt] }.to_h,
133
+ definitions: OPTIONS.merge(transport_definitions)
134
+ }
122
135
  end
123
136
 
124
137
  def self.system_path
@@ -154,7 +167,7 @@ module Bolt
154
167
 
155
168
  # Validate the config against the schema. This will raise a single error
156
169
  # with all validation errors.
157
- Validator.new.tap do |validator|
170
+ Bolt::Validator.new.tap do |validator|
158
171
  validator.validate(data, defaults_schema, filepath)
159
172
 
160
173
  validator.warnings.each { |warning| logs << { warn: warning } }
@@ -216,12 +229,12 @@ module Bolt
216
229
  data = Bolt::Util.read_yaml_hash(filepath, 'config')
217
230
  logs = [{ debug: "Loaded configuration from #{filepath}" }]
218
231
  deprecations = [{ type: 'Using bolt.yaml for system configuration',
219
- msg: "Configuration file #{filepath} is deprecated and will be removed in a future version "\
220
- "of Bolt. Use '#{dir + BOLT_DEFAULTS_NAME}' instead." }]
232
+ msg: "Configuration file #{filepath} is deprecated and will be removed in Bolt 3.0. "\
233
+ "See https://pup.pt/update-bolt-config for how to update to the latest Bolt practices." }]
221
234
 
222
235
  # Validate the config against the schema. This will raise a single error
223
236
  # with all validation errors.
224
- Validator.new.tap do |validator|
237
+ Bolt::Validator.new.tap do |validator|
225
238
  validator.validate(data, bolt_schema, filepath)
226
239
 
227
240
  validator.warnings.each { |warning| logs << { warn: warning } }
@@ -283,12 +296,14 @@ module Bolt
283
296
  'concurrency' => default_concurrency,
284
297
  'format' => 'human',
285
298
  'log' => { 'console' => {} },
299
+ 'module-install' => {},
286
300
  'plugin-hooks' => {},
287
301
  'plugin_hooks' => {},
288
302
  'plugins' => {},
289
303
  'puppetdb' => {},
290
304
  'puppetfile' => {},
291
305
  'save-rerun' => true,
306
+ 'spinner' => true,
292
307
  'transport' => 'ssh'
293
308
  }
294
309
 
@@ -353,7 +368,7 @@ module Bolt
353
368
  overrides['trace'] = opts['trace'] if opts.key?('trace')
354
369
 
355
370
  # Validate the overrides
356
- Validator.new.validate(overrides, OPTIONS, 'command line')
371
+ Bolt::Validator.new.validate(overrides, self.class.bolt_schema, 'command line')
357
372
 
358
373
  overrides
359
374
  end
@@ -408,7 +423,7 @@ module Bolt
408
423
  end
409
424
 
410
425
  # Filter hashes to only include valid options
411
- %w[apply-settings apply_settings puppetfile].each do |opt|
426
+ %w[apply-settings apply_settings module-install puppetfile].each do |opt|
412
427
  @data[opt] = @data[opt].slice(*OPTIONS.dig(opt, :properties).keys)
413
428
  end
414
429
  end
@@ -433,31 +448,14 @@ module Bolt
433
448
  next if val == 'disable'
434
449
 
435
450
  name = normalize_log(key)
451
+ acc[name] = val.slice('append', 'level').transform_keys(&:to_sym)
436
452
 
437
- # But otherwise it has to be a Hash
438
- unless val.is_a?(Hash)
439
- raise Bolt::ValidationError,
440
- "config of log #{name} must be a Hash, received #{val.class} #{val.inspect}"
441
- end
442
-
443
- acc[name] = val.slice('append', 'level')
444
- .transform_keys(&:to_sym)
453
+ next unless acc[name][:level] == 'notice'
445
454
 
446
- if (v = acc[name][:level])
447
- unless v.is_a?(String) || v.is_a?(Symbol)
448
- raise Bolt::ValidationError,
449
- "level of log #{name} must be a String or Symbol, received #{v.class} #{v.inspect}"
450
- end
451
- unless Bolt::Logger.valid_level?(v)
452
- raise Bolt::ValidationError,
453
- "level of log #{name} must be one of #{Bolt::Logger.levels.join(', ')}; received #{v}"
454
- end
455
- end
456
-
457
- if (v = acc[name][:append]) && v != true && v != false
458
- raise Bolt::ValidationError,
459
- "append flag of log #{name} must be a Boolean, received #{v.class} #{v.inspect}"
460
- end
455
+ @deprecations << {
456
+ type: 'notice log level',
457
+ msg: "Log level 'notice' is deprecated and will be removed in Bolt 3.0. Use 'info' instead."
458
+ }
461
459
  end
462
460
  end
463
461
 
@@ -486,8 +484,23 @@ module Bolt
486
484
  Bolt::Util.validate_file('inventory file', default_inventoryfile)
487
485
  end
488
486
 
489
- unless TRANSPORT_CONFIG.include?(transport)
490
- raise UnknownTransportError, transport
487
+ # Warn the user how they should be using the 'puppetfile' or
488
+ # 'module-install' config options. We don't error here since these
489
+ # settings can be set at the user or system level.
490
+ if @project.modules && puppetfile_config.any? && module_install.empty?
491
+ command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
492
+ @logs << { warn: "Detected configuration for 'puppetfile'. This setting is not "\
493
+ "used when 'modules' is configured. Use 'module-install' instead. "\
494
+ "To automatically update your project configuration, run '#{command}'." }
495
+ elsif @project.modules.nil? && puppetfile_config.empty? && module_install.any?
496
+ @logs << { warn: "Detected configuration for 'module-install'. This setting is not "\
497
+ "used when 'modules' is not configured. Use 'puppetfile' instead." }
498
+ elsif @project.modules && puppetfile_config.any? && module_install.any?
499
+ @logs << { warn: "Detected configuration for 'puppetfile' and 'module-install'. Using "\
500
+ "configuration for 'module-install' because 'modules' is also configured." }
501
+ elsif @project.modules.nil? && puppetfile_config.any? && module_install.any?
502
+ @logs << { warn: "Detected configuration for 'puppetfile' and 'module-install'. Using "\
503
+ "configuration for 'puppetfile' because 'modules' is not configured." }
491
504
  end
492
505
  end
493
506
 
@@ -521,6 +534,10 @@ module Bolt
521
534
  @data['modulepath'] = value
522
535
  end
523
536
 
537
+ def plugin_cache
538
+ @project.plugin_cache || @data['plugin-cache'] || {}
539
+ end
540
+
524
541
  def concurrency
525
542
  @data['concurrency']
526
543
  end
@@ -553,6 +570,10 @@ module Bolt
553
570
  @data['save-rerun']
554
571
  end
555
572
 
573
+ def spinner
574
+ @data['spinner']
575
+ end
576
+
556
577
  def inventoryfile
557
578
  @data['inventoryfile']
558
579
  end
@@ -607,6 +628,10 @@ module Bolt
607
628
  @data['transport']
608
629
  end
609
630
 
631
+ def module_install
632
+ @project.module_install || @data['module-install']
633
+ end
634
+
610
635
  # Check if there is a case-insensitive match to the path
611
636
  def check_path_case(type, paths)
612
637
  return if paths.nil?