bolt 2.7.0 → 2.11.1

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 +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_task_with.rb +192 -0
  9. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +122 -0
  10. data/bolt-modules/boltlib/types/planresult.pp +12 -1
  11. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +3 -1
  12. data/bolt-modules/file/lib/puppet/functions/file/join.rb +1 -1
  13. data/bolt-modules/file/lib/puppet/functions/file/read.rb +2 -1
  14. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
  15. data/bolt-modules/file/lib/puppet/functions/file/write.rb +3 -1
  16. data/lib/bolt/applicator.rb +3 -2
  17. data/lib/bolt/apply_inventory.rb +1 -1
  18. data/lib/bolt/apply_result.rb +1 -1
  19. data/lib/bolt/apply_target.rb +11 -2
  20. data/lib/bolt/bolt_option_parser.rb +22 -6
  21. data/lib/bolt/cli.rb +52 -22
  22. data/lib/bolt/config.rb +57 -27
  23. data/lib/bolt/config/transport/base.rb +3 -3
  24. data/lib/bolt/config/transport/docker.rb +2 -0
  25. data/lib/bolt/config/transport/local.rb +2 -0
  26. data/lib/bolt/config/transport/orch.rb +4 -2
  27. data/lib/bolt/config/transport/remote.rb +2 -0
  28. data/lib/bolt/config/transport/ssh.rb +51 -2
  29. data/lib/bolt/config/transport/winrm.rb +3 -1
  30. data/lib/bolt/executor.rb +16 -0
  31. data/lib/bolt/inventory.rb +2 -1
  32. data/lib/bolt/inventory/group.rb +1 -0
  33. data/lib/bolt/inventory/inventory.rb +5 -0
  34. data/lib/bolt/inventory/target.rb +17 -1
  35. data/lib/bolt/node/output.rb +1 -1
  36. data/lib/bolt/outputter/human.rb +5 -4
  37. data/lib/bolt/outputter/json.rb +1 -1
  38. data/lib/bolt/pal.rb +32 -14
  39. data/lib/bolt/pal/yaml_plan.rb +1 -0
  40. data/lib/bolt/plugin.rb +14 -8
  41. data/lib/bolt/plugin/module.rb +40 -7
  42. data/lib/bolt/plugin/puppetdb.rb +5 -2
  43. data/lib/bolt/project.rb +135 -0
  44. data/lib/bolt/puppetdb/config.rb +16 -28
  45. data/lib/bolt/rerun.rb +1 -1
  46. data/lib/bolt/resource_instance.rb +126 -0
  47. data/lib/bolt/result.rb +46 -23
  48. data/lib/bolt/result_set.rb +2 -5
  49. data/lib/bolt/secret.rb +20 -4
  50. data/lib/bolt/shell/bash.rb +12 -5
  51. data/lib/bolt/shell/powershell.rb +12 -4
  52. data/lib/bolt/target.rb +16 -1
  53. data/lib/bolt/transport/base.rb +24 -8
  54. data/lib/bolt/transport/orch.rb +4 -0
  55. data/lib/bolt/transport/ssh.rb +6 -2
  56. data/lib/bolt/transport/ssh/connection.rb +4 -0
  57. data/lib/bolt/transport/ssh/exec_connection.rb +110 -0
  58. data/lib/bolt/transport/winrm/connection.rb +6 -2
  59. data/lib/bolt/version.rb +1 -1
  60. data/lib/bolt_server/pe/pal.rb +1 -38
  61. data/lib/bolt_server/transport_app.rb +7 -7
  62. data/lib/bolt_spec/bolt_context.rb +3 -6
  63. data/lib/bolt_spec/plans.rb +1 -1
  64. data/lib/bolt_spec/plans/mock_executor.rb +1 -0
  65. data/lib/bolt_spec/run.rb +10 -13
  66. metadata +10 -7
  67. data/lib/bolt/boltdir.rb +0 -54
  68. data/lib/bolt/plugin/pkcs7.rb +0 -104
  69. 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.
@@ -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
@@ -11,7 +11,7 @@ module Bolt
11
11
  escalation: %w[run-as sudo-password sudo-password-prompt sudo-executable],
12
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,7 +689,7 @@ 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,
@@ -716,6 +717,10 @@ module Bolt
716
717
  'Directory containing bolt.yaml will be used as the Boltdir.') do |path|
717
718
  @options[:configfile] = path
718
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
719
724
  define('-i', '--inventoryfile FILEPATH',
720
725
  'Specify where to load inventory from (default: ~/.puppetlabs/bolt/inventory.yaml)') do |path|
721
726
  if ENV.include?(Bolt::Inventory::ENVIRONMENT_VAR)
@@ -737,6 +742,14 @@ module Bolt
737
742
  "Specify a default transport: #{TRANSPORTS.keys.join(', ')}") do |t|
738
743
  @options[:transport] = t
739
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
740
753
  define('--connect-timeout TIMEOUT', Integer, 'Connection timeout (defaults vary)') do |timeout|
741
754
  @options[:'connect-timeout'] = timeout
742
755
  end
@@ -775,6 +788,9 @@ module Bolt
775
788
  'when initializing a project. Resolves and installs all dependencies.') do |modules|
776
789
  @options[:modules] = modules.split(',')
777
790
  end
791
+ define('--force', 'Overwrite existing key pairs') do |_force|
792
+ @options[:force] = true
793
+ end
778
794
 
779
795
  separator "\nGLOBAL OPTIONS"
780
796
  define('-h', '--help', 'Display help') do |_|
@@ -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
@@ -3,7 +3,7 @@
3
3
  require 'etc'
4
4
  require 'logging'
5
5
  require 'pathname'
6
- require 'bolt/boltdir'
6
+ require 'bolt/project'
7
7
  require 'bolt/logger'
8
8
  require 'bolt/util'
9
9
  # Transport config objects
@@ -23,7 +23,7 @@ module Bolt
23
23
  end
24
24
 
25
25
  class Config
26
- attr_reader :config_files, :warnings, :data, :transports, :boltdir
26
+ attr_reader :config_files, :warnings, :data, :transports, :project
27
27
 
28
28
  TRANSPORT_CONFIG = {
29
29
  'ssh' => Bolt::Config::Transport::SSH,
@@ -34,6 +34,8 @@ module Bolt
34
34
  'remote' => Bolt::Config::Transport::Remote
35
35
  }.freeze
36
36
 
37
+ # NOTE: All configuration options should have a corresponding schema property
38
+ # in schemas/bolt-config.schema.json
37
39
  OPTIONS = {
38
40
  "apply_settings" => "A map of Puppet settings to use when applying Puppet code",
39
41
  "color" => "Whether to use colored output when printing messages to the console.",
@@ -64,8 +66,8 @@ module Bolt
64
66
 
65
67
  DEFAULT_OPTIONS = {
66
68
  "color" => true,
67
- "concurrency" => 100,
68
69
  "compile-concurrency" => "Number of cores",
70
+ "concurrency" => "100 or one-third of the ulimit, whichever is lower",
69
71
  "format" => "human",
70
72
  "hiera-config" => "Boltdir/hiera.yaml",
71
73
  "inventoryfile" => "Boltdir/inventory.yaml",
@@ -101,31 +103,33 @@ module Bolt
101
103
  "show_diff" => false
102
104
  }.freeze
103
105
 
106
+ DEFAULT_DEFAULT_CONCURRENCY = 100
107
+
104
108
  def self.default
105
- new(Bolt::Boltdir.new('.'), {})
109
+ new(Bolt::Project.new('.'), {})
106
110
  end
107
111
 
108
- def self.from_boltdir(boltdir, overrides = {})
112
+ def self.from_project(project, overrides = {})
109
113
  data = {
110
- filepath: boltdir.config_file,
111
- data: Bolt::Util.read_optional_yaml_hash(boltdir.config_file, 'config')
114
+ filepath: project.config_file,
115
+ data: Bolt::Util.read_optional_yaml_hash(project.config_file, 'config')
112
116
  }
113
117
 
114
118
  data = load_defaults.push(data).select { |config| config[:data]&.any? }
115
119
 
116
- new(boltdir, data, overrides)
120
+ new(project, data, overrides)
117
121
  end
118
122
 
119
123
  def self.from_file(configfile, overrides = {})
120
- boltdir = Bolt::Boltdir.new(Pathname.new(configfile).expand_path.dirname)
124
+ project = Bolt::Project.new(Pathname.new(configfile).expand_path.dirname)
121
125
 
122
126
  data = {
123
- filepath: boltdir.config_file,
127
+ filepath: project.config_file,
124
128
  data: Bolt::Util.read_yaml_hash(configfile, 'config')
125
129
  }
126
130
  data = load_defaults.push(data).select { |config| config[:data]&.any? }
127
131
 
128
- new(boltdir, data, overrides)
132
+ new(project, data, overrides)
129
133
  end
130
134
 
131
135
  def self.load_defaults
@@ -148,14 +152,14 @@ module Bolt
148
152
  confs
149
153
  end
150
154
 
151
- def initialize(boltdir, config_data, overrides = {})
155
+ def initialize(project, config_data, overrides = {})
152
156
  unless config_data.is_a?(Array)
153
- config_data = [{ filepath: boltdir.config_file, data: config_data }]
157
+ config_data = [{ filepath: project.config_file, data: config_data }]
154
158
  end
155
159
 
156
160
  @logger = Logging.logger[self]
157
161
  @warnings = []
158
- @boltdir = boltdir
162
+ @project = project
159
163
  @transports = {}
160
164
  @config_files = []
161
165
 
@@ -163,7 +167,7 @@ module Bolt
163
167
  'apply_settings' => {},
164
168
  'color' => true,
165
169
  'compile-concurrency' => Etc.nprocessors,
166
- 'concurrency' => 100,
170
+ 'concurrency' => default_concurrency,
167
171
  'format' => 'human',
168
172
  'log' => { 'console' => {} },
169
173
  'plugin_hooks' => {},
@@ -181,10 +185,22 @@ module Bolt
181
185
 
182
186
  override_data = normalize_overrides(overrides)
183
187
 
188
+ # If we need to lower concurrency and concurrency is not configured
189
+ ld_concurrency = loaded_data.map(&:keys).flatten.include?('concurrency')
190
+ if default_concurrency != DEFAULT_DEFAULT_CONCURRENCY &&
191
+ !ld_concurrency &&
192
+ !override_data.key?('concurrency')
193
+ concurrency_warning = { option: 'concurrency',
194
+ msg: "Concurrency will default to #{default_concurrency} because ulimit "\
195
+ "is low: #{Etc.sysconf(Etc::SC_OPEN_MAX)}. Set concurrency with "\
196
+ "'--concurrency', or set your ulimit with 'ulimit -n <limit>'" }
197
+ @warnings << concurrency_warning
198
+ end
199
+
184
200
  @data = merge_config_layers(default_data, *loaded_data, override_data)
185
201
 
186
202
  TRANSPORT_CONFIG.each do |transport, config|
187
- @transports[transport] = config.new(@data.delete(transport), @boltdir.path)
203
+ @transports[transport] = config.new(@data.delete(transport), @project.path)
188
204
  end
189
205
 
190
206
  finalize_data
@@ -250,7 +266,7 @@ module Bolt
250
266
  @data['log'] = update_logs(@data['log'])
251
267
  end
252
268
 
253
- # Expand paths relative to the Boltdir. Any settings that came from the
269
+ # Expand paths relative to the project. Any settings that came from the
254
270
  # CLI will already be absolute, so the expand will be skipped.
255
271
  if @data.key?('modulepath')
256
272
  moduledirs = if data['modulepath'].is_a?(String)
@@ -259,12 +275,12 @@ module Bolt
259
275
  data['modulepath']
260
276
  end
261
277
  @data['modulepath'] = moduledirs.map do |moduledir|
262
- File.expand_path(moduledir, @boltdir.path)
278
+ File.expand_path(moduledir, @project.path)
263
279
  end
264
280
  end
265
281
 
266
282
  %w[hiera-config inventoryfile trusted-external-command].each do |opt|
267
- @data[opt] = File.expand_path(@data[opt], @boltdir.path) if @data.key?(opt)
283
+ @data[opt] = File.expand_path(@data[opt], @project.path) if @data.key?(opt)
268
284
  end
269
285
 
270
286
  # Filter hashes to only include valid options
@@ -275,7 +291,7 @@ module Bolt
275
291
  private def normalize_log(target)
276
292
  return target if target == 'console'
277
293
  target = target[5..-1] if target.start_with?('file:')
278
- 'file:' + File.expand_path(target, @boltdir.path)
294
+ 'file:' + File.expand_path(target, @project.path)
279
295
  end
280
296
 
281
297
  private def update_logs(logs)
@@ -310,10 +326,10 @@ module Bolt
310
326
  @warnings << { option: 'future', msg: msg }
311
327
  end
312
328
 
313
- keys = OPTIONS.keys - %w[plugins plugin_hooks]
329
+ keys = OPTIONS.keys - %w[plugins plugin_hooks puppetdb]
314
330
  keys.each do |key|
315
331
  next unless Bolt::Util.references?(@data[key])
316
- valid_keys = TRANSPORT_CONFIG.keys + %w[plugins plugin_hooks]
332
+ valid_keys = TRANSPORT_CONFIG.keys + %w[plugins plugin_hooks puppetdb]
317
333
  raise Bolt::ValidationError,
318
334
  "Found unsupported key _plugin in config setting #{key}. Plugins are only available in "\
319
335
  "#{valid_keys.join(', ')}."
@@ -348,23 +364,23 @@ module Bolt
348
364
  end
349
365
 
350
366
  def default_inventoryfile
351
- @boltdir.inventory_file
367
+ @project.inventory_file
352
368
  end
353
369
 
354
370
  def rerunfile
355
- @boltdir.rerunfile
371
+ @project.rerunfile
356
372
  end
357
373
 
358
374
  def hiera_config
359
- @data['hiera-config'] || @boltdir.hiera_config
375
+ @data['hiera-config'] || @project.hiera_config
360
376
  end
361
377
 
362
378
  def puppetfile
363
- @puppetfile || @boltdir.puppetfile
379
+ @puppetfile || @project.puppetfile
364
380
  end
365
381
 
366
382
  def modulepath
367
- @data['modulepath'] || @boltdir.modulepath
383
+ @data['modulepath'] || @project.modulepath
368
384
  end
369
385
 
370
386
  def modulepath=(value)
@@ -456,5 +472,19 @@ module Bolt
456
472
  l =~ /[A-Za-z]/ ? "[#{l.upcase}#{l.downcase}]" : l
457
473
  end.join
458
474
  end
475
+
476
+ # Etc::SC_OPEN_MAX is meaningless on windows, not defined in PE Jruby and not available
477
+ # on some platforms. This method holds the logic to decide whether or not to even consider it.
478
+ def sc_open_max_available?
479
+ !Bolt::Util.windows? && defined?(Etc::SC_OPEN_MAX) && Etc.sysconf(Etc::SC_OPEN_MAX)
480
+ end
481
+
482
+ def default_concurrency
483
+ @default_concurrency ||= if !sc_open_max_available? || Etc.sysconf(Etc::SC_OPEN_MAX) >= 300
484
+ DEFAULT_DEFAULT_CONCURRENCY
485
+ else
486
+ Etc.sysconf(Etc::SC_OPEN_MAX) / 3
487
+ end
488
+ end
459
489
  end
460
490
  end