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.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +2 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +3 -2
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +2 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +52 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +65 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +4 -2
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +8 -2
- data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +65 -43
- data/lib/bolt/analytics.rb +21 -2
- data/lib/bolt/applicator.rb +8 -2
- data/lib/bolt/apply_inventory.rb +4 -0
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/apply_target.rb +4 -0
- data/lib/bolt/bolt_option_parser.rb +5 -4
- data/lib/bolt/catalog.rb +81 -68
- data/lib/bolt/cli.rb +16 -5
- data/lib/bolt/config.rb +62 -29
- data/lib/bolt/config/transport/ssh.rb +130 -91
- data/lib/bolt/executor.rb +14 -1
- data/lib/bolt/inventory/group.rb +1 -1
- data/lib/bolt/inventory/inventory.rb +4 -0
- data/lib/bolt/inventory/target.rb +4 -0
- data/lib/bolt/pal.rb +3 -0
- data/lib/bolt/project.rb +55 -11
- data/lib/bolt/resource_instance.rb +10 -3
- data/lib/bolt/shell/bash.rb +1 -1
- data/lib/bolt/shell/powershell/snippets.rb +8 -0
- data/lib/bolt/transport/local/connection.rb +2 -1
- data/lib/bolt/transport/ssh/connection.rb +39 -0
- data/lib/bolt/transport/winrm/connection.rb +4 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_spec/bolt_context.rb +1 -1
- data/lib/bolt_spec/run.rb +1 -1
- metadata +6 -5
data/lib/bolt/applicator.rb
CHANGED
@@ -14,14 +14,16 @@ require 'open3'
|
|
14
14
|
|
15
15
|
module Bolt
|
16
16
|
class Applicator
|
17
|
-
def initialize(inventory, executor, modulepath, plugin_dirs,
|
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,
|
data/lib/bolt/apply_inventory.rb
CHANGED
data/lib/bolt/apply_result.rb
CHANGED
@@ -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,
|
data/lib/bolt/apply_target.rb
CHANGED
@@ -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
|
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
|
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
|
693
|
+
'Maximum number of simultaneous connections') do |concurrency|
|
693
694
|
@options[:concurrency] = concurrency
|
694
695
|
end
|
695
696
|
define('--compile-concurrency CONCURRENCY', Integer,
|
data/lib/bolt/catalog.rb
CHANGED
@@ -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(
|
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
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
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.
|
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
|
data/lib/bolt/cli.rb
CHANGED
@@ -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.
|
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
|
-
|
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.
|
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,
|
data/lib/bolt/config.rb
CHANGED
@@ -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" => "
|
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
|
-
"
|
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.
|
114
|
+
new(Bolt::Project.create_project('.'), {})
|
108
115
|
end
|
109
116
|
|
110
117
|
def self.from_project(project, overrides = {})
|
111
|
-
|
112
|
-
|
113
|
-
|
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.
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
data =
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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' =>
|
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
|