bolt 1.42.0 → 1.43.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/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +6 -5
- data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +4 -4
- data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +2 -3
- data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +2 -3
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +73 -13
- data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +2 -2
- data/lib/bolt/applicator.rb +67 -2
- data/lib/bolt/apply_inventory.rb +89 -0
- data/lib/bolt/apply_result.rb +7 -0
- data/lib/bolt/apply_target.rb +77 -0
- data/lib/bolt/bolt_option_parser.rb +5 -1
- data/lib/bolt/catalog.rb +20 -5
- data/lib/bolt/config.rb +1 -1
- data/lib/bolt/error.rb +2 -1
- data/lib/bolt/executor.rb +4 -0
- data/lib/bolt/outputter/human.rb +3 -3
- data/lib/bolt/outputter/json.rb +11 -3
- data/lib/bolt/result.rb +18 -0
- data/lib/bolt/result_set.rb +12 -0
- data/lib/bolt/target.rb +1 -0
- data/lib/bolt/transport/local.rb +1 -1
- data/lib/bolt/transport/local/shell.rb +2 -1
- data/lib/bolt/transport/ssh.rb +8 -2
- data/lib/bolt/transport/ssh/connection.rb +2 -2
- data/lib/bolt/transport/sudoable/connection.rb +3 -1
- data/lib/bolt/version.rb +1 -1
- data/modules/aggregate/lib/puppet/functions/aggregate/count.rb +1 -1
- data/modules/aggregate/lib/puppet/functions/aggregate/nodes.rb +1 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/targets.rb +21 -0
- data/modules/aggregate/plans/count.pp +4 -4
- data/modules/aggregate/plans/nodes.pp +1 -0
- data/modules/aggregate/plans/targets.pp +35 -0
- data/modules/canary/lib/puppet/functions/canary/skip.rb +9 -9
- data/modules/canary/plans/init.pp +9 -9
- data/modules/puppetdb_fact/plans/init.pp +4 -4
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c237cb6fec506c63071962c4d8cf16fafb0b196abe720c5e0af8b9d8d350f11
|
4
|
+
data.tar.gz: b7505bc572e3e123b265b869ed2386bbb784882365102a3b816ab1b5e5599ab1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0178352d34985cdd7c2960a018ff4a502b813763df806e72dd21f302bf26b173eeaa54eb2b44092b340e82b682b141fca5f72ba6e11bda6475556983dcbcf0ee'
|
7
|
+
data.tar.gz: 4473441d248c6023b3fe87364439f6c32498b6a6ac71b61925116838ab72eab22a457e309189cac2d4a0ebd58b57e7ded0140c902dc2367c5f45d76cbba5932a
|
@@ -3,6 +3,7 @@
|
|
3
3
|
Puppet::DataTypes.create_type('Target') do
|
4
4
|
begin
|
5
5
|
inventory = Puppet.lookup(:bolt_inventory)
|
6
|
+
|
6
7
|
inventory_version = inventory.version
|
7
8
|
if inventory_version != 1
|
8
9
|
target_implementation_class = inventory.target_implementation_class
|
@@ -10,7 +11,7 @@ Puppet::DataTypes.create_type('Target') do
|
|
10
11
|
rescue Puppet::Context::UndefinedBindingError
|
11
12
|
inventory_version = 1
|
12
13
|
end
|
13
|
-
|
14
|
+
require 'bolt/target'
|
14
15
|
|
15
16
|
if inventory_version == 1
|
16
17
|
interface <<-PUPPET
|
@@ -33,15 +34,15 @@ Puppet::DataTypes.create_type('Target') do
|
|
33
34
|
attributes => {
|
34
35
|
uri => { type => Optional[String[1]], kind => given_or_derived },
|
35
36
|
name => { type => Optional[String[1]] , kind => given_or_derived },
|
37
|
+
safe_name => { type => Optional[String[1]], kind => given_or_derived },
|
36
38
|
target_alias => { type => Optional[Variant[String[1], Array[String[1]]]], kind => given_or_derived },
|
37
39
|
config => { type => Optional[Hash[String[1], Data]], kind => given_or_derived },
|
38
|
-
vars => { type => Optional[Hash[String[1], Data]], kind => given_or_derived
|
39
|
-
facts => { type => Optional[Hash[String[1], Data]], kind => given_or_derived
|
40
|
+
vars => { type => Optional[Hash[String[1], Data]], kind => given_or_derived },
|
41
|
+
facts => { type => Optional[Hash[String[1], Data]], kind => given_or_derived },
|
40
42
|
features => { type => Optional[Array[String[1]]], kind => given_or_derived },
|
41
|
-
plugin_hooks => { type => Optional[Hash[String[1], Data]], kind => given_or_derived
|
43
|
+
plugin_hooks => { type => Optional[Hash[String[1], Data]], kind => given_or_derived }
|
42
44
|
},
|
43
45
|
functions => {
|
44
|
-
safe_name => Callable[[], String[1]],
|
45
46
|
host => Callable[[], Optional[String]],
|
46
47
|
password => Callable[[], Optional[String[1]]],
|
47
48
|
port => Callable[[], Optional[Integer]],
|
@@ -12,14 +12,14 @@ Puppet::Functions.create_function(:catch_errors) do
|
|
12
12
|
# otherwise the result will be returned
|
13
13
|
# @example Catch errors for a block
|
14
14
|
# catch_errors() || {
|
15
|
-
# run_command("whoami", $
|
16
|
-
# run_command("adduser ryan", $
|
15
|
+
# run_command("whoami", $targets)
|
16
|
+
# run_command("adduser ryan", $targets)
|
17
17
|
# }
|
18
18
|
# @example Catch parse errors for a block of code
|
19
19
|
# catch_errors(['bolt/parse-error']) || {
|
20
|
-
# run_plan('canary', $
|
20
|
+
# run_plan('canary', $targets)
|
21
21
|
# run_plan('other_plan)
|
22
|
-
# apply($
|
22
|
+
# apply($targets) || {
|
23
23
|
# notify { "Hello": }
|
24
24
|
# }
|
25
25
|
# }
|
@@ -4,10 +4,9 @@ require 'bolt/error'
|
|
4
4
|
|
5
5
|
# Get a single target from inventory if it exists, otherwise create a new Target.
|
6
6
|
#
|
7
|
-
# **NOTE:** Calling `get_target`
|
8
|
-
# version 2 inventory creates a new Target object.
|
9
|
-
# `get_target('all')` returns an empty array.
|
7
|
+
# **NOTE:** Calling `get_target('all')` returns an empty array.
|
10
8
|
# **NOTE:** Only compatible with inventory v2
|
9
|
+
# **NOTE:** Not available in apply block when `future` is true
|
11
10
|
Puppet::Functions.create_function(:get_target) do
|
12
11
|
# @param name A Target name.
|
13
12
|
# @return A single target, either new or from inventory.
|
@@ -3,10 +3,9 @@
|
|
3
3
|
require 'bolt/error'
|
4
4
|
|
5
5
|
# Parses common ways of referring to targets and returns an array of Targets.
|
6
|
-
#
|
7
|
-
# **NOTE:** Calling `get_targets` inside an `apply` block with a
|
8
|
-
# version 2 inventory creates a new Target object.
|
9
6
|
# `get_targets('all')` returns an empty array.
|
7
|
+
#
|
8
|
+
# **NOTE:** Not available in apply block when `future` is true
|
10
9
|
Puppet::Functions.create_function(:get_targets) do
|
11
10
|
# @param names A pattern or array of patterns identifying a set of targets.
|
12
11
|
# @return A list of unique Targets resolved from any target URIs and groups.
|
@@ -11,7 +11,7 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
|
|
11
11
|
# @param args Arguments to the plan. Can also include additional options: '_catch_errors', '_run_as'.
|
12
12
|
# @return [PlanResult] The result of running the plan. Undef if plan does not explicitly return results.
|
13
13
|
# @example Run a plan
|
14
|
-
# run_plan('canary', 'command' => 'false', '
|
14
|
+
# run_plan('canary', 'command' => 'false', 'targets' => $targets, '_catch_errors' => true)
|
15
15
|
dispatch :run_plan do
|
16
16
|
scope_param
|
17
17
|
param 'String', :plan_name
|
@@ -19,13 +19,22 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
|
|
19
19
|
return_type 'Boltlib::PlanResult'
|
20
20
|
end
|
21
21
|
|
22
|
-
# Run a plan, specifying $nodes as a positional argument.
|
22
|
+
# Run a plan, specifying $nodes or $targets as a positional argument.
|
23
|
+
#
|
24
|
+
# When running a plan with a $nodes parameter, the second positional argument will always specify
|
25
|
+
# the $nodes parameter. When running a plan with a $targets parameter and no $nodes parameter, the
|
26
|
+
# second positional argument specifies the $targets parameter.
|
27
|
+
#
|
28
|
+
# Deprecation Warning: Starting with Bolt 2.0, a plan with both a $nodes and $targets parameter
|
29
|
+
# cannot specify either parameter using the second positional argument and will result in the plan
|
30
|
+
# failing to run.
|
31
|
+
#
|
23
32
|
# @param plan_name The plan to run.
|
24
33
|
# @param args Arguments to the plan. Can also include additional options: '_catch_errors', '_run_as'.
|
25
34
|
# @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
|
26
35
|
# @return [PlanResult] The result of running the plan. Undef if plan does not explicitly return results.
|
27
36
|
# @example Run a plan
|
28
|
-
# run_plan('canary', $
|
37
|
+
# run_plan('canary', $targets, 'command' => 'false')
|
29
38
|
dispatch :run_plan_with_targetspec do
|
30
39
|
scope_param
|
31
40
|
param 'String', :plan_name
|
@@ -35,16 +44,14 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
|
|
35
44
|
end
|
36
45
|
|
37
46
|
def run_plan_with_targetspec(scope, plan_name, targets, args = {})
|
38
|
-
|
39
|
-
raise ArgumentError,
|
40
|
-
"A plan's 'nodes' parameter may be specified as the second positional argument to " \
|
41
|
-
"run_plan(), but in that case 'nodes' must not be specified in the named arguments " \
|
42
|
-
"hash."
|
43
|
-
end
|
44
|
-
run_plan(scope, plan_name, args.merge('nodes' => targets))
|
47
|
+
run_inner_plan(scope, plan_name, targets, args)
|
45
48
|
end
|
46
49
|
|
47
50
|
def run_plan(scope, plan_name, args = {})
|
51
|
+
run_inner_plan(scope, plan_name, nil, args)
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_inner_plan(scope, plan_name, targets, args = {})
|
48
55
|
unless Puppet[:tasks]
|
49
56
|
raise Puppet::ParseErrorWithIssue
|
50
57
|
.from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'run_plan')
|
@@ -87,10 +94,13 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
|
|
87
94
|
# If a TargetSpec parameter is passed, ensure it is in inventory
|
88
95
|
inventory = Puppet.lookup(:bolt_inventory)
|
89
96
|
|
97
|
+
param_types = closure.parameters.each_with_object({}) do |param, param_acc|
|
98
|
+
param_acc[param.name] = extract_parameter_types(param.type_expr)&.flatten
|
99
|
+
end
|
100
|
+
|
101
|
+
targets_to_param(targets, params, param_types, executor) if targets
|
102
|
+
|
90
103
|
if inventory.version > 1
|
91
|
-
param_types = closure.parameters.each_with_object({}) do |param, param_acc|
|
92
|
-
param_acc[param.name] = extract_parameter_types(param.type_expr).flatten
|
93
|
-
end
|
94
104
|
params.each do |param, value|
|
95
105
|
# Note the safe lookup operator is needed to handle case where a parameter is passed to a
|
96
106
|
# plan that the plan is not expecting
|
@@ -159,4 +169,54 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
|
|
159
169
|
extract_parameter_types(type_expr.element_type)
|
160
170
|
end
|
161
171
|
end
|
172
|
+
|
173
|
+
def targets_to_param(targets, params, param_types, executor)
|
174
|
+
nodes_param = param_types.include?('nodes')
|
175
|
+
targets_param = param_types['targets']&.any? { |p| p.match?(/TargetSpec/) }
|
176
|
+
|
177
|
+
# Both a 'TargetSpec $nodes' and 'TargetSpec $targets' parameter are present in the plan
|
178
|
+
# 1.x behavior: Populate $nodes and warn user that this will error in 2.x
|
179
|
+
# 2.x behavior: Error
|
180
|
+
if nodes_param && targets_param
|
181
|
+
# rubocop:disable Style/GlobalVars
|
182
|
+
if $future
|
183
|
+
raise ArgumentError,
|
184
|
+
"A plan with both a $nodes and $targets parameter cannot have either parameter specified " \
|
185
|
+
"as the second positional argument to run_plan()."
|
186
|
+
else
|
187
|
+
msg = <<~WARNING
|
188
|
+
Deprecation Warning: A plan with both a $nodes and $targets parameter can only specify
|
189
|
+
the $nodes parameter as the second positional argument to run_plan(). Starting in
|
190
|
+
Bolt 2.0, a plan with both a $nodes and $targets parameter will not be able to specify
|
191
|
+
either parameter as the second positional argument to run_plan() and will result in the
|
192
|
+
plan failing.
|
193
|
+
WARNING
|
194
|
+
executor.deprecation(msg)
|
195
|
+
end
|
196
|
+
# rubocop:enable Style/GlobalVars
|
197
|
+
end
|
198
|
+
|
199
|
+
# Always populate a $nodes parameter over $targets
|
200
|
+
if nodes_param
|
201
|
+
if params['nodes']
|
202
|
+
raise ArgumentError,
|
203
|
+
"A plan's 'nodes' parameter may be specified as the second positional argument to " \
|
204
|
+
"run_plan(), but in that case 'nodes' must not be specified in the named arguments " \
|
205
|
+
"hash."
|
206
|
+
end
|
207
|
+
params['nodes'] = targets
|
208
|
+
# If there is only a $targets parameter, then populate it
|
209
|
+
elsif targets_param
|
210
|
+
if params['targets']
|
211
|
+
raise ArgumentError,
|
212
|
+
"A plan's 'targets' parameter may be specified as the second positional argument to " \
|
213
|
+
"run_plan(), but in that case 'targets' must not be specified in the named arguments " \
|
214
|
+
"hash."
|
215
|
+
end
|
216
|
+
params['targets'] = targets
|
217
|
+
# If a plan has neither parameter, just fall back to $nodes
|
218
|
+
else
|
219
|
+
params['nodes'] = targets
|
220
|
+
end
|
221
|
+
end
|
162
222
|
end
|
@@ -13,8 +13,8 @@ Puppet::Functions.create_function(:without_default_logging) do
|
|
13
13
|
# @example Suppress default logging for a series of functions
|
14
14
|
# without_default_logging() || {
|
15
15
|
# notice("Deploying on ${nodes}")
|
16
|
-
# get_targets($
|
17
|
-
# run_task(deploy, $
|
16
|
+
# get_targets($targets).each |$target| {
|
17
|
+
# run_task(deploy, $target)
|
18
18
|
# }
|
19
19
|
# }
|
20
20
|
dispatch :without_default_logging do
|
data/lib/bolt/applicator.rb
CHANGED
@@ -8,6 +8,7 @@ require 'open3'
|
|
8
8
|
require 'bolt/error'
|
9
9
|
require 'bolt/task'
|
10
10
|
require 'bolt/apply_result'
|
11
|
+
require 'bolt/apply_target'
|
11
12
|
require 'bolt/util/puppet_log_level'
|
12
13
|
|
13
14
|
module Bolt
|
@@ -129,6 +130,49 @@ module Bolt
|
|
129
130
|
JSON.parse(out)
|
130
131
|
end
|
131
132
|
|
133
|
+
def future_compile(target, catalog_input)
|
134
|
+
trusted = Puppet::Context::TrustedInformation.new('local', target.name, {})
|
135
|
+
catalog_input[:target] = {
|
136
|
+
name: target.name,
|
137
|
+
facts: @inventory.facts(target).merge('bolt' => true),
|
138
|
+
variables: @inventory.vars(target),
|
139
|
+
trusted: trusted.to_h
|
140
|
+
}
|
141
|
+
# rubocop:disable Style/GlobalVars
|
142
|
+
catalog_input[:future] = $future
|
143
|
+
# rubocop:enable Style/GlobalVars
|
144
|
+
|
145
|
+
bolt_catalog_exe = File.join(libexec, 'bolt_catalog')
|
146
|
+
old_path = ENV['PATH']
|
147
|
+
ENV['PATH'] = "#{RbConfig::CONFIG['bindir']}#{File::PATH_SEPARATOR}#{old_path}"
|
148
|
+
out, err, stat = Open3.capture3('ruby', bolt_catalog_exe, 'compile', stdin_data: catalog_input.to_json)
|
149
|
+
ENV['PATH'] = old_path
|
150
|
+
|
151
|
+
# stderr may contain formatted logs from Puppet's logger or other errors.
|
152
|
+
# Print them in order, but handle them separately. Anything not a formatted log is assumed
|
153
|
+
# to be an error message.
|
154
|
+
logs = err.lines.map do |l|
|
155
|
+
begin
|
156
|
+
JSON.parse(l)
|
157
|
+
rescue StandardError
|
158
|
+
l
|
159
|
+
end
|
160
|
+
end
|
161
|
+
logs.each do |log|
|
162
|
+
if log.is_a?(String)
|
163
|
+
@logger.error(log.chomp)
|
164
|
+
else
|
165
|
+
log.map { |k, v| [k.to_sym, v] }.each do |level, msg|
|
166
|
+
bolt_level = Bolt::Util::PuppetLogLevel::MAPPING[level]
|
167
|
+
@logger.send(bolt_level, "#{target.name}: #{msg.chomp}")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
raise(ApplyError, target.name) unless stat.success?
|
173
|
+
JSON.parse(out)
|
174
|
+
end
|
175
|
+
|
132
176
|
def validate_hiera_config(hiera_config)
|
133
177
|
if File.exist?(File.path(hiera_config))
|
134
178
|
data = File.open(File.path(hiera_config), "r:UTF-8") { |f| YAML.safe_load(f.read, [Symbol]) }
|
@@ -155,7 +199,6 @@ module Bolt
|
|
155
199
|
options = args[1].map { |k, v| [k.sub(/^_/, '').to_sym, v] }.to_h
|
156
200
|
end
|
157
201
|
|
158
|
-
# collect plan vars and merge them over target vars
|
159
202
|
plan_vars = scope.to_hash(true, true)
|
160
203
|
%w[trusted server_facts facts].each { |k| plan_vars.delete(k) }
|
161
204
|
|
@@ -179,11 +222,33 @@ module Bolt
|
|
179
222
|
def apply_ast(raw_ast, targets, options, plan_vars = {})
|
180
223
|
ast = Puppet::Pops::Serialization::ToDataConverter.convert(raw_ast, rich_data: true, symbol_to_string: true)
|
181
224
|
|
225
|
+
# rubocop:disable Style/GlobalVars
|
226
|
+
if $future
|
227
|
+
# Serialize as pcore for *Result* objects
|
228
|
+
plan_vars = Puppet::Pops::Serialization::ToDataConverter.convert(plan_vars,
|
229
|
+
rich_data: true,
|
230
|
+
symbol_as_string: true,
|
231
|
+
type_by_reference: true,
|
232
|
+
local_reference: false)
|
233
|
+
scope = {
|
234
|
+
code_ast: ast,
|
235
|
+
modulepath: @modulepath,
|
236
|
+
pdb_config: @pdb_client.config.to_hash,
|
237
|
+
hiera_config: @hiera_config,
|
238
|
+
plan_vars: plan_vars,
|
239
|
+
# This data isn't available on the target config hash
|
240
|
+
config: @inventory.config.transport_data_get
|
241
|
+
}
|
242
|
+
end
|
243
|
+
# rubocop:enable Style/GlobalVars
|
244
|
+
|
182
245
|
r = @executor.log_action('apply catalog', targets) do
|
183
246
|
futures = targets.map do |target|
|
184
247
|
Concurrent::Future.execute(executor: @pool) do
|
185
248
|
@executor.with_node_logging("Compiling manifest block", [target]) do
|
186
|
-
|
249
|
+
# rubocop:disable Style/GlobalVars
|
250
|
+
$future ? future_compile(target, scope) : compile(target, ast, plan_vars)
|
251
|
+
# rubocop:enable Style/GlobalVars
|
187
252
|
end
|
188
253
|
end
|
189
254
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/boltdir'
|
4
|
+
require 'bolt/config'
|
5
|
+
require 'bolt/error'
|
6
|
+
|
7
|
+
module Bolt
|
8
|
+
class ApplyInventory
|
9
|
+
class InvalidFunctionCall < Bolt::Error
|
10
|
+
def initialize(function)
|
11
|
+
super("The function '#{function}' is not callable within an apply block",
|
12
|
+
'bolt.inventory/invalid-function-call')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :config_hash
|
17
|
+
|
18
|
+
def initialize(config_hash = {})
|
19
|
+
@config_hash = config_hash
|
20
|
+
@targets = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_apply_target(target)
|
24
|
+
@targets[target.name] = target
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate
|
28
|
+
@groups.validate
|
29
|
+
end
|
30
|
+
|
31
|
+
def version
|
32
|
+
2
|
33
|
+
end
|
34
|
+
|
35
|
+
def target_implementation_class
|
36
|
+
Bolt::ApplyTarget
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_targets(*_params)
|
40
|
+
raise InvalidFunctionCall, 'get_targets'
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_target(*_params)
|
44
|
+
raise InvalidFunctionCall, 'get_target'
|
45
|
+
end
|
46
|
+
|
47
|
+
# rubocop:disable Naming/AccessorMethodName
|
48
|
+
def set_var(*_params)
|
49
|
+
raise InvalidFunctionCall, 'set_var'
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_feature(*_params)
|
53
|
+
raise InvalidFunctionCall, 'set_feature'
|
54
|
+
end
|
55
|
+
# rubocop:enable Naming/AccessorMethodName
|
56
|
+
|
57
|
+
def vars(target)
|
58
|
+
@targets[target.name].vars
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_facts(*_params)
|
62
|
+
raise InvalidFunctionCall, 'add_facts'
|
63
|
+
end
|
64
|
+
|
65
|
+
def facts(target)
|
66
|
+
@targets[target.name].facts
|
67
|
+
end
|
68
|
+
|
69
|
+
def features(target)
|
70
|
+
@targets[target.name].features
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_to_group(*_params)
|
74
|
+
raise InvalidFunctionCall, 'add_to_group'
|
75
|
+
end
|
76
|
+
|
77
|
+
def plugin_hooks(target)
|
78
|
+
@targets[target.name].plugin_hooks
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_config(_target, _key_or_key_path, _value)
|
82
|
+
raise InvalidFunctionCall, 'set_config'
|
83
|
+
end
|
84
|
+
|
85
|
+
def target_config(target)
|
86
|
+
@targets[target.name].config
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/bolt/apply_result.rb
CHANGED
@@ -85,6 +85,13 @@ module Bolt
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
+
# Other pcore methods are inherited from Result
|
89
|
+
def _pcore_init_hash
|
90
|
+
{ 'target' => @target,
|
91
|
+
'error' => value['_error'],
|
92
|
+
'report' => value['report'] }
|
93
|
+
end
|
94
|
+
|
88
95
|
def initialize(target, error: nil, report: nil)
|
89
96
|
@target = target
|
90
97
|
@value = {}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
class ApplyTarget
|
5
|
+
ATTRIBUTES = %i[uri name target_alias config vars facts features
|
6
|
+
plugin_hooks safe_name].freeze
|
7
|
+
COMPUTED = %i[host password port protocol user].freeze
|
8
|
+
|
9
|
+
attr_reader(*ATTRIBUTES)
|
10
|
+
attr_accessor(*COMPUTED)
|
11
|
+
|
12
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
13
|
+
# Target.new from a plan initialized with a hash
|
14
|
+
def self.from_asserted_hash(hash)
|
15
|
+
raise Bolt::Error.new("Target objects cannot be instantiated inside apply blocks", 'bolt/apply-error')
|
16
|
+
end
|
17
|
+
|
18
|
+
# Target.new from a plan with just a uri.
|
19
|
+
def self.from_asserted_args(uri = nil,
|
20
|
+
name = nil,
|
21
|
+
safe_name = nil,
|
22
|
+
target_alias = nil,
|
23
|
+
config = nil,
|
24
|
+
facts = nil,
|
25
|
+
vars = nil,
|
26
|
+
features = nil,
|
27
|
+
plugin_hooks = nil)
|
28
|
+
raise Bolt::Error.new("Target objects cannot be instantiated inside apply blocks", 'bolt/apply-error')
|
29
|
+
end
|
30
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
31
|
+
|
32
|
+
def self._pcore_init_from_hash
|
33
|
+
raise "ApplyTarget shouldn't be instantiated from a pcore_init class method. How did this get called?"
|
34
|
+
end
|
35
|
+
|
36
|
+
def _pcore_init_from_hash(init_hash)
|
37
|
+
inventory = Puppet.lookup(:bolt_inventory)
|
38
|
+
initialize(init_hash, inventory.config_hash)
|
39
|
+
inventory.create_apply_target(self)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(target_hash, config)
|
44
|
+
ATTRIBUTES.each do |attr|
|
45
|
+
instance_variable_set("@#{attr}", target_hash[attr.to_s])
|
46
|
+
end
|
47
|
+
|
48
|
+
# Merge the config hash with inventory config
|
49
|
+
config = Bolt::Util.deep_merge(config, @config)
|
50
|
+
transport = config['transport'] || 'ssh'
|
51
|
+
t_conf = config['transports'][transport] || {}
|
52
|
+
uri_obj = parse_uri(uri)
|
53
|
+
@host = uri_obj.hostname || t_conf['host']
|
54
|
+
@password = Addressable::URI.unencode_component(uri_obj.password) || t_conf['password']
|
55
|
+
@port = uri_obj.port || t_conf['port']
|
56
|
+
@protocol = uri_obj.scheme || transport
|
57
|
+
@user = Addressable::URI.unencode_component(uri_obj.user) || t_conf['user']
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_uri(string)
|
61
|
+
require 'addressable/uri'
|
62
|
+
if string.nil?
|
63
|
+
Addressable::URI.new
|
64
|
+
# Forbid empty uri
|
65
|
+
elsif string.empty?
|
66
|
+
raise Bolt::ParseError, "Could not parse target URI: URI is empty string"
|
67
|
+
elsif string =~ %r{^[^:]+://}
|
68
|
+
Addressable::URI.parse(string)
|
69
|
+
else
|
70
|
+
# Initialize with an empty scheme to ensure we parse the hostname correctly
|
71
|
+
Addressable::URI.parse("//#{string}")
|
72
|
+
end
|
73
|
+
rescue Addressable::URI::InvalidURIError => e
|
74
|
+
raise Bolt::ParseError, "Could not parse target URI: #{e.message}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -8,7 +8,7 @@ module Bolt
|
|
8
8
|
class BoltOptionParser < OptionParser
|
9
9
|
OPTIONS = { inventory: %w[nodes targets query rerun description],
|
10
10
|
authentication: %w[user password password-prompt private-key host-key-check ssl ssl-verify],
|
11
|
-
escalation: %w[run-as sudo-password sudo-password-prompt],
|
11
|
+
escalation: %w[run-as sudo-password sudo-password-prompt sudo-executable],
|
12
12
|
run_context: %w[concurrency inventoryfile save-rerun],
|
13
13
|
global_config_setters: %w[modulepath boltdir configfile],
|
14
14
|
transports: %w[transport connect-timeout tty],
|
@@ -703,6 +703,10 @@ module Bolt
|
|
703
703
|
@options[:'sudo-password'] = STDIN.noecho(&:gets).chomp
|
704
704
|
STDERR.puts
|
705
705
|
end
|
706
|
+
define('--sudo-executable EXEC', "Specify an executable for running as another user.",
|
707
|
+
"This option is experimental.") do |exec|
|
708
|
+
@options[:'sudo-executable'] = exec
|
709
|
+
end
|
706
710
|
|
707
711
|
separator "\nRUN CONTEXT OPTIONS"
|
708
712
|
define('-c', '--concurrency CONCURRENCY', Integer,
|
data/lib/bolt/catalog.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bolt/apply_target'
|
3
4
|
require 'bolt/config'
|
5
|
+
require 'bolt/error'
|
4
6
|
require 'bolt/inventory'
|
7
|
+
require 'bolt/apply_inventory'
|
5
8
|
require 'bolt/pal'
|
6
9
|
require 'bolt/puppetdb'
|
7
10
|
require 'bolt/util'
|
@@ -65,17 +68,29 @@ module Bolt
|
|
65
68
|
target = request['target']
|
66
69
|
pdb_client = Bolt::PuppetDB::Client.new(Bolt::PuppetDB::Config.new(request['pdb_config']))
|
67
70
|
options = request['puppet_config'] || {}
|
71
|
+
|
68
72
|
with_puppet_settings(request['hiera_config']) do
|
69
73
|
Puppet[:rich_data] = true
|
70
74
|
Puppet[:node_name_value] = target['name']
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
+
env_conf = { modulepath: request['modulepath'] || [],
|
76
|
+
facts: target['facts'] || {} }
|
77
|
+
env_conf[:variables] = request['future'] ? {} : target['variables']
|
78
|
+
Puppet::Pal.in_tmp_environment('bolt_catalog', env_conf) do |pal|
|
79
|
+
inv = if request['future']
|
80
|
+
Bolt::ApplyInventory.new(request['config'])
|
81
|
+
else
|
82
|
+
setup_inventory(request['inventory'])
|
83
|
+
end
|
75
84
|
Puppet.override(bolt_pdb_client: pdb_client,
|
76
|
-
bolt_inventory:
|
85
|
+
bolt_inventory: inv) do
|
77
86
|
Puppet.lookup(:pal_current_node).trusted_data = target['trusted']
|
78
87
|
pal.with_catalog_compiler do |compiler|
|
88
|
+
if request['future']
|
89
|
+
# This needs to happen inside the catalog compiler so loaders are initialized for loading
|
90
|
+
vars = Puppet::Pops::Serialization::FromDataConverter.convert(request['plan_vars'])
|
91
|
+
pal.send(:add_variables, compiler.send(:topscope), vars.merge(target['variables']))
|
92
|
+
end
|
93
|
+
|
79
94
|
# Configure language strictness in the CatalogCompiler. We want Bolt to be able
|
80
95
|
# to compile most Puppet 4+ manifests, so we default to allowing deprecated functions.
|
81
96
|
Puppet[:strict] = options['strict'] || :warning
|
data/lib/bolt/config.rb
CHANGED
@@ -36,7 +36,7 @@ module Bolt
|
|
36
36
|
:puppetfile_config, :plugins, :plugin_hooks, :future
|
37
37
|
attr_writer :modulepath
|
38
38
|
|
39
|
-
TRANSPORT_OPTIONS = %i[password run-as sudo-password extensions
|
39
|
+
TRANSPORT_OPTIONS = %i[password run-as sudo-password extensions sudo-executable
|
40
40
|
private-key tty tmpdir user connect-timeout disconnect-timeout
|
41
41
|
cacert token-file service-url interpreters file-protocol smb-port realm].freeze
|
42
42
|
|
data/lib/bolt/error.rb
CHANGED
@@ -61,7 +61,8 @@ module Bolt
|
|
61
61
|
'result_set' => result_set
|
62
62
|
}
|
63
63
|
object_msg = " '#{object}'" if object
|
64
|
-
message = "Plan aborted: #{action}#{object_msg} failed on #{result_set.error_set.length}
|
64
|
+
message = "Plan aborted: #{action}#{object_msg} failed on #{result_set.error_set.length} target"
|
65
|
+
message += "s" unless result_set.error_set.length == 1
|
65
66
|
super(message, 'bolt/run-failure', details)
|
66
67
|
@result_set = result_set
|
67
68
|
@error_code = 2
|
data/lib/bolt/executor.rb
CHANGED
data/lib/bolt/outputter/human.rb
CHANGED
@@ -167,7 +167,7 @@ module Bolt
|
|
167
167
|
def print_summary(results, elapsed_time = nil)
|
168
168
|
ok_set = results.ok_set
|
169
169
|
unless ok_set.empty?
|
170
|
-
@stream.puts format('Successful on %<size>d
|
170
|
+
@stream.puts format('Successful on %<size>d target%<plural>s: %<names>s',
|
171
171
|
size: ok_set.size,
|
172
172
|
plural: ok_set.size == 1 ? '' : 's',
|
173
173
|
names: ok_set.targets.map(&:safe_name).join(','))
|
@@ -176,13 +176,13 @@ module Bolt
|
|
176
176
|
error_set = results.error_set
|
177
177
|
unless error_set.empty?
|
178
178
|
@stream.puts colorize(:red,
|
179
|
-
format('Failed on %<size>d
|
179
|
+
format('Failed on %<size>d target%<plural>s: %<names>s',
|
180
180
|
size: error_set.size,
|
181
181
|
plural: error_set.size == 1 ? '' : 's',
|
182
182
|
names: error_set.targets.map(&:safe_name).join(',')))
|
183
183
|
end
|
184
184
|
|
185
|
-
total_msg = format('Ran on %<size>d
|
185
|
+
total_msg = format('Ran on %<size>d target%<plural>s',
|
186
186
|
size: results.size,
|
187
187
|
plural: results.size == 1 ? '' : 's')
|
188
188
|
total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -36,9 +36,17 @@ module Bolt
|
|
36
36
|
@stream.puts "],\n"
|
37
37
|
@preceding_item = false
|
38
38
|
@items_open = false
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
# rubocop:disable Style/GlobalVars
|
40
|
+
if $future
|
41
|
+
@stream.puts format('"target_count": %<size>d, "elapsed_time": %<elapsed>d }',
|
42
|
+
size: results.size,
|
43
|
+
elapsed: elapsed_time)
|
44
|
+
else
|
45
|
+
@stream.puts format('"node_count": %<size>d, "elapsed_time": %<elapsed>d }',
|
46
|
+
size: results.size,
|
47
|
+
elapsed: elapsed_time)
|
48
|
+
end
|
49
|
+
# rubocop:enable Style/GlobalVars
|
42
50
|
end
|
43
51
|
|
44
52
|
def print_table(results)
|
data/lib/bolt/result.rb
CHANGED
@@ -72,6 +72,24 @@ module Bolt
|
|
72
72
|
new(target, value: value)
|
73
73
|
end
|
74
74
|
|
75
|
+
def self._pcore_init_from_hash
|
76
|
+
raise "Result shouldn't be instantiated from a pcore_init class method. How did this get called?"
|
77
|
+
end
|
78
|
+
|
79
|
+
def _pcore_init_from_hash(init_hash)
|
80
|
+
opts = init_hash.reject { |k, _v| k == 'target' }
|
81
|
+
initialize(init_hash['target'], opts.map { |k, v| [k.to_sym, v] }.to_h)
|
82
|
+
end
|
83
|
+
|
84
|
+
def _pcore_init_hash
|
85
|
+
{ 'target' => @target,
|
86
|
+
'error' => @value['_error'],
|
87
|
+
'message' => @value['_output'],
|
88
|
+
'value' => @value,
|
89
|
+
'action' => @action,
|
90
|
+
'object' => @object }
|
91
|
+
end
|
92
|
+
|
75
93
|
def initialize(target, error: nil, message: nil, value: nil, action: nil, object: nil)
|
76
94
|
@target = target
|
77
95
|
@value = value || {}
|
data/lib/bolt/result_set.rb
CHANGED
@@ -12,6 +12,18 @@ module Bolt
|
|
12
12
|
include(Puppet::Pops::Types::IteratorProducer)
|
13
13
|
end
|
14
14
|
|
15
|
+
def self._pcore_init_from_hash
|
16
|
+
raise "ResultSet shouldn't be instantiated from a pcore_init class method. How did this get called?"
|
17
|
+
end
|
18
|
+
|
19
|
+
def _pcore_init_from_hash(init_hash)
|
20
|
+
initialize(init_hash['results'])
|
21
|
+
end
|
22
|
+
|
23
|
+
def _pcore_init_hash
|
24
|
+
{ 'results' => @results }
|
25
|
+
end
|
26
|
+
|
15
27
|
def iterator
|
16
28
|
if Object.const_defined?(:Puppet) && Puppet.const_defined?(:Pops) &&
|
17
29
|
self.class.included_modules.include?(Puppet::Pops::Types::Iterable)
|
data/lib/bolt/target.rb
CHANGED
data/lib/bolt/transport/local.rb
CHANGED
@@ -126,7 +126,8 @@ module Bolt
|
|
126
126
|
|
127
127
|
if escalate
|
128
128
|
if use_sudo
|
129
|
-
|
129
|
+
sudo_exec = target.options['sudo-executable'] || "sudo"
|
130
|
+
sudo_flags = [sudo_exec, "-k", "-S", "-u", run_as, "-p", Sudoable.sudo_prompt]
|
130
131
|
sudo_flags += ["-E"] if options[:environment]
|
131
132
|
sudo_str = Shellwords.shelljoin(sudo_flags)
|
132
133
|
else
|
data/lib/bolt/transport/ssh.rb
CHANGED
@@ -9,8 +9,8 @@ module Bolt
|
|
9
9
|
module Transport
|
10
10
|
class SSH < Sudoable
|
11
11
|
def self.options
|
12
|
-
%w[host port user password sudo-password private-key host-key-check
|
13
|
-
connect-timeout disconnect-timeout tmpdir run-as tty run-as-command proxyjump interpreters]
|
12
|
+
%w[host port user password sudo-password private-key host-key-check sudo-executable
|
13
|
+
connect-timeout disconnect-timeout tmpdir script-dir run-as tty run-as-command proxyjump interpreters]
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.default_options
|
@@ -48,6 +48,12 @@ module Bolt
|
|
48
48
|
raise Bolt::ValidationError, error_msg
|
49
49
|
end
|
50
50
|
end
|
51
|
+
|
52
|
+
if (dir_opt = options['script-dir'])
|
53
|
+
unless dir_opt.is_a?(String) && !dir_opt.empty?
|
54
|
+
raise Bolt::ValidationError, "script-dir option must be a non-empty string"
|
55
|
+
end
|
56
|
+
end
|
51
57
|
end
|
52
58
|
|
53
59
|
def initialize
|
@@ -194,10 +194,10 @@ module Bolt
|
|
194
194
|
use_sudo = escalate && @target.options['run-as-command'].nil?
|
195
195
|
|
196
196
|
command_str = inject_interpreter(options[:interpreter], command)
|
197
|
-
|
198
197
|
if escalate
|
199
198
|
if use_sudo
|
200
|
-
|
199
|
+
sudo_exec = target.options['sudo-executable'] || "sudo"
|
200
|
+
sudo_flags = [sudo_exec, "-S", "-u", run_as, "-p", Sudoable.sudo_prompt]
|
201
201
|
sudo_flags += ["-E"] if options[:environment]
|
202
202
|
sudo_str = Shellwords.shelljoin(sudo_flags)
|
203
203
|
else
|
@@ -7,6 +7,7 @@ module Bolt
|
|
7
7
|
class Sudoable < Base
|
8
8
|
class Connection
|
9
9
|
attr_accessor :target
|
10
|
+
|
10
11
|
def initialize(target)
|
11
12
|
@target = target
|
12
13
|
@run_as = nil
|
@@ -38,7 +39,8 @@ module Bolt
|
|
38
39
|
|
39
40
|
def make_tempdir
|
40
41
|
tmpdir = @target.options.fetch('tmpdir', '/tmp')
|
41
|
-
|
42
|
+
script_dir = @target.options.fetch('script-dir', SecureRandom.uuid)
|
43
|
+
tmppath = File.join(tmpdir, script_dir)
|
42
44
|
command = ['mkdir', '-m', 700, tmppath]
|
43
45
|
|
44
46
|
result = execute(command)
|
data/lib/bolt/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Aggregates the key/value pairs in the results of a ResultSet into a hash
|
4
|
-
# mapping the keys to a hash of each distinct value and how many
|
4
|
+
# mapping the keys to a hash of each distinct value and how many targets returned
|
5
5
|
# that value for the key.
|
6
6
|
Puppet::Functions.create_function(:'aggregate::count') do
|
7
7
|
dispatch :aggregate_count do
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Aggregates the key/value pairs in the results of a ResultSet into a hash
|
4
|
+
# mapping the keys to a hash of each distinct value and the list of targets
|
5
|
+
# returning that value for the key.
|
6
|
+
Puppet::Functions.create_function(:'aggregate::targets') do
|
7
|
+
dispatch :aggregate_targets do
|
8
|
+
param 'ResultSet', :resultset
|
9
|
+
end
|
10
|
+
|
11
|
+
def aggregate_targets(resultset)
|
12
|
+
resultset.each_with_object({}) do |result, agg|
|
13
|
+
result.value.each do |key, val|
|
14
|
+
agg[key] ||= {}
|
15
|
+
agg[key][val.to_s] ||= []
|
16
|
+
agg[key][val.to_s] << result.target.name
|
17
|
+
end
|
18
|
+
agg
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,7 +2,7 @@ plan aggregate::count(
|
|
2
2
|
Optional[String[0]] $task = undef,
|
3
3
|
Optional[String[0]] $command = undef,
|
4
4
|
Optional[String[0]] $script = undef,
|
5
|
-
TargetSpec $
|
5
|
+
TargetSpec $targets,
|
6
6
|
Hash[String, Data] $params = {}
|
7
7
|
) {
|
8
8
|
|
@@ -24,11 +24,11 @@ plan aggregate::count(
|
|
24
24
|
}
|
25
25
|
|
26
26
|
$res = if ($task) {
|
27
|
-
run_task($task, $
|
27
|
+
run_task($task, $targets, $params)
|
28
28
|
} elsif ($command) {
|
29
|
-
run_command($command, $
|
29
|
+
run_command($command, $targets, $params)
|
30
30
|
} elsif ($script) {
|
31
|
-
run_script($script, $
|
31
|
+
run_script($script, $targets, $params)
|
32
32
|
}
|
33
33
|
|
34
34
|
return aggregate::count($res)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
plan aggregate::targets(
|
2
|
+
Optional[String[0]] $task = undef,
|
3
|
+
Optional[String[0]] $command = undef,
|
4
|
+
Optional[String[0]] $script = undef,
|
5
|
+
TargetSpec $targets,
|
6
|
+
Hash[String, Data] $params = {}
|
7
|
+
) {
|
8
|
+
|
9
|
+
# Validation
|
10
|
+
$type_count = [$task, $command, $script].reduce(0) |$acc, $v| {
|
11
|
+
if ($v) {
|
12
|
+
$acc + 1
|
13
|
+
} else {
|
14
|
+
$acc
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
if ($type_count == 0) {
|
19
|
+
fail_plan("Must specify a command, script, or task to run", 'aggregate/invalid-params')
|
20
|
+
}
|
21
|
+
|
22
|
+
if ($type_count > 1) {
|
23
|
+
fail_plan("Must specify only one command, script, or task to run", 'aggregate/invalid-params')
|
24
|
+
}
|
25
|
+
|
26
|
+
$res = if ($task) {
|
27
|
+
run_task($task, $targets, $params)
|
28
|
+
} elsif ($command) {
|
29
|
+
run_command($command, $targets, $params)
|
30
|
+
} elsif ($script) {
|
31
|
+
run_script($script, $targets, $params)
|
32
|
+
}
|
33
|
+
|
34
|
+
return aggregate::targets($res)
|
35
|
+
}
|
@@ -1,22 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Returns a ResultSet with canary/skipped-
|
3
|
+
# Returns a ResultSet with canary/skipped-target errors for each Target provided.
|
4
4
|
#
|
5
5
|
# This function takes a single parameter:
|
6
|
-
# * List of
|
6
|
+
# * List of targets (Array[Variant[Target,String]])
|
7
7
|
#
|
8
8
|
# Returns a ResultSet.
|
9
9
|
Puppet::Functions.create_function(:'canary::skip') do
|
10
10
|
dispatch :skip_result do
|
11
|
-
param 'Array[Variant[Target,String]]', :
|
11
|
+
param 'Array[Variant[Target,String]]', :targets
|
12
12
|
end
|
13
13
|
|
14
|
-
def skip_result(
|
15
|
-
results =
|
16
|
-
|
17
|
-
Bolt::Result.new(
|
18
|
-
'msg' => "Skipped #{
|
19
|
-
'kind' => 'canary/skipped-
|
14
|
+
def skip_result(targets)
|
15
|
+
results = targets.map do |target|
|
16
|
+
target = Bolt::Target.new(target) unless target.is_a? Bolt::Target
|
17
|
+
Bolt::Result.new(target, value: { '_error' => {
|
18
|
+
'msg' => "Skipped #{target.name} because of a previous failure",
|
19
|
+
'kind' => 'canary/skipped-target',
|
20
20
|
'details' => {}
|
21
21
|
} })
|
22
22
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# @summary
|
2
|
-
# Run a task, command or script on canary
|
2
|
+
# Run a task, command or script on canary targets before running it on all targets.
|
3
3
|
#
|
4
|
-
# This plan accepts a action and a $
|
4
|
+
# This plan accepts a action and a $targets parameter. The action can be the name
|
5
5
|
# of a task, a script or a command to run. It will run the action on a canary
|
6
|
-
# group of
|
7
|
-
# all canaries. This returns a ResultSet object with a Result for every
|
8
|
-
# Any skipped
|
6
|
+
# group of targets and only continue to the rest of the targets if it succeeds on
|
7
|
+
# all canaries. This returns a ResultSet object with a Result for every target.
|
8
|
+
# Any skipped targets will have a 'canary/skipped-target' error kind.
|
9
9
|
#
|
10
10
|
# @param task
|
11
11
|
# The name of the task to run. Mutually exclusive with command and script.
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# The command to run. Mutually exclusive with task and script.
|
14
14
|
# @param script
|
15
15
|
# The script to run. Mutually exclusive with task and command.
|
16
|
-
# @param
|
16
|
+
# @param targets
|
17
17
|
# The target to run on.
|
18
18
|
# @param params
|
19
19
|
# The parameters to use for the task.
|
@@ -23,13 +23,13 @@
|
|
23
23
|
# @return ResultSet a merged resultset from running the action on all targets
|
24
24
|
#
|
25
25
|
# @example Run a command
|
26
|
-
# run_plan(canary, command => 'whoami',
|
26
|
+
# run_plan(canary, command => 'whoami', targets => $mytargets)
|
27
27
|
#
|
28
28
|
plan canary(
|
29
29
|
Optional[String[0]] $task = undef,
|
30
30
|
Optional[String[0]] $command = undef,
|
31
31
|
Optional[String[0]] $script = undef,
|
32
|
-
TargetSpec $
|
32
|
+
TargetSpec $targets,
|
33
33
|
Hash[String, Data] $params = {},
|
34
34
|
Integer $canary_size = 1
|
35
35
|
) {
|
@@ -51,7 +51,7 @@ plan canary(
|
|
51
51
|
fail_plan("Must specify only one command, script, or task to run", 'canary/invalid-params')
|
52
52
|
}
|
53
53
|
|
54
|
-
[$canaries, $rest] = canary::random_split(get_targets($
|
54
|
+
[$canaries, $rest] = canary::random_split(get_targets($targets), $canary_size)
|
55
55
|
$catch_params = $params + { '_catch_errors' => true }
|
56
56
|
|
57
57
|
if ($task) {
|
@@ -1,8 +1,8 @@
|
|
1
|
-
plan puppetdb_fact(TargetSpec $
|
2
|
-
$
|
3
|
-
$certnames = $
|
1
|
+
plan puppetdb_fact(TargetSpec $targets) {
|
2
|
+
$targs = get_targets($targets)
|
3
|
+
$certnames = $targs.map |$target| { $target.host }
|
4
4
|
$pdb_facts = puppetdb_fact($certnames)
|
5
|
-
$
|
5
|
+
$targs.each |$target| {
|
6
6
|
add_facts($target, $pdb_facts[$target.host])
|
7
7
|
}
|
8
8
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bolt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.43.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-12-
|
11
|
+
date: 2019-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -377,7 +377,9 @@ files:
|
|
377
377
|
- lib/bolt.rb
|
378
378
|
- lib/bolt/analytics.rb
|
379
379
|
- lib/bolt/applicator.rb
|
380
|
+
- lib/bolt/apply_inventory.rb
|
380
381
|
- lib/bolt/apply_result.rb
|
382
|
+
- lib/bolt/apply_target.rb
|
381
383
|
- lib/bolt/bolt_option_parser.rb
|
382
384
|
- lib/bolt/boltdir.rb
|
383
385
|
- lib/bolt/catalog.rb
|
@@ -495,8 +497,10 @@ files:
|
|
495
497
|
- libexec/query_resources.rb
|
496
498
|
- modules/aggregate/lib/puppet/functions/aggregate/count.rb
|
497
499
|
- modules/aggregate/lib/puppet/functions/aggregate/nodes.rb
|
500
|
+
- modules/aggregate/lib/puppet/functions/aggregate/targets.rb
|
498
501
|
- modules/aggregate/plans/count.pp
|
499
502
|
- modules/aggregate/plans/nodes.pp
|
503
|
+
- modules/aggregate/plans/targets.pp
|
500
504
|
- modules/canary/lib/puppet/functions/canary/merge.rb
|
501
505
|
- modules/canary/lib/puppet/functions/canary/random_split.rb
|
502
506
|
- modules/canary/lib/puppet/functions/canary/skip.rb
|
@@ -521,7 +525,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
521
525
|
- !ruby/object:Gem::Version
|
522
526
|
version: '0'
|
523
527
|
requirements: []
|
524
|
-
rubygems_version: 3.0.
|
528
|
+
rubygems_version: 3.0.6
|
525
529
|
signing_key:
|
526
530
|
specification_version: 4
|
527
531
|
summary: Execute commands remotely over SSH and WinRM
|