-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathrun_task_with.rb
More file actions
211 lines (183 loc) · 8.63 KB
/
run_task_with.rb
File metadata and controls
211 lines (183 loc) · 8.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# frozen_string_literal: true
require 'bolt/error'
require 'bolt/pal'
require 'bolt/task'
# Runs a given instance of a `Task` with target-specific parameters on the given set of targets and
# returns the result from each. This function differs from {run_task} by accepting a block that returns
# a `Hash` of target-specific parameters that are passed to the task. This can be used to send parameters
# based on a target's attributes, such as its `facts`, or to use conditional logic to determine the
# parameters a task should receive for a specific target.
#
# This function does nothing if the list of targets is empty.
#
# > **Note:** Not available in apply block
#
# > **Note:** Not available to targets using the pcp transport
Puppet::Functions.create_function(:run_task_with) do
# Run a task with target-specific parameters.
# @param task_name The task to run.
# @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
# @param options A hash of additional options.
# @option options [Boolean] _catch_errors Whether to catch raised errors.
# @option options [Boolean] _noop Run the task in noop mode if available.
# @option options [String] _run_as User to run as using privilege escalation.
# @param block A block that returns a `Hash` of target-specific parameters for the task.
# @return A list of results, one entry per target.
# @example Run a task with target-specific parameters as root
# run_task_with('my_task', $targets, '_run_as' => 'root') |$t| {
# { 'param1' => $t.vars['var1'],
# 'param2' => $t.vars['var2'] }
# }
dispatch :run_task_with do
param 'String[1]', :task_name
param 'Boltlib::TargetSpec', :targets
optional_param 'Hash[String[1], Any]', :options
required_block_param 'Callable[Target]', :block
return_type 'ResultSet'
end
# Run a task with target-specific parameters, logging the provided description.
# @param task_name The task to run.
# @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
# @param description A description to be output when calling this function.
# @param options A hash of additional options.
# @option options [Boolean] _catch_errors Whether to catch raised errors.
# @option options [Boolean] _noop Run the task in noop mode if available.
# @option options [String] _run_as User to run as using privilege escalation.
# @param block A block that returns a `Hash` of target-specific parameters for the task.
# @return A list of results, one entry per target.
# @example Run a task with target-specific parameters and a description
# run_task_with('my_task', $targets, 'Update system packages') |$t| {
# { 'param1' => $t.vars['var1'],
# 'param2' => $t.vars['var2'] }
# }
dispatch :run_task_with_with_description do
param 'String[1]', :task_name
param 'Boltlib::TargetSpec', :targets
param 'Optional[String]', :description
optional_param 'Hash[String[1], Any]', :options
required_block_param 'Callable[Target]', :block
return_type 'ResultSet'
end
def run_task_with(task_name, targets, options = {}, &block)
run_task_with_with_description(task_name, targets, nil, options, &block)
end
def run_task_with_with_description(task_name, targets, description, options = {})
unless Puppet[:tasks]
raise Puppet::ParseErrorWithIssue
.from_issue_and_stack(
Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
action: 'run_task_with'
)
end
executor = Puppet.lookup(:bolt_executor)
inventory = Puppet.lookup(:bolt_inventory)
error_set = []
# Report to analytics
executor.report_function_call(self.class.name)
executor.report_bundled_content('Task', task_name)
# Keep valid metaparameters, discarding everything else
options = options.select { |k, _v| k.start_with?('_') }
.transform_keys { |k| k.sub(/^_/, '').to_sym }
options[:description] = description if description
# Get all the targets
targets = Array(inventory.get_targets(targets))
# Return early if there are no targets.
if targets.empty?
return Bolt::ResultSet.new([])
end
# If all targets use the 'pcp' transport, use a fake task instead of loading the local definition
# Otherwise, load the local task definition
if (pcp_only = targets.all? { |t| t.transport == 'pcp' })
task = Bolt::Task.new(task_name,
{ 'supports_noop' => true },
[{ 'name' => '', 'path' => '' }])
else
task_signature = Puppet::Pal::ScriptCompiler.new(closure_scope.compiler).task_signature(task_name)
if task_signature.nil?
raise Bolt::Error.unknown_task(task_name)
end
task = Bolt::Task.from_task_signature(task_signature)
end
# Map the targets to their specific parameters and merge with the defaults
target_mapping = targets.each_with_object({}) do |target, mapping|
params = yield(target)
# Parameters returned from the block should be a Hash. If they're not, create a failing
# Result for the target that will later be added to the ResultSet.
unless params.is_a?(Hash)
exception = with_stack(
:TYPE_MISMATCH,
"Block must return a Hash of parameters, received #{params.class}"
)
error_set << Bolt::Result.from_exception(target, exception, action: 'task')
next
end
# If parameters are mismatched, create a failing result for the target that will later
# be added to the ResultSet.
unless pcp_only
# Set the default value for any params that have one and were not provided or are undef
params = task.parameter_defaults.merge(params) do |_, default, passed|
passed.nil? ? default : passed
end
type_match = task_signature.runnable_with?(params) do |mismatch_message|
exception = with_stack(:TYPE_MISMATCH, mismatch_message)
error_set << Bolt::Result.from_exception(target, exception, action: 'task')
end
next unless type_match
end
# If there is a type mismatch between the type Data and the type of the params, create
# a failing result for the target that will later be added to the ResultSet.
unless Puppet::Pops::Types::TypeFactory.data.instance?(params)
params_t = Puppet::Pops::Types::TypeCalculator.infer_set(params)
desc = Puppet::Pops::Types::TypeMismatchDescriber.singleton.describe_mismatch(
'Task parameters are not of type Data. run_task_with()',
Puppet::Pops::Types::TypeFactory.data, params_t
)
exception = with_stack(:TYPE_NOT_DATA, desc)
error_set << Bolt::Result.from_exception(target, exception, action: 'task')
next
end
# Wrap parameters marked with '"sensitive": true' in the task metadata with a
# Sensitive wrapper type. This way it's not shown in logs.
if (param_spec = task.parameters)
params.each do |k, v|
if param_spec[k] && param_spec[k]['sensitive']
params[k] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(v)
end
end
end
# Set the default value for any params that have one and were not provided or are undef
mapping[target] = task.parameter_defaults.merge(params) do |_, default, passed|
passed.nil? ? default : passed
end
end
# Add a noop parameter if the function was called with the noop metaparameter.
if options[:noop]
if task.supports_noop
target_mapping.each_value { |params| params['_noop'] = true }
else
raise with_stack(:TASK_NO_NOOP, 'Task does not support noop')
end
end
# Report whether the task was run in noop mode.
executor.report_noop_mode(executor.noop || options[:noop])
# Combine the results from the task run with any failing results that were
# generated earlier when creating the target mapping
file_line = Puppet::Pops::PuppetStack.top_of_stack
task_result = if executor.in_parallel?
executor.run_in_thread do
executor.run_task_with(target_mapping, task, options, file_line)
end
else
executor.run_task_with(target_mapping, task, options, file_line)
end
result = Bolt::ResultSet.new(task_result.results + error_set)
if !result.ok && !options[:catch_errors]
raise Bolt::RunFailure.new(result, 'run_task', task_name)
end
result
end
def with_stack(kind, msg)
issue = Puppet::Pops::Issues.issue(kind) { msg }
Puppet::ParseErrorWithIssue.from_issue_and_stack(issue)
end
end