bolt 2.10.0 → 2.14.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +2 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +3 -2
  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 +2 -2
  8. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -1
  10. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +52 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +65 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +4 -2
  13. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +8 -2
  14. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +65 -43
  15. data/lib/bolt/analytics.rb +21 -2
  16. data/lib/bolt/applicator.rb +8 -2
  17. data/lib/bolt/apply_inventory.rb +4 -0
  18. data/lib/bolt/apply_result.rb +1 -1
  19. data/lib/bolt/apply_target.rb +4 -0
  20. data/lib/bolt/bolt_option_parser.rb +5 -4
  21. data/lib/bolt/catalog.rb +81 -68
  22. data/lib/bolt/cli.rb +16 -5
  23. data/lib/bolt/config.rb +62 -29
  24. data/lib/bolt/config/transport/ssh.rb +130 -91
  25. data/lib/bolt/executor.rb +14 -1
  26. data/lib/bolt/inventory/group.rb +1 -1
  27. data/lib/bolt/inventory/inventory.rb +4 -0
  28. data/lib/bolt/inventory/target.rb +4 -0
  29. data/lib/bolt/pal.rb +3 -0
  30. data/lib/bolt/project.rb +55 -11
  31. data/lib/bolt/resource_instance.rb +10 -3
  32. data/lib/bolt/shell/bash.rb +1 -1
  33. data/lib/bolt/shell/powershell/snippets.rb +8 -0
  34. data/lib/bolt/transport/local/connection.rb +2 -1
  35. data/lib/bolt/transport/ssh/connection.rb +39 -0
  36. data/lib/bolt/transport/winrm/connection.rb +4 -0
  37. data/lib/bolt/version.rb +1 -1
  38. data/lib/bolt_spec/bolt_context.rb +1 -1
  39. data/lib/bolt_spec/run.rb +1 -1
  40. metadata +6 -5
@@ -14,14 +14,16 @@ require 'open3'
14
14
 
15
15
  module Bolt
16
16
  class Applicator
17
- def initialize(inventory, executor, modulepath, plugin_dirs, pdb_client, hiera_config, max_compiles, apply_settings)
17
+ def initialize(inventory, executor, modulepath, plugin_dirs, project,
18
+ pdb_client, hiera_config, max_compiles, apply_settings)
18
19
  # lazy-load expensive gem code
19
20
  require 'concurrent'
20
21
 
21
22
  @inventory = inventory
22
23
  @executor = executor
23
- @modulepath = modulepath
24
+ @modulepath = modulepath || []
24
25
  @plugin_dirs = plugin_dirs
26
+ @project = project
25
27
  @pdb_client = pdb_client
26
28
  @hiera_config = hiera_config ? validate_hiera_config(hiera_config) : nil
27
29
  @apply_settings = apply_settings || {}
@@ -34,6 +36,8 @@ module Bolt
34
36
  search_dirs << mod.plugins if mod.plugins?
35
37
  search_dirs << mod.pluginfacts if mod.pluginfacts?
36
38
  search_dirs << mod.files if mod.files?
39
+ type_files = "#{mod.path}/types"
40
+ search_dirs << type_files if File.exist?(type_files)
37
41
  search_dirs
38
42
  end
39
43
  end
@@ -142,6 +146,7 @@ module Bolt
142
146
 
143
147
  def apply(args, apply_body, scope)
144
148
  raise(ArgumentError, 'apply requires a TargetSpec') if args.empty?
149
+ raise(ArgumentError, 'apply requires at least one statement in the apply block') if apply_body.nil?
145
150
  type0 = Puppet.lookup(:pal_script_compiler).type('TargetSpec')
146
151
  Puppet::Pal.assert_type(type0, args[0], 'apply targets')
147
152
 
@@ -186,6 +191,7 @@ module Bolt
186
191
  scope = {
187
192
  code_ast: ast,
188
193
  modulepath: @modulepath,
194
+ project: @project.to_h,
189
195
  pdb_config: @pdb_client.config.to_hash,
190
196
  hiera_config: @hiera_config,
191
197
  plan_vars: plan_vars,
@@ -70,6 +70,10 @@ module Bolt
70
70
  @targets[target.name].features
71
71
  end
72
72
 
73
+ def resource(target, type, title)
74
+ @targets[target.name].resource(type, title)
75
+ end
76
+
73
77
  def add_to_group(*_params)
74
78
  raise InvalidFunctionCall, 'add_to_group'
75
79
  end
@@ -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,
@@ -62,6 +62,10 @@ module Bolt
62
62
  @safe_name
63
63
  end
64
64
 
65
+ def resource(type, title)
66
+ resources[Bolt::ResourceInstance.format_reference(type, title)]
67
+ end
68
+
65
69
  def parse_uri(string)
66
70
  require 'addressable/uri'
67
71
  if string.nil?
@@ -20,7 +20,7 @@ module Bolt
20
20
  def get_help_text(subcommand, action = nil)
21
21
  case subcommand
22
22
  when 'apply'
23
- { flags: ACTION_OPTS + %w[noop execute compile-concurrency],
23
+ { flags: ACTION_OPTS + %w[noop execute compile-concurrency hiera-config],
24
24
  banner: APPLY_HELP }
25
25
  when 'command'
26
26
  case action
@@ -172,13 +172,14 @@ module Bolt
172
172
  apply
173
173
 
174
174
  USAGE
175
- bolt apply <manifest.pp> [options]
175
+ bolt apply [manifest.pp] [options]
176
176
 
177
177
  DESCRIPTION
178
178
  Apply Puppet manifest code on the specified targets.
179
179
 
180
180
  EXAMPLES
181
- bolt apply manifest.pp --targets target1,target2
181
+ bolt apply manifest.pp -t target
182
+ bolt apply -e "file { '/etc/puppetlabs': ensure => present }" -t target
182
183
  HELP
183
184
 
184
185
  COMMAND_HELP = <<~HELP
@@ -689,7 +690,7 @@ module Bolt
689
690
 
690
691
  separator "\nRUN CONTEXT OPTIONS"
691
692
  define('-c', '--concurrency CONCURRENCY', Integer,
692
- 'Maximum number of simultaneous connections (default: 100)') do |concurrency|
693
+ 'Maximum number of simultaneous connections') do |concurrency|
693
694
  @options[:concurrency] = concurrency
694
695
  end
695
696
  define('--compile-concurrency CONCURRENCY', Integer,
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bolt/apply_inventory'
3
4
  require 'bolt/apply_target'
4
5
  require 'bolt/config'
5
6
  require 'bolt/error'
6
7
  require 'bolt/inventory'
7
- require 'bolt/apply_inventory'
8
8
  require 'bolt/pal'
9
9
  require 'bolt/puppetdb'
10
10
  require 'bolt/util'
@@ -19,7 +19,7 @@ module Bolt
19
19
  @log_level = log_level
20
20
  end
21
21
 
22
- def with_puppet_settings(hiera_config = {})
22
+ def with_puppet_settings(overrides = {})
23
23
  Dir.mktmpdir('bolt') do |dir|
24
24
  cli = []
25
25
  Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
@@ -31,7 +31,9 @@ module Bolt
31
31
  Puppet.settings.override_default(:vendormoduledir, '')
32
32
 
33
33
  Puppet.initialize_settings(cli)
34
- Puppet.settings[:hiera_config] = hiera_config
34
+ overrides.each do |setting, value|
35
+ Puppet.settings[setting] = value
36
+ end
35
37
 
36
38
  # Use a special logdest that serializes all log messages and their level to stderr.
37
39
  Puppet::Util::Log.newdestination(:stderr)
@@ -54,80 +56,51 @@ module Bolt
54
56
  end
55
57
 
56
58
  def compile_catalog(request)
57
- pal_main = request['code_ast'] || request['code_string']
58
- target = request['target']
59
59
  pdb_client = Bolt::PuppetDB::Client.new(Bolt::PuppetDB::Config.new(request['pdb_config']))
60
- options = request['puppet_config'] || {}
61
- with_puppet_settings(request['hiera_config']) do
62
- Puppet[:rich_data] = true
63
- Puppet[:node_name_value] = target['name']
64
- env_conf = { modulepath: request['modulepath'] || [],
65
- facts: target['facts'] || {} }
66
- env_conf[:variables] = {}
60
+ project = request['project'] || {}
61
+ bolt_project = Struct.new(:name, :path).new(project['name'], project['path']) unless project.empty?
62
+ inv = Bolt::ApplyInventory.new(request['config'])
63
+ puppet_overrides = {
64
+ bolt_pdb_client: pdb_client,
65
+ bolt_inventory: inv,
66
+ bolt_project: bolt_project
67
+ }
68
+
69
+ # Facts will be set by the catalog compiler, so we need to ensure
70
+ # that any plan or target variables with the same name are not
71
+ # passed into the apply block to avoid a redefinition error.
72
+ # Filter out plan and target vars separately and raise a Puppet
73
+ # warning if there are any collisions for either. Puppet warning
74
+ # is the only way to log a message that will make it back to Bolt
75
+ # to be printed.
76
+ target = request['target']
77
+ plan_vars = shadow_vars('plan', request['plan_vars'], target['facts'])
78
+ target_vars = shadow_vars('target', target['variables'], target['facts'])
79
+ topscope_vars = target_vars.merge(plan_vars)
80
+ env_conf = { modulepath: request['modulepath'],
81
+ facts: target['facts'],
82
+ variables: topscope_vars }
83
+
84
+ puppet_settings = {
85
+ node_name_value: target['name'],
86
+ hiera_config: request['hiera_config']
87
+ }
88
+
89
+ with_puppet_settings(puppet_settings) do
67
90
  Puppet::Pal.in_tmp_environment('bolt_catalog', env_conf) do |pal|
68
- inv = Bolt::ApplyInventory.new(request['config'])
69
- Puppet.override(bolt_pdb_client: pdb_client,
70
- bolt_inventory: inv) do
91
+ Puppet.override(puppet_overrides) do
71
92
  Puppet.lookup(:pal_current_node).trusted_data = target['trusted']
72
93
  pal.with_catalog_compiler do |compiler|
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))
105
-
94
+ options = request['puppet_config'] || {}
106
95
  # Configure language strictness in the CatalogCompiler. We want Bolt to be able
107
96
  # to compile most Puppet 4+ manifests, so we default to allowing deprecated functions.
108
97
  Puppet[:strict] = options['strict'] || :warning
109
98
  Puppet[:strict_variables] = options['strict_variables'] || false
110
- ast = Puppet::Pops::Serialization::FromDataConverter.convert(pal_main)
111
- # This will be a Program when running via `bolt apply`, but will
112
- # only be a subset of the AST when compiling an apply block in a
113
- # plan. In that case, we need to discover the definitions (which
114
- # would ordinarily be stored on the Program) and construct a Program object.
115
- unless ast.is_a?(Puppet::Pops::Model::Program)
116
- # Node definitions must be at the top level of the apply block.
117
- # That means the apply body either a) consists of just a
118
- # NodeDefinition, b) consists of a BlockExpression which may
119
- # contain NodeDefinitions, or c) doesn't contain NodeDefinitions.
120
- definitions = if ast.is_a?(Puppet::Pops::Model::BlockExpression)
121
- ast.statements.select { |st| st.is_a?(Puppet::Pops::Model::NodeDefinition) }
122
- elsif ast.is_a?(Puppet::Pops::Model::NodeDefinition)
123
- [ast]
124
- else
125
- []
126
- end
127
- ast = Puppet::Pops::Model::Factory.PROGRAM(ast, definitions, ast.locator).model
128
- end
99
+
100
+ pal_main = request['code_ast'] || request['code_string']
101
+ ast = build_program(pal_main)
129
102
  compiler.evaluate(ast)
130
- compiler.instance_variable_get(:@internal_compiler).send(:evaluate_ast_node)
103
+ compiler.evaluate_ast_node
131
104
  compiler.compile_additions
132
105
  compiler.with_json_encoding(&:encode)
133
106
  end
@@ -135,5 +108,45 @@ module Bolt
135
108
  end
136
109
  end
137
110
  end
111
+
112
+ # Warn and remove variables that will be shadowed by facts of the same
113
+ # name, which are set in scope earlier.
114
+ def shadow_vars(type, vars, facts)
115
+ collisions, valid = vars.partition do |k, _|
116
+ facts.include?(k)
117
+ end
118
+ if collisions.any?
119
+ names = collisions.map { |k, _| "$#{k}" }.join(', ')
120
+ plural = collisions.length == 1 ? '' : 's'
121
+ Puppet.warning("#{type.capitalize} variable#{plural} #{names} will be overridden by fact#{plural} " \
122
+ "of the same name in the apply block")
123
+ end
124
+ valid.to_h
125
+ end
126
+
127
+ def build_program(code)
128
+ ast = Puppet::Pops::Serialization::FromDataConverter.convert(code)
129
+
130
+ # This will be a Program when running via `bolt apply`, but will
131
+ # only be a subset of the AST when compiling an apply block in a
132
+ # plan. In that case, we need to discover the definitions (which
133
+ # would ordinarily be stored on the Program) and construct a Program object.
134
+ if ast.is_a?(Puppet::Pops::Model::Program)
135
+ ast
136
+ else
137
+ # Node definitions must be at the top level of the apply block.
138
+ # That means the apply body either a) consists of just a
139
+ # NodeDefinition, b) consists of a BlockExpression which may
140
+ # contain NodeDefinitions, or c) doesn't contain NodeDefinitions.
141
+ definitions = if ast.is_a?(Puppet::Pops::Model::BlockExpression)
142
+ ast.statements.select { |st| st.is_a?(Puppet::Pops::Model::NodeDefinition) }
143
+ elsif ast.is_a?(Puppet::Pops::Model::NodeDefinition)
144
+ [ast]
145
+ else
146
+ []
147
+ end
148
+ Puppet::Pops::Model::Factory.PROGRAM(ast, definitions, ast.locator).model
149
+ end
150
+ end
138
151
  end
139
152
  end
@@ -115,7 +115,7 @@ module Bolt
115
115
  Bolt::Config.from_file(options[:configfile], options)
116
116
  else
117
117
  project = if options[:boltdir]
118
- Bolt::Project.new(options[:boltdir])
118
+ Bolt::Project.create_project(options[:boltdir])
119
119
  else
120
120
  Bolt::Project.find_boltdir(Dir.pwd)
121
121
  end
@@ -392,7 +392,7 @@ module Bolt
392
392
  end
393
393
  code = apply_manifest(options[:code], options[:targets], options[:object], options[:noop])
394
394
  else
395
- executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop])
395
+ executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop], config.modified_concurrency)
396
396
  targets = options[:targets]
397
397
 
398
398
  results = nil
@@ -512,7 +512,7 @@ module Bolt
512
512
  params: plan_arguments }
513
513
  plan_context[:description] = options[:description] if options[:description]
514
514
 
515
- executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop])
515
+ executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop], config.modified_concurrency)
516
516
  if options.fetch(:format, 'human') == 'human'
517
517
  executor.subscribe(outputter)
518
518
  else
@@ -537,7 +537,18 @@ module Bolt
537
537
  Puppet[:tasks] = false
538
538
  ast = pal.parse_manifest(code, filename)
539
539
 
540
- executor = Bolt::Executor.new(config.concurrency, analytics, noop)
540
+ if defined?(ast.body) &&
541
+ (ast.body.is_a?(Puppet::Pops::Model::HostClassDefinition) ||
542
+ ast.body.is_a?(Puppet::Pops::Model::ResourceTypeDefinition))
543
+ message = "Manifest only contains definitions and will result in no changes on the targets. "\
544
+ "Definitions must be declared for their resources to be applied. You can read more "\
545
+ "about defining and declaring classes and types in the Puppet documentation at "\
546
+ "https://puppet.com/docs/puppet/latest/lang_classes.html and "\
547
+ "https://puppet.com/docs/puppet/latest/lang_defined_types.html"
548
+ @logger.warn(message)
549
+ end
550
+
551
+ executor = Bolt::Executor.new(config.concurrency, analytics, noop, config.modified_concurrency)
541
552
  executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
542
553
  executor.subscribe(log_outputter)
543
554
  # apply logging looks like plan logging, so tell the outputter we're in a
@@ -760,7 +771,7 @@ module Bolt
760
771
  end
761
772
 
762
773
  def pal
763
- project = config.project.load_as_module? ? config.project : nil
774
+ project = config.project.project_file? ? config.project : nil
764
775
  @pal ||= Bolt::PAL.new(config.modulepath,
765
776
  config.hiera_config,
766
777
  config.project.resource_types,
@@ -23,7 +23,7 @@ module Bolt
23
23
  end
24
24
 
25
25
  class Config
26
- attr_reader :config_files, :warnings, :data, :transports, :project
26
+ attr_reader :config_files, :warnings, :data, :transports, :project, :modified_concurrency
27
27
 
28
28
  TRANSPORT_CONFIG = {
29
29
  'ssh' => Bolt::Config::Transport::SSH,
@@ -34,6 +34,13 @@ module Bolt
34
34
  'remote' => Bolt::Config::Transport::Remote
35
35
  }.freeze
36
36
 
37
+ TRANSPORT_OPTION = { 'transport' => 'The default transport to use when the '\
38
+ 'transport for a target is not specified in the URL.' }.freeze
39
+
40
+ DEFAULT_TRANSPORT_OPTION = { 'transport' => 'ssh' }.freeze
41
+
42
+ CONFIG_IN_INVENTORY = TRANSPORT_CONFIG.merge(TRANSPORT_OPTION)
43
+
37
44
  # NOTE: All configuration options should have a corresponding schema property
38
45
  # in schemas/bolt-config.schema.json
39
46
  OPTIONS = {
@@ -47,9 +54,7 @@ module Bolt
47
54
  "targets on the command line and from plans.",
48
55
  "log" => "The configuration of the logfile output. Configuration can be set for "\
49
56
  "`console` and the path to a log file, such as `~/.puppetlabs/bolt/debug.log`.",
50
- "modulepath" => "The module path for loading tasks and plan code. This is either an array "\
51
- "of directories or a string containing a list of directories separated by the "\
52
- "OS-specific PATH separator.",
57
+ "modulepath" => "An array of directories that Bolt loads content (e.g. plans and tasks) from.",
53
58
  "plugin_hooks" => "Which plugins a specific hook should use.",
54
59
  "plugins" => "A map of plugins and their configuration data.",
55
60
  "puppetdb" => "A map containing options for configuring the Bolt PuppetDB client.",
@@ -57,8 +62,8 @@ module Bolt
57
62
  "save-rerun" => "Whether to update `.rerun.json` in the Bolt project directory. If "\
58
63
  "your target names include passwords, set this value to `false` to avoid "\
59
64
  "writing passwords to disk.",
60
- "transport" => "The default transport to use when the transport for a target is not "\
61
- "specified in the URL or inventory.",
65
+ "transport" => "The default transport to use when the transport for a target is not specified "\
66
+ "in the URL or inventory.",
62
67
  "trusted-external-command" => "The path to an executable on the Bolt controller that can produce "\
63
68
  "external trusted facts. **External trusted facts are experimental in both "\
64
69
  "Puppet and Bolt and this API may change or be removed.**"
@@ -66,8 +71,8 @@ module Bolt
66
71
 
67
72
  DEFAULT_OPTIONS = {
68
73
  "color" => true,
69
- "concurrency" => 100,
70
74
  "compile-concurrency" => "Number of cores",
75
+ "concurrency" => "100 or one-third of the ulimit, whichever is lower",
71
76
  "format" => "human",
72
77
  "hiera-config" => "Boltdir/hiera.yaml",
73
78
  "inventoryfile" => "Boltdir/inventory.yaml",
@@ -103,49 +108,57 @@ module Bolt
103
108
  "show_diff" => false
104
109
  }.freeze
105
110
 
111
+ DEFAULT_DEFAULT_CONCURRENCY = 100
112
+
106
113
  def self.default
107
- new(Bolt::Project.new('.'), {})
114
+ new(Bolt::Project.create_project('.'), {})
108
115
  end
109
116
 
110
117
  def self.from_project(project, overrides = {})
111
- data = {
112
- filepath: project.config_file,
113
- data: Bolt::Util.read_optional_yaml_hash(project.config_file, 'config')
114
- }
118
+ conf = if project.project_file == project.config_file
119
+ project.data
120
+ else
121
+ Bolt::Util.read_optional_yaml_hash(project.config_file, 'config')
122
+ end
123
+
124
+ data = { filepath: project.config_file, data: conf }
115
125
 
116
- data = load_defaults.push(data).select { |config| config[:data]&.any? }
126
+ data = load_defaults(project).push(data).select { |config| config[:data]&.any? }
117
127
 
118
128
  new(project, data, overrides)
119
129
  end
120
130
 
121
131
  def self.from_file(configfile, overrides = {})
122
- project = Bolt::Project.new(Pathname.new(configfile).expand_path.dirname)
123
-
124
- data = {
125
- filepath: project.config_file,
126
- data: Bolt::Util.read_yaml_hash(configfile, 'config')
127
- }
128
- data = load_defaults.push(data).select { |config| config[:data]&.any? }
132
+ project = Bolt::Project.create_project(Pathname.new(configfile).expand_path.dirname)
133
+ conf = if project.project_file == project.config_file
134
+ project.data
135
+ else
136
+ Bolt::Util.read_yaml_hash(configfile, 'config')
137
+ end
138
+ data = { filepath: project.config_file, data: conf }
139
+ data = load_defaults(project).push(data).select { |config| config[:data]&.any? }
129
140
 
130
141
  new(project, data, overrides)
131
142
  end
132
143
 
133
- def self.load_defaults
144
+ def self.load_defaults(project)
134
145
  # Lazy-load expensive gem code
135
146
  require 'win32/dir' if Bolt::Util.windows?
136
147
 
137
- system_path = if Bolt::Util.windows?
138
- Pathname.new(File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'bolt', 'etc', 'bolt.yaml'))
139
- else
140
- Pathname.new(File.join('/etc', 'puppetlabs', 'bolt', 'bolt.yaml'))
141
- end
148
+ # Don't load /etc/puppetlabs/bolt/bolt.yaml twice
149
+ confs = if project.path == Bolt::Project.system_path
150
+ []
151
+ else
152
+ system_path = Pathname.new(File.join(Bolt::Project.system_path, 'bolt.yaml'))
153
+ [{ filepath: system_path, data: Bolt::Util.read_optional_yaml_hash(system_path, 'config') }]
154
+ end
155
+
142
156
  user_path = begin
143
157
  Pathname.new(File.expand_path(File.join('~', '.puppetlabs', 'etc', 'bolt', 'bolt.yaml')))
144
158
  rescue ArgumentError
145
159
  nil
146
160
  end
147
161
 
148
- confs = [{ filepath: system_path, data: Bolt::Util.read_optional_yaml_hash(system_path, 'config') }]
149
162
  confs << { filepath: user_path, data: Bolt::Util.read_optional_yaml_hash(user_path, 'config') } if user_path
150
163
  confs
151
164
  end
@@ -156,8 +169,8 @@ module Bolt
156
169
  end
157
170
 
158
171
  @logger = Logging.logger[self]
159
- @warnings = []
160
172
  @project = project
173
+ @warnings = @project.warnings.dup
161
174
  @transports = {}
162
175
  @config_files = []
163
176
 
@@ -165,7 +178,7 @@ module Bolt
165
178
  'apply_settings' => {},
166
179
  'color' => true,
167
180
  'compile-concurrency' => Etc.nprocessors,
168
- 'concurrency' => 100,
181
+ 'concurrency' => default_concurrency,
169
182
  'format' => 'human',
170
183
  'log' => { 'console' => {} },
171
184
  'plugin_hooks' => {},
@@ -183,6 +196,12 @@ module Bolt
183
196
 
184
197
  override_data = normalize_overrides(overrides)
185
198
 
199
+ # If we need to lower concurrency and concurrency is not configured
200
+ ld_concurrency = loaded_data.map(&:keys).flatten.include?('concurrency')
201
+ @modified_concurrency = default_concurrency != DEFAULT_DEFAULT_CONCURRENCY &&
202
+ !ld_concurrency &&
203
+ !override_data.key?('concurrency')
204
+
186
205
  @data = merge_config_layers(default_data, *loaded_data, override_data)
187
206
 
188
207
  TRANSPORT_CONFIG.each do |transport, config|
@@ -458,5 +477,19 @@ module Bolt
458
477
  l =~ /[A-Za-z]/ ? "[#{l.upcase}#{l.downcase}]" : l
459
478
  end.join
460
479
  end
480
+
481
+ # Etc::SC_OPEN_MAX is meaningless on windows, not defined in PE Jruby and not available
482
+ # on some platforms. This method holds the logic to decide whether or not to even consider it.
483
+ def sc_open_max_available?
484
+ !Bolt::Util.windows? && defined?(Etc::SC_OPEN_MAX) && Etc.sysconf(Etc::SC_OPEN_MAX)
485
+ end
486
+
487
+ def default_concurrency
488
+ @default_concurrency ||= if !sc_open_max_available? || Etc.sysconf(Etc::SC_OPEN_MAX) >= 300
489
+ DEFAULT_DEFAULT_CONCURRENCY
490
+ else
491
+ Etc.sysconf(Etc::SC_OPEN_MAX) / 7
492
+ end
493
+ end
461
494
  end
462
495
  end