bolt 2.33.1 → 2.37.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +1 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -3
  5. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
  6. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
  10. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
  12. data/lib/bolt/analytics.rb +27 -8
  13. data/lib/bolt/apply_result.rb +3 -3
  14. data/lib/bolt/bolt_option_parser.rb +48 -16
  15. data/lib/bolt/cli.rb +95 -227
  16. data/lib/bolt/config.rb +145 -54
  17. data/lib/bolt/config/options.rb +76 -10
  18. data/lib/bolt/config/transport/base.rb +10 -19
  19. data/lib/bolt/config/transport/local.rb +0 -7
  20. data/lib/bolt/config/transport/options.rb +1 -1
  21. data/lib/bolt/config/transport/ssh.rb +8 -14
  22. data/lib/bolt/config/validator.rb +231 -0
  23. data/lib/bolt/error.rb +33 -3
  24. data/lib/bolt/executor.rb +92 -6
  25. data/lib/bolt/inventory/group.rb +2 -1
  26. data/lib/bolt/module_installer.rb +1 -1
  27. data/lib/bolt/module_installer/specs/forge_spec.rb +5 -4
  28. data/lib/bolt/module_installer/specs/git_spec.rb +4 -3
  29. data/lib/bolt/outputter/human.rb +21 -9
  30. data/lib/bolt/outputter/rainbow.rb +1 -1
  31. data/lib/bolt/pal.rb +19 -7
  32. data/lib/bolt/pal/yaml_plan.rb +7 -0
  33. data/lib/bolt/plan_creator.rb +160 -0
  34. data/lib/bolt/plugin.rb +42 -13
  35. data/lib/bolt/plugin/cache.rb +76 -0
  36. data/lib/bolt/plugin/module.rb +4 -4
  37. data/lib/bolt/project.rb +46 -40
  38. data/lib/bolt/project_manager.rb +199 -0
  39. data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +43 -5
  40. data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +5 -5
  41. data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
  42. data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +3 -3
  43. data/lib/bolt/puppetdb/client.rb +3 -2
  44. data/lib/bolt/puppetdb/config.rb +9 -8
  45. data/lib/bolt/rerun.rb +1 -5
  46. data/lib/bolt/shell/bash.rb +8 -2
  47. data/lib/bolt/shell/powershell.rb +17 -1
  48. data/lib/bolt/task/run.rb +1 -1
  49. data/lib/bolt/transport/orch.rb +0 -5
  50. data/lib/bolt/transport/orch/connection.rb +10 -3
  51. data/lib/bolt/transport/remote.rb +1 -1
  52. data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
  53. data/lib/bolt/util.rb +41 -7
  54. data/lib/bolt/version.rb +1 -1
  55. data/lib/bolt/yarn.rb +23 -0
  56. data/lib/bolt_server/base_config.rb +3 -1
  57. data/lib/bolt_server/config.rb +3 -1
  58. data/lib/bolt_server/file_cache.rb +2 -0
  59. data/lib/bolt_server/plugin.rb +13 -0
  60. data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
  61. data/lib/bolt_server/schemas/connect-data.json +22 -0
  62. data/lib/bolt_server/schemas/partials/task.json +2 -2
  63. data/lib/bolt_server/transport_app.rb +72 -13
  64. data/lib/bolt_spec/plans/mock_executor.rb +4 -1
  65. data/libexec/apply_catalog.rb +1 -1
  66. data/libexec/custom_facts.rb +1 -1
  67. data/libexec/query_resources.rb +1 -1
  68. metadata +15 -13
  69. data/lib/bolt/project_migrator.rb +0 -80
@@ -15,20 +15,22 @@ require 'bolt/config'
15
15
  require 'bolt/error'
16
16
  require 'bolt/executor'
17
17
  require 'bolt/inventory'
18
- require 'bolt/rerun'
19
18
  require 'bolt/logger'
19
+ require 'bolt/module_installer'
20
20
  require 'bolt/outputter'
21
- require 'bolt/puppetdb'
22
- require 'bolt/plugin'
23
- require 'bolt/project_migrator'
24
21
  require 'bolt/pal'
22
+ require 'bolt/plan_creator'
23
+ require 'bolt/plugin'
24
+ require 'bolt/project_manager'
25
+ require 'bolt/puppetdb'
26
+ require 'bolt/rerun'
27
+ require 'bolt/secret'
25
28
  require 'bolt/target'
26
29
  require 'bolt/version'
27
- require 'bolt/secret'
28
- require 'bolt/module_installer'
29
30
 
30
31
  module Bolt
31
32
  class CLIExit < StandardError; end
33
+
32
34
  class CLI
33
35
  COMMANDS = {
34
36
  'command' => %w[run],
@@ -163,8 +165,9 @@ module Bolt
163
165
  elsif options[:configfile]
164
166
  Bolt::Config.from_file(options[:configfile], options)
165
167
  else
166
- project = if options[:boltdir]
167
- dir = Pathname.new(options[:boltdir])
168
+ cli_flag = options[:project] || options[:boltdir]
169
+ project = if cli_flag
170
+ dir = Pathname.new(cli_flag)
168
171
  if (dir + Bolt::Project::BOLTDIR_NAME).directory?
169
172
  Bolt::Project.create_project(dir + Bolt::Project::BOLTDIR_NAME)
170
173
  else
@@ -194,11 +197,12 @@ module Bolt
194
197
  @parser_deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
195
198
  config.deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
196
199
 
197
- warn_inventory_overrides_cli(options)
200
+ if options[:clear_cache] && File.exist?(config.project.cache_file)
201
+ FileUtils.rm(config.project.cache_file)
202
+ end
198
203
 
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)
204
+ warn_inventory_overrides_cli(options)
205
+ validate_ps_version
202
206
 
203
207
  options
204
208
  rescue Bolt::Error => e
@@ -206,6 +210,17 @@ module Bolt
206
210
  raise e
207
211
  end
208
212
 
213
+ private def validate_ps_version
214
+ if Bolt::Util.powershell?
215
+ target = inventory.get_target('localhost')
216
+ Bolt::Transport::Local.new.with_connection(target) do |conn|
217
+ # This will automatically validate the powershell version on the Bolt
218
+ # controller
219
+ Bolt::Shell::Powershell.new(target, conn)
220
+ end
221
+ end
222
+ end
223
+
209
224
  def update_targets(options)
210
225
  target_opts = options.keys.select { |opt| %i[query rerun targets].include?(opt) }
211
226
  target_string = "'--targets', '--rerun', or '--query'"
@@ -228,9 +243,9 @@ module Bolt
228
243
 
229
244
  def validate(options)
230
245
  unless COMMANDS.include?(options[:subcommand])
246
+ command = Bolt::Util.powershell? ? 'Get-Command -Module PuppetBolt' : 'bolt help'
231
247
  raise Bolt::CLIError,
232
- "Expected subcommand '#{options[:subcommand]}' to be one of " \
233
- "#{COMMANDS.keys.join(', ')}"
248
+ "'#{options[:subcommand]}' is not a Bolt command. See '#{command}'."
234
249
  end
235
250
 
236
251
  actions = COMMANDS[options[:subcommand]]
@@ -285,8 +300,9 @@ module Bolt
285
300
  end
286
301
 
287
302
  if options[:subcommand] == 'module' && options[:action] == 'install' && options[:object]
303
+ command = Bolt::Util.powershell? ? 'Add-BoltModule -Module' : 'bolt module add'
288
304
  raise Bolt::CLIError, "Invalid argument '#{options[:object]}'. To add a new module to "\
289
- "the project, run 'bolt module add #{options[:object]}'."
305
+ "the project, run '#{command} #{options[:object]}'."
290
306
  end
291
307
 
292
308
  if options[:subcommand] != 'file' && options[:subcommand] != 'script' &&
@@ -295,7 +311,7 @@ module Bolt
295
311
  "Unknown argument(s) #{options[:leftovers].join(', ')}"
296
312
  end
297
313
 
298
- if options[:boltdir] && options[:configfile]
314
+ if options.slice(:boltdir, :configfile, :project).length > 1
299
315
  raise Bolt::CLIError, "Only one of '--boltdir', '--project', or '--configfile' may be specified"
300
316
  end
301
317
 
@@ -342,15 +358,10 @@ module Bolt
342
358
  def warn_inventory_overrides_cli(opts)
343
359
  inventory_source = if ENV[Bolt::Inventory::ENVIRONMENT_VAR]
344
360
  Bolt::Inventory::ENVIRONMENT_VAR
345
- elsif config.inventoryfile && Bolt::Util.file_stat(config.inventoryfile)
361
+ elsif config.inventoryfile
346
362
  config.inventoryfile
347
- else
348
- begin
349
- Bolt::Util.file_stat(config.default_inventoryfile)
350
- config.default_inventoryfile
351
- rescue Errno::ENOENT
352
- nil
353
- end
363
+ elsif File.exist?(config.default_inventoryfile)
364
+ config.default_inventoryfile
354
365
  end
355
366
 
356
367
  inventory_cli_opts = %i[authentication escalation transports].each_with_object([]) do |key, acc|
@@ -394,7 +405,7 @@ module Bolt
394
405
  output_format: config.format,
395
406
  # For continuity
396
407
  boltdir_type: config.project.type
397
- }
408
+ }.merge!(analytics.plan_counts(config.project.plans_path))
398
409
 
399
410
  # Only include target and inventory info for commands that take a targets
400
411
  # list. This avoids loading inventory for commands that don't need it.
@@ -458,14 +469,22 @@ module Bolt
458
469
  when 'project'
459
470
  case options[:action]
460
471
  when 'init'
461
- code = initialize_project
472
+ code = Bolt::ProjectManager.new(config, outputter, pal)
473
+ .create(Dir.pwd, options[:object], options[:modules])
462
474
  when 'migrate'
463
- code = Bolt::ProjectMigrator.new(config, outputter).migrate
475
+ code = Bolt::ProjectManager.new(config, outputter, pal).migrate
464
476
  end
465
477
  when 'plan'
466
478
  case options[:action]
467
479
  when 'new'
468
- code = new_plan(options[:object])
480
+ plan_name = options[:object]
481
+
482
+ # If this passes validation, it will return the path to the plan to create
483
+ Bolt::PlanCreator.validate_input(config.project, plan_name)
484
+ code = Bolt::PlanCreator.create_plan(config.project.plans_path,
485
+ plan_name,
486
+ outputter,
487
+ options[:puppet])
469
488
  when 'run'
470
489
  code = run_plan(options[:object], options[:task_options], options[:target_args], options)
471
490
  end
@@ -568,10 +587,15 @@ module Bolt
568
587
  outputter.print_task_info(pal.get_task(task_name))
569
588
  end
570
589
 
590
+ # Filters a list of content by matching substring.
591
+ #
592
+ private def filter_content(content, filter)
593
+ return content unless content && filter
594
+ content.select { |name,| name.include?(filter) }
595
+ end
596
+
571
597
  def list_tasks
572
- tasks = pal.list_tasks
573
- tasks.select! { |task| task.first.include?(options[:filter]) } if options[:filter]
574
- tasks.select! { |task| config.project.tasks.include?(task.first) } unless config.project.tasks.nil?
598
+ tasks = filter_content(pal.list_tasks(filter_content: true), options[:filter])
575
599
  outputter.print_tasks(tasks, pal.user_modulepath)
576
600
  end
577
601
 
@@ -580,9 +604,7 @@ module Bolt
580
604
  end
581
605
 
582
606
  def list_plans
583
- plans = pal.list_plans
584
- plans.select! { |plan| plan.first.include?(options[:filter]) } if options[:filter]
585
- plans.select! { |plan| config.project.plans.include?(plan.first) } unless config.project.plans.nil?
607
+ plans = filter_content(pal.list_plans(filter_content: true), options[:filter])
586
608
  outputter.print_plans(plans, pal.user_modulepath)
587
609
  end
588
610
 
@@ -617,118 +639,6 @@ module Bolt
617
639
  outputter.print_groups(groups)
618
640
  end
619
641
 
620
- def new_plan(plan_name)
621
- @logger.warn("Command 'bolt plan new' is experimental and subject to changes.")
622
-
623
- if config.project.name.nil?
624
- raise Bolt::Error.new(
625
- "Project directory '#{config.project.path}' is not a named project. Unable to create "\
626
- "a project-level plan. To name a project, set the 'name' key in the 'bolt-project.yaml' "\
627
- "configuration file.",
628
- "bolt/unnamed-project-error"
629
- )
630
- end
631
-
632
- if plan_name !~ Bolt::Module::CONTENT_NAME_REGEX
633
- message = <<~MESSAGE.chomp
634
- Invalid plan name '#{plan_name}'. Plan names are composed of one or more name segments
635
- separated by double colons '::'.
636
-
637
- Each name segment must begin with a lowercase letter, and may only include lowercase
638
- letters, digits, and underscores.
639
-
640
- Examples of valid plan names:
641
- - #{config.project.name}
642
- - #{config.project.name}::my_plan
643
- MESSAGE
644
-
645
- raise Bolt::ValidationError, message
646
- end
647
-
648
- prefix, *name_segments, basename = plan_name.split('::')
649
-
650
- # If the plan name is just the project name, then create an 'init' plan.
651
- # Otherwise, use the last name segment for the plan's filename.
652
- basename ||= 'init'
653
-
654
- unless prefix == config.project.name
655
- message = "First segment of plan name '#{plan_name}' must match project name '#{config.project.name}'. "\
656
- "Did you mean '#{config.project.name}::#{plan_name}'?"
657
-
658
- raise Bolt::ValidationError, message
659
- end
660
-
661
- dir_path = config.project.plans_path.join(*name_segments)
662
-
663
- %w[pp yaml].each do |ext|
664
- next unless (path = config.project.plans_path + "#{basename}.#{ext}").exist?
665
- raise Bolt::Error.new(
666
- "A plan with the name '#{plan_name}' already exists at '#{path}', nothing to do.",
667
- 'bolt/existing-plan-error'
668
- )
669
- end
670
-
671
- begin
672
- FileUtils.mkdir_p(dir_path)
673
- rescue Errno::EEXIST => e
674
- raise Bolt::Error.new(
675
- "#{e.message}; unable to create plan directory '#{dir_path}'",
676
- 'bolt/existing-file-error'
677
- )
678
- end
679
-
680
- plan_path = dir_path + "#{basename}.yaml"
681
-
682
- plan_template = <<~PLAN
683
- # This is the structure of a simple plan. To learn more about writing
684
- # YAML plans, see the documentation: http://pup.pt/bolt-yaml-plans
685
-
686
- # The description sets the description of the plan that will appear
687
- # in 'bolt plan show' output.
688
- description: A plan created with bolt plan new
689
-
690
- # The parameters key defines the parameters that can be passed to
691
- # the plan.
692
- parameters:
693
- targets:
694
- type: TargetSpec
695
- description: A list of targets to run actions on
696
- default: localhost
697
-
698
- # The steps key defines the actions the plan will take in order.
699
- steps:
700
- - message: Hello from #{plan_name}
701
- - name: command_step
702
- command: whoami
703
- targets: $targets
704
-
705
- # The return key sets the return value of the plan.
706
- return: $command_step
707
- PLAN
708
-
709
- begin
710
- File.write(plan_path, plan_template)
711
- rescue Errno::EACCES => e
712
- raise Bolt::FileError.new(
713
- "#{e.message}; unable to create plan",
714
- plan_path
715
- )
716
- end
717
-
718
- output = <<~OUTPUT
719
- Created plan '#{plan_name}' at '#{plan_path}'
720
-
721
- Show this plan with:
722
- bolt plan show #{plan_name}
723
- Run this plan with:
724
- bolt plan run #{plan_name}
725
- OUTPUT
726
-
727
- outputter.print_message(output)
728
-
729
- 0
730
- end
731
-
732
642
  def run_plan(plan_name, plan_arguments, nodes, options)
733
643
  unless nodes.empty?
734
644
  if plan_arguments['nodes'] || plan_arguments['targets']
@@ -820,88 +730,22 @@ module Bolt
820
730
  end
821
731
 
822
732
  def list_modules
733
+ assert_puppetfile_or_module_command(config.project.modules)
823
734
  outputter.print_module_list(pal.list_modules)
824
735
  end
825
736
 
826
737
  def generate_types
738
+ assert_puppetfile_or_module_command(config.project.modules)
827
739
  # generate_types will surface a nice error with helpful message if it fails
828
740
  pal.generate_types
829
741
  0
830
742
  end
831
743
 
832
- # Initializes a specified directory as a Bolt project and installs any modules
833
- # specified by the user, along with their dependencies
834
- def initialize_project
835
- # Dir.pwd will return backslashes on Windows, but Pathname always uses
836
- # forward slashes to concatenate paths. This results in paths like
837
- # C:\User\Administrator/modules, which fail module install. This ensure
838
- # forward slashes in the cwd path.
839
- dir = File.expand_path(Dir.pwd)
840
- name = options[:object] || File.basename(dir)
841
- if name !~ Bolt::Module::MODULE_NAME_REGEX
842
- if options[:object]
843
- raise Bolt::ValidationError, "The provided project name '#{name}' is invalid; "\
844
- "project name must begin with a lowercase letter and can include lowercase "\
845
- "letters, numbers, and underscores."
846
- else
847
- raise Bolt::ValidationError, "The current directory name '#{name}' is an invalid "\
848
- "project name. Please specify a name using 'bolt project init <name>'."
849
- end
850
- end
851
-
852
- project = Pathname.new(dir)
853
- old_config = project + 'bolt.yaml'
854
- config = project + 'bolt-project.yaml'
855
- puppetfile = project + 'Puppetfile'
856
- moduledir = project + 'modules'
857
-
858
- # Warn the user if the project directory already exists. We don't error
859
- # here since users might not have installed any modules yet. If both
860
- # bolt.yaml and bolt-project.yaml exist, this will just warn about
861
- # bolt-project.yaml and subsequent Bolt actions will warn about both files
862
- # existing.
863
- if config.exist?
864
- @logger.warn "Found existing project directory at #{project}. Skipping file creation."
865
- elsif old_config.exist?
866
- @logger.warn "Found existing #{old_config.basename} at #{project}. "\
867
- "#{old_config.basename} is deprecated, please rename to #{config.basename}."
868
- end
869
-
870
- # If modules were specified, first check if there is already a Puppetfile
871
- # at the project directory, erroring if there is. If there is no
872
- # Puppetfile, install the specified modules. The module installer will
873
- # resolve dependencies, generate a Puppetfile, and install the modules.
874
- if options[:modules]
875
- if puppetfile.exist?
876
- raise Bolt::CLIError,
877
- "Found existing Puppetfile at #{puppetfile}, unable to initialize "\
878
- "project with modules."
879
- end
880
-
881
- installer = Bolt::ModuleInstaller.new(outputter, pal)
882
- installer.install(options[:modules], puppetfile, moduledir)
883
- end
884
-
885
- # If either bolt.yaml or bolt-project.yaml exist, the user has already
886
- # been warned and we can just finish project creation. Otherwise, create a
887
- # bolt-project.yaml with the project name in it.
888
- unless config.exist? || old_config.exist?
889
- begin
890
- content = { 'name' => name }
891
- File.write(config.to_path, content.to_yaml)
892
- outputter.print_message "Successfully created Bolt project at #{project}"
893
- rescue StandardError => e
894
- raise Bolt::FileError.new("Could not create bolt-project.yaml at #{project}: #{e.message}", nil)
895
- end
896
- end
897
-
898
- 0
899
- end
900
-
901
744
  # Installs modules declared in the project configuration file.
902
745
  #
903
746
  def install_project_modules(project, force, resolve)
904
747
  assert_project_file(project)
748
+ assert_puppetfile_or_module_command(project.modules)
905
749
 
906
750
  unless project.modules
907
751
  outputter.print_message "Project configuration file #{project.project_file} does not "\
@@ -923,6 +767,7 @@ module Bolt
923
767
  #
924
768
  def add_project_module(name, project)
925
769
  assert_project_file(project)
770
+ assert_puppetfile_or_module_command(project.modules)
926
771
 
927
772
  modules = project.modules || []
928
773
  installer = Bolt::ModuleInstaller.new(outputter, pal)
@@ -940,11 +785,13 @@ module Bolt
940
785
  def assert_project_file(project)
941
786
  unless project.project_file?
942
787
  msg = if project.config_file.exist?
788
+ command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
943
789
  "Detected Bolt configuration file #{project.config_file}, unable to install "\
944
- "modules. To update to a project configuration file, run 'bolt project migrate'."
790
+ "modules. To update to a project configuration file, run '#{command}'."
945
791
  else
792
+ command = Bolt::Util.powershell? ? 'New-BoltProject' : 'bolt project init'
946
793
  "Could not find project configuration file #{project.project_file}, unable "\
947
- "to install modules. To create a Bolt project, run 'bolt project init'."
794
+ "to install modules. To create a Bolt project, run '#{command}'."
948
795
  end
949
796
 
950
797
  raise Bolt::Error.new(msg, 'bolt/missing-project-config-error')
@@ -953,10 +800,12 @@ module Bolt
953
800
 
954
801
  # Loads a Puppetfile and installs its modules.
955
802
  #
956
- def install_puppetfile(config, puppetfile, moduledir)
803
+ def install_puppetfile(puppetfile_config, puppetfile, moduledir)
804
+ assert_puppetfile_or_module_command(config.project.modules)
805
+
957
806
  outputter.print_message("Installing modules from Puppetfile")
958
807
  installer = Bolt::ModuleInstaller.new(outputter, pal)
959
- ok = installer.install_puppetfile(puppetfile, moduledir, config)
808
+ ok = installer.install_puppetfile(puppetfile, moduledir, puppetfile_config)
960
809
  ok ? 0 : 1
961
810
  end
962
811
 
@@ -964,17 +813,36 @@ module Bolt
964
813
  # modules being configured.
965
814
  #
966
815
  def assert_puppetfile_or_module_command(modules)
816
+ if Bolt::Util.powershell?
817
+ case options[:action]
818
+ when 'generate-types'
819
+ old_command = 'Register-BoltPuppetfileTypes'
820
+ new_command = 'Register-BoltModuleTypes'
821
+ when 'install'
822
+ old_command = 'Install-BoltPuppetfile'
823
+ new_command = 'Install-BoltModule'
824
+ when 'show', 'show-modules'
825
+ old_command = 'Get-BoltPuppetfileModules'
826
+ new_command = 'Get-BoltModule'
827
+ end
828
+ else
829
+ old_command = "bolt puppetfile #{options[:action]}"
830
+ new_command = if options[:action] == 'show-modules'
831
+ 'bolt module show'
832
+ else
833
+ "bolt module #{options[:action]}"
834
+ end
835
+ end
836
+
967
837
  if modules && options[:subcommand] == 'puppetfile'
968
838
  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'."
839
+ "Unable to use command '#{old_command}' when 'modules' is configured in "\
840
+ "bolt-project.yaml. Use '#{new_command}' instead."
973
841
  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."
842
+ msg = "Unable to use command '#{new_command}' when 'modules' is not configured in "\
843
+ "bolt-project.yaml. "
844
+ msg += "Use '#{old_command}' instead." if options[:action] != 'add'
845
+ raise Bolt::CLIError, msg
978
846
  end
979
847
  end
980
848