bolt 1.3.0 → 1.4.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/functions/add_to_group.rb +36 -0
- data/lib/bolt/analytics.rb +10 -4
- data/lib/bolt/applicator.rb +24 -5
- data/lib/bolt/executor.rb +17 -3
- data/lib/bolt/inventory.rb +48 -0
- data/lib/bolt/inventory/group.rb +1 -1
- data/lib/bolt/pal.rb +1 -2
- data/lib/bolt/plan_result.rb +4 -0
- data/lib/bolt/transport/ssh.rb +1 -4
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_spec/plans.rb +77 -19
- data/lib/bolt_spec/plans/action_stubs.rb +164 -0
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +43 -0
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +50 -0
- data/lib/bolt_spec/plans/action_stubs/task_stub.rb +52 -0
- data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +64 -0
- data/lib/bolt_spec/plans/mock_executor.rb +92 -168
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8afdc9d66e35557fd6dee9433d0c5dd62f9620fb111a7b56d3ba11e7a0b66e73
|
4
|
+
data.tar.gz: d2ea3502f583cfa8cd136f8e04eaebac50364c822f892595eb0782c8e6c6f175
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c15b22e1ab464ba249372b62161da08c69e31db4c55ff2a89b590fde7abccde114dc0559e1b3b32eae3fcae69ef09afd1f142fe89e1976d682247f4e1e16bdc
|
7
|
+
data.tar.gz: d1b82c13cb0faf8b80e5b37bf6800a24a7837e4ed572690257163fcf95da6dcd2a14f25a0f23e7fd33c7e797683e00eda3f0f2933a319785ce92a40f5bdfcc01
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/error'
|
4
|
+
|
5
|
+
# Adds a target to specified inventory group.
|
6
|
+
Puppet::Functions.create_function(:add_to_group) do
|
7
|
+
# @param targets A pattern or array of patterns identifying a set of targets.
|
8
|
+
# @param group The name of the group to add targets to.
|
9
|
+
# @example Add new Target to group.
|
10
|
+
# Target.new('foo@example.com', 'password' => 'secret').add_to_group('group1')
|
11
|
+
# @example Add new target to group by name.
|
12
|
+
# add_to_group('bolt:bolt@web.com', 'group1')
|
13
|
+
# @example Add an array of targets to group by name.
|
14
|
+
# add_to_group(['host1', 'group1', 'winrm://host2:54321'], 'group1')
|
15
|
+
# @example Add a comma separated list list of targets to group by name.
|
16
|
+
# add_to_group('foo,bar,baz', 'group1')
|
17
|
+
dispatch :add_to_group do
|
18
|
+
param 'Boltlib::TargetSpec', :targets
|
19
|
+
param 'String[1]', :group
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_to_group(targets, group)
|
23
|
+
inventory = Puppet.lookup(:bolt_inventory) { nil }
|
24
|
+
|
25
|
+
unless inventory && Puppet.features.bolt?
|
26
|
+
raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
|
27
|
+
Puppet::Pops::Issues::TASK_MISSING_BOLT, action: _('process targets through inventory')
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
executor = Puppet.lookup(:bolt_executor) { nil }
|
32
|
+
executor&.report_function_call('add_to_group')
|
33
|
+
|
34
|
+
inventory.add_to_group(inventory.get_targets(targets), group)
|
35
|
+
end
|
36
|
+
end
|
data/lib/bolt/analytics.rb
CHANGED
@@ -19,7 +19,9 @@ module Bolt
|
|
19
19
|
inventory_nodes: :cd2,
|
20
20
|
inventory_groups: :cd3,
|
21
21
|
target_nodes: :cd4,
|
22
|
-
output_format: :cd5
|
22
|
+
output_format: :cd5,
|
23
|
+
statement_count: :cd6,
|
24
|
+
resource_mean: :cd7
|
23
25
|
}.freeze
|
24
26
|
|
25
27
|
def self.build_client
|
@@ -82,7 +84,11 @@ module Bolt
|
|
82
84
|
submit(base_params.merge(screen_view_params))
|
83
85
|
end
|
84
86
|
|
85
|
-
def event(category, action, label
|
87
|
+
def event(category, action, label: nil, value: nil, **kwargs)
|
88
|
+
custom_dimensions = Bolt::Util.walk_keys(kwargs) do |k|
|
89
|
+
CUSTOM_DIMENSIONS[k] || raise("Unknown analytics key '#{k}'")
|
90
|
+
end
|
91
|
+
|
86
92
|
event_params = {
|
87
93
|
# Type
|
88
94
|
t: 'event',
|
@@ -90,7 +96,7 @@ module Bolt
|
|
90
96
|
ec: category,
|
91
97
|
# Event Action
|
92
98
|
ea: action
|
93
|
-
}
|
99
|
+
}.merge(custom_dimensions)
|
94
100
|
|
95
101
|
# Event Label
|
96
102
|
event_params[:el] = label if label
|
@@ -160,7 +166,7 @@ module Bolt
|
|
160
166
|
@logger.debug "Skipping submission of '#{screen}' screenview because analytics is disabled"
|
161
167
|
end
|
162
168
|
|
163
|
-
def event(category, action,
|
169
|
+
def event(category, action, **_kwargs)
|
164
170
|
@logger.debug "Skipping submission of '#{category} #{action}' event because analytics is disabled"
|
165
171
|
end
|
166
172
|
|
data/lib/bolt/applicator.rb
CHANGED
@@ -134,12 +134,24 @@ module Bolt
|
|
134
134
|
|
135
135
|
targets = @inventory.get_targets(args[0])
|
136
136
|
|
137
|
-
|
137
|
+
apply_ast(apply_body, targets, options, plan_vars)
|
138
|
+
end
|
138
139
|
|
139
|
-
|
140
|
+
# Count the number of top-level statements in the AST.
|
141
|
+
def count_statements(ast)
|
142
|
+
case ast
|
143
|
+
when Puppet::Pops::Model::Program
|
144
|
+
count_statements(ast.body)
|
145
|
+
when Puppet::Pops::Model::BlockExpression
|
146
|
+
ast.statements.count
|
147
|
+
else
|
148
|
+
1
|
149
|
+
end
|
140
150
|
end
|
141
151
|
|
142
|
-
def apply_ast(
|
152
|
+
def apply_ast(raw_ast, targets, options, plan_vars = {})
|
153
|
+
ast = Puppet::Pops::Serialization::ToDataConverter.convert(raw_ast, rich_data: true, symbol_to_string: true)
|
154
|
+
|
143
155
|
notify = proc { |_| nil }
|
144
156
|
|
145
157
|
r = @executor.log_action('apply catalog', targets) do
|
@@ -154,12 +166,15 @@ module Bolt
|
|
154
166
|
result_promises = targets.zip(futures).flat_map do |target, future|
|
155
167
|
@executor.queue_execute([target]) do |transport, batch|
|
156
168
|
@executor.with_node_logging("Applying manifest block", batch) do
|
169
|
+
catalog = future.value
|
170
|
+
raise future.reason if future.rejected?
|
171
|
+
|
157
172
|
arguments = {
|
158
|
-
'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(
|
173
|
+
'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(catalog),
|
159
174
|
'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins),
|
160
175
|
'_noop' => options['_noop']
|
161
176
|
}
|
162
|
-
|
177
|
+
|
163
178
|
results = transport.batch_task(batch, catalog_apply_task, arguments, options, ¬ify)
|
164
179
|
Array(results).map { |result| ApplyResult.from_task_result(result) }
|
165
180
|
end
|
@@ -169,6 +184,10 @@ module Bolt
|
|
169
184
|
@executor.await_results(result_promises)
|
170
185
|
end
|
171
186
|
|
187
|
+
# Allow for report to exclude event metrics (apply_result doesn't require it to be present)
|
188
|
+
resource_counts = r.ok_set.map { |result| result.event_metrics&.fetch('total') }.compact
|
189
|
+
@executor.report_apply(count_statements(raw_ast), resource_counts)
|
190
|
+
|
172
191
|
if !r.ok && !options['_catch_errors']
|
173
192
|
raise Bolt::ApplyFailure, r
|
174
193
|
end
|
data/lib/bolt/executor.rb
CHANGED
@@ -165,18 +165,32 @@ module Bolt
|
|
165
165
|
|
166
166
|
def report_transport(transport, count)
|
167
167
|
name = transport.class.name.split('::').last.downcase
|
168
|
-
|
168
|
+
unless @reported_transports.include?(name)
|
169
|
+
@analytics&.event('Transport', 'initialize', label: name, value: count)
|
170
|
+
end
|
169
171
|
@reported_transports.add(name)
|
170
172
|
end
|
171
173
|
|
172
174
|
def report_function_call(function)
|
173
|
-
@analytics&.event('Plan', 'call_function', function)
|
175
|
+
@analytics&.event('Plan', 'call_function', label: function)
|
174
176
|
end
|
175
177
|
|
176
178
|
def report_bundled_content(mode, name)
|
177
179
|
if @bundled_content&.include?(name)
|
178
|
-
@analytics&.event('Bundled Content', mode, name)
|
180
|
+
@analytics&.event('Bundled Content', mode, label: name)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def report_apply(statement_count, resource_counts)
|
185
|
+
data = { statement_count: statement_count }
|
186
|
+
|
187
|
+
unless resource_counts.empty?
|
188
|
+
sum = resource_counts.inject(0) { |accum, i| accum + i }
|
189
|
+
# Intentionally rounded to an integer. High precision isn't useful.
|
190
|
+
data[:resource_mean] = sum / resource_counts.length
|
179
191
|
end
|
192
|
+
|
193
|
+
@analytics&.event('Apply', 'ast', data)
|
180
194
|
end
|
181
195
|
|
182
196
|
def with_node_logging(description, batch)
|
data/lib/bolt/inventory.rb
CHANGED
@@ -93,6 +93,19 @@ module Bolt
|
|
93
93
|
targets.map { |t| update_target(t) }
|
94
94
|
end
|
95
95
|
|
96
|
+
def add_to_group(targets, desired_group)
|
97
|
+
if group_names.include?(desired_group)
|
98
|
+
targets.each do |target|
|
99
|
+
if group_names.include?(target.name)
|
100
|
+
raise ValidationError.new("Group #{target.name} conflicts with node of the same name", target.name)
|
101
|
+
end
|
102
|
+
add_node(@groups, target, desired_group)
|
103
|
+
end
|
104
|
+
else
|
105
|
+
raise ValidationError.new("Group #{desired_group} does not exist in inventory", nil)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
96
109
|
def set_var(target, key, value)
|
97
110
|
data = { key => value }
|
98
111
|
set_vars_from_hash(target.name, data)
|
@@ -243,5 +256,40 @@ module Bolt
|
|
243
256
|
end
|
244
257
|
end
|
245
258
|
private :set_facts
|
259
|
+
|
260
|
+
def add_node(current_group, target, desired_group, track = { 'all' => nil })
|
261
|
+
if current_group.name == desired_group
|
262
|
+
# Group to add to is found
|
263
|
+
t_name = target.name
|
264
|
+
# Add target to nodes hash
|
265
|
+
current_group.nodes[t_name] = { 'name' => t_name }.merge(target.options)
|
266
|
+
# Inherit facts, vars, and features from hierarchy
|
267
|
+
current_group_data = { facts: current_group.facts, vars: current_group.vars, features: current_group.features }
|
268
|
+
data = inherit_data(track, current_group.name, current_group_data)
|
269
|
+
set_facts(t_name, @target_facts[t_name] ? data[:facts].merge(@target_facts[t_name]) : data[:facts])
|
270
|
+
set_vars_from_hash(t_name, @target_vars[t_name] ? data[:vars].merge(@target_vars[t_name]) : data[:vars])
|
271
|
+
data[:features].each do |feature|
|
272
|
+
set_feature(target, feature)
|
273
|
+
end
|
274
|
+
return true
|
275
|
+
end
|
276
|
+
# Recurse on children Groups if not desired_group
|
277
|
+
current_group.groups.each do |child_group|
|
278
|
+
track[child_group.name] = current_group
|
279
|
+
add_node(child_group, target, desired_group, track)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
private :add_node
|
283
|
+
|
284
|
+
def inherit_data(track, name, data)
|
285
|
+
unless track[name].nil?
|
286
|
+
data[:facts] = track[name].facts.merge(data[:facts])
|
287
|
+
data[:vars] = track[name].vars.merge(data[:vars])
|
288
|
+
data[:features].concat(track[name].features)
|
289
|
+
inherit_data(track, track[name].name, data)
|
290
|
+
end
|
291
|
+
data
|
292
|
+
end
|
293
|
+
private :inherit_data
|
246
294
|
end
|
247
295
|
end
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -5,7 +5,7 @@ module Bolt
|
|
5
5
|
# Group is a specific implementation of Inventory based on nested
|
6
6
|
# structured data.
|
7
7
|
class Group
|
8
|
-
attr_accessor :name, :nodes, :groups, :config, :rest
|
8
|
+
attr_accessor :name, :nodes, :groups, :config, :rest, :facts, :vars, :features
|
9
9
|
|
10
10
|
def initialize(data)
|
11
11
|
@logger = Logging.logger[self]
|
data/lib/bolt/pal.rb
CHANGED
@@ -180,8 +180,7 @@ module Bolt
|
|
180
180
|
# Parses a snippet of Puppet manifest code and returns the AST represented
|
181
181
|
# in JSON.
|
182
182
|
def parse_manifest(code, filename)
|
183
|
-
|
184
|
-
Puppet::Pops::Serialization::ToDataConverter.convert(raw_ast, rich_data: true, symbol_to_string: true)
|
183
|
+
Puppet::Pops::Parser::EvaluatingParser.new.parse_string(code, filename)
|
185
184
|
rescue Puppet::Error => e
|
186
185
|
raise Bolt::PAL::PALError, "Failed to parse manifest: #{e}"
|
187
186
|
end
|
data/lib/bolt/plan_result.rb
CHANGED
data/lib/bolt/transport/ssh.rb
CHANGED
@@ -54,10 +54,7 @@ module Bolt
|
|
54
54
|
begin
|
55
55
|
require 'net/ssh/krb'
|
56
56
|
rescue LoadError
|
57
|
-
logger.debug
|
58
|
-
"Authentication method 'gssapi-with-mic' is not available. "\
|
59
|
-
"Please install the kerberos gem with `gem install net-ssh-krb`"
|
60
|
-
}
|
57
|
+
logger.debug("Authentication method 'gssapi-with-mic' (Kerberos) is not available.")
|
61
58
|
end
|
62
59
|
|
63
60
|
@transport_logger = Logging.logger[Net::SSH]
|
data/lib/bolt/version.rb
CHANGED
data/lib/bolt_spec/plans.rb
CHANGED
@@ -38,7 +38,6 @@ require 'bolt/pal'
|
|
38
38
|
#
|
39
39
|
#
|
40
40
|
# TODO:
|
41
|
-
# - allow stubbing for commands, scripts and file uploads
|
42
41
|
# - Allow description based stub matching
|
43
42
|
# - Better testing of plan errors
|
44
43
|
# - Better error collection around call counts. Show what stubs exists and more than a single failure
|
@@ -52,6 +51,36 @@ require 'bolt/pal'
|
|
52
51
|
# - resultset matchers to help testing canary like plans?
|
53
52
|
# - inventory matchers to help testing plans that change inventory
|
54
53
|
#
|
54
|
+
# Stubs:
|
55
|
+
# - allow_command(cmd), expect_command(cmd): expect the exact command
|
56
|
+
# - allow_script(script), expect_script(script): expect the script as <module>/path/to/file
|
57
|
+
# - allow_task(task), expect_task(task): expect the named task
|
58
|
+
# - allow_upload(file), expect_upload(file): expect the identified source file
|
59
|
+
# - allow_apply_prep: allows `apply_prep` to be invoked in the plan but does not allow modifiers
|
60
|
+
# - allow_apply: allows `apply` to be invoked in the plan but does not allow modifiers
|
61
|
+
#
|
62
|
+
# Stub modifiers:
|
63
|
+
# - be_called_times(n): if allowed, fail if the action is called more than 'n' times
|
64
|
+
# if expected, fail unless the action is called 'n' times
|
65
|
+
# - not_be_called: fail if the action is called
|
66
|
+
# - with_targets(targets): target or list of targets that you expect to be passed to the action
|
67
|
+
# - with_params(params): list of params and metaparams (or options) that you expect to be passed to the action.
|
68
|
+
# Corresponds to the action's last argument.
|
69
|
+
# - with_destination(dest): for upload_file, the expected destination path
|
70
|
+
# - always_return(value): return a Bolt::ResultSet of Bolt::Result objects with the specified value Hash
|
71
|
+
# command and script: only accept 'stdout' and 'stderr' keys
|
72
|
+
# upload: does not support this modifier
|
73
|
+
# - return_for_targets(targets_to_values): return a Bolt::ResultSet of Bolt::Result objects from the Hash mapping
|
74
|
+
# targets to their value Hashes
|
75
|
+
# command and script: only accept 'stdout' and 'stderr' keys
|
76
|
+
# upload: does not support this modifier
|
77
|
+
# - return(&block): invoke the block to construct a Bolt::ResultSet. The blocks parameters differ based on action
|
78
|
+
# command: `{ |targets:, command:, params:| ... }`
|
79
|
+
# script: `{ |targets:, script:, params:| ... }`
|
80
|
+
# task: `{ |targets:, task:, params:| ... }`
|
81
|
+
# upload: `{ |targets:, source:, destination:, params:| ... }`
|
82
|
+
# - error_with(err): return a failing Bolt::ResultSet, with Bolt::Result objects with the identified err hash
|
83
|
+
#
|
55
84
|
# Example:
|
56
85
|
# describe "my_plan" do
|
57
86
|
# it 'should return' do
|
@@ -87,8 +116,16 @@ require 'bolt/pal'
|
|
87
116
|
# 'node2' => {'result_key' => 6} })
|
88
117
|
# expect(run_plan('my_plan', { 'param1' => 10 })).to eq(13)
|
89
118
|
# end
|
119
|
+
#
|
120
|
+
# it 'should construct a custom return value' do
|
121
|
+
# expect_task('my_task').return do |targets:, task:, params:|
|
122
|
+
# Bolt::ResultSet.new(targets.map { |targ| Bolt::Result.new(targ, {'result_key' => 10'})})
|
123
|
+
# end
|
124
|
+
# expect(run_plan('my_plan', { 'param1' => 10 })).to eq(10)
|
125
|
+
# end
|
90
126
|
# end
|
91
127
|
#
|
128
|
+
# See spec/bolt_spec/plan_spec.rb for more examples.
|
92
129
|
module BoltSpec
|
93
130
|
module Plans
|
94
131
|
def self.init
|
@@ -110,9 +147,11 @@ module BoltSpec
|
|
110
147
|
|
111
148
|
# Override in your tests
|
112
149
|
def config
|
113
|
-
config
|
114
|
-
|
115
|
-
|
150
|
+
@config ||= begin
|
151
|
+
conf = Bolt::Config.new(Bolt::Boltdir.new('.'), {})
|
152
|
+
conf.modulepath = [modulepath].flatten
|
153
|
+
conf
|
154
|
+
end
|
116
155
|
end
|
117
156
|
|
118
157
|
# Override in your tests
|
@@ -120,8 +159,11 @@ module BoltSpec
|
|
120
159
|
@inventory ||= Bolt::Inventory.new({})
|
121
160
|
end
|
122
161
|
|
162
|
+
# Provided as a class so expectations can be placed on it.
|
163
|
+
class MockPuppetDBClient; end
|
164
|
+
|
123
165
|
def puppetdb_client
|
124
|
-
@puppetdb_client ||=
|
166
|
+
@puppetdb_client ||= MockPuppetDBClient.new
|
125
167
|
end
|
126
168
|
|
127
169
|
def run_plan(name, params)
|
@@ -132,30 +174,46 @@ module BoltSpec
|
|
132
174
|
raise executor.error_message
|
133
175
|
end
|
134
176
|
|
135
|
-
|
177
|
+
begin
|
178
|
+
executor.assert_call_expectations
|
179
|
+
rescue StandardError => e
|
180
|
+
raise "#{e.message}\nPlan result: #{result}"
|
181
|
+
end
|
136
182
|
|
137
183
|
result
|
138
184
|
end
|
139
185
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
186
|
+
MOCKED_ACTIONS.each do |action|
|
187
|
+
# Allowed action stubs can be called up to be_called_times number of times
|
188
|
+
define_method :"allow_#{action}" do |object|
|
189
|
+
executor.send(:"stub_#{action}", object).add_stub
|
190
|
+
end
|
191
|
+
|
192
|
+
# Expected action stubs must be called exactly the expected number of times
|
193
|
+
# or at least once without be_called_times
|
194
|
+
define_method :"expect_#{action}" do |object|
|
195
|
+
send(:"allow_#{action}", object).expect_call
|
196
|
+
end
|
197
|
+
|
198
|
+
# This stub will catch any action call if there are no stubs specifically for that task
|
199
|
+
define_method :"allow_any_#{action}" do
|
200
|
+
executor.send(:"stub_#{action}", :default).add_stub
|
201
|
+
end
|
144
202
|
end
|
145
203
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
204
|
+
def allow_apply_prep
|
205
|
+
allow_task('puppet_agent::version').always_return('version' => '6.0')
|
206
|
+
allow_task('apply_helpers::custom_facts')
|
207
|
+
nil
|
150
208
|
end
|
151
209
|
|
152
|
-
|
153
|
-
|
154
|
-
|
210
|
+
def allow_apply
|
211
|
+
executor.stub_apply
|
212
|
+
nil
|
155
213
|
end
|
156
214
|
|
157
215
|
# Example helpers to mock other run functions
|
158
|
-
# The with_targets method
|
216
|
+
# The with_targets method makes sense for all stubs
|
159
217
|
# with_params could be reused for options
|
160
218
|
# They probably need special stub methods for other arguments through
|
161
219
|
|
@@ -180,7 +238,7 @@ module BoltSpec
|
|
180
238
|
|
181
239
|
# intended to be private below here
|
182
240
|
def executor
|
183
|
-
@executor ||= BoltSpec::Plans::MockExecutor.new
|
241
|
+
@executor ||= BoltSpec::Plans::MockExecutor.new(modulepath)
|
184
242
|
end
|
185
243
|
end
|
186
244
|
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/result'
|
4
|
+
require 'bolt/util'
|
5
|
+
|
6
|
+
module BoltSpec
|
7
|
+
module Plans
|
8
|
+
# Nothing in the ActionDouble is 'public'
|
9
|
+
class ActionDouble
|
10
|
+
def initialize(action_stub)
|
11
|
+
@stubs = []
|
12
|
+
@action_stub = action_stub
|
13
|
+
end
|
14
|
+
|
15
|
+
def process(*args)
|
16
|
+
matches = @stubs.select { |s| s.matches(*args) }
|
17
|
+
unless matches.empty?
|
18
|
+
matches[0].call(*args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def assert_called(object)
|
23
|
+
@stubs.each { |s| s.assert_called(object) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_stub
|
27
|
+
stub = Plans.const_get(@action_stub).new
|
28
|
+
@stubs.unshift stub
|
29
|
+
stub
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ActionStub
|
34
|
+
attr_reader :invocation
|
35
|
+
|
36
|
+
def initialize(expect = false)
|
37
|
+
@calls = 0
|
38
|
+
@expect = expect
|
39
|
+
@expected_calls = 1
|
40
|
+
# invocation spec
|
41
|
+
@invocation = {}
|
42
|
+
# return value
|
43
|
+
@data = { default: {} }
|
44
|
+
end
|
45
|
+
|
46
|
+
def assert_called(object)
|
47
|
+
satisfied = if @expect
|
48
|
+
(@expected_calls.nil? && @calls > 0) || @calls == @expected_calls
|
49
|
+
else
|
50
|
+
@expected_calls.nil? || @calls <= @expected_calls
|
51
|
+
end
|
52
|
+
unless satisfied
|
53
|
+
unless (times = @expected_calls)
|
54
|
+
times = @expect ? "at least one" : "any number of"
|
55
|
+
end
|
56
|
+
message = "Expected #{object} to be called #{times} times"
|
57
|
+
message += " with targets #{@invocation[:targets]}" if @invocation[:targets]
|
58
|
+
message += " with parameters #{parameters}" if parameters
|
59
|
+
raise message
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# This changes the stub from an allow to an expect which will validate
|
64
|
+
# that it has been called.
|
65
|
+
def expect_call
|
66
|
+
@expect = true
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Used to create a valid Bolt::Result object from result data.
|
71
|
+
def default_for(target)
|
72
|
+
case @data[:default]
|
73
|
+
when Bolt::Error
|
74
|
+
Bolt::Result.from_exception(target, @data[:default])
|
75
|
+
when Hash
|
76
|
+
result_for(target, Bolt::Util.walk_keys(@data[:default], &:to_sym))
|
77
|
+
else
|
78
|
+
raise 'Default result must be a Hash'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def check_resultset(result_set, object)
|
83
|
+
unless result_set.is_a?(Bolt::ResultSet)
|
84
|
+
raise "Return block for #{object} did not return a Bolt::ResultSet"
|
85
|
+
end
|
86
|
+
result_set
|
87
|
+
end
|
88
|
+
|
89
|
+
# Below here are the intended 'public' methods of the stub
|
90
|
+
|
91
|
+
# Restricts the stub to only match invocations with
|
92
|
+
# the correct targets
|
93
|
+
def with_targets(targets)
|
94
|
+
targets = [targets] unless targets.is_a? Array
|
95
|
+
@invocation[:targets] = targets.map do |target|
|
96
|
+
if target.is_a? String
|
97
|
+
target
|
98
|
+
else
|
99
|
+
target.name
|
100
|
+
end
|
101
|
+
end
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
# limit the maximum number of times an allow stub may be called or
|
106
|
+
# specify how many times an expect stub must be called.
|
107
|
+
def be_called_times(times)
|
108
|
+
@expected_calls = times
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# error if the stub is called at all.
|
113
|
+
def not_be_called
|
114
|
+
@expected_calls = 0
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
def return(&block)
|
119
|
+
raise "Cannot set return values and return block." if @data_set
|
120
|
+
@return_block = block
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
# Set different result values for each target. May use string or symbol keys, but allowed key names
|
125
|
+
# are restricted based on action.
|
126
|
+
def return_for_targets(data)
|
127
|
+
data.each_with_object(@data) do |(target, result), hsh|
|
128
|
+
raise "Mocked results must be hashes: #{target}: #{result}" unless result.is_a? Hash
|
129
|
+
hsh[target] = result_for(Bolt::Target.new(target), Bolt::Util.walk_keys(result, &:to_sym))
|
130
|
+
end
|
131
|
+
raise "Cannot set return values and return block." if @return_block
|
132
|
+
@data_set = true
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
# Set a default return value for all targets, specific targets may be overridden with return_for_targets.
|
137
|
+
# Follows the same rules for data as return_for_targets.
|
138
|
+
def always_return(data)
|
139
|
+
@data[:default] = data
|
140
|
+
@data_set = true
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
# Set a default error result for all targets.
|
145
|
+
def error_with(data)
|
146
|
+
data = Bolt::Util.walk_keys(data, &:to_s)
|
147
|
+
if data['msg'] && data['kind'] && (data.keys - %w[msg kind details issue_code]).empty?
|
148
|
+
@data[:default] = Bolt::Error.new(data['msg'], data['kind'], data['details'], data['issue_code'])
|
149
|
+
else
|
150
|
+
STDERR.puts "In the future 'error_with()' may require msg and kind, and " \
|
151
|
+
"optionally accept only details and issue_code."
|
152
|
+
@data[:default] = data
|
153
|
+
end
|
154
|
+
@data_set = true
|
155
|
+
self
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
require_relative 'action_stubs/command_stub'
|
162
|
+
require_relative 'action_stubs/script_stub'
|
163
|
+
require_relative 'action_stubs/task_stub'
|
164
|
+
require_relative 'action_stubs/upload_stub'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BoltSpec
|
4
|
+
module Plans
|
5
|
+
class CommandStub < ActionStub
|
6
|
+
def matches(targets, _command, options)
|
7
|
+
if @invocation[:targets] && Set.new(@invocation[:targets]) != Set.new(targets.map(&:name))
|
8
|
+
return false
|
9
|
+
end
|
10
|
+
|
11
|
+
if @invocation[:options] && options != @invocation[:options]
|
12
|
+
return false
|
13
|
+
end
|
14
|
+
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(targets, command, options)
|
19
|
+
@calls += 1
|
20
|
+
if @return_block
|
21
|
+
check_resultset(@return_block.call(targets: targets, command: command, params: options), command)
|
22
|
+
else
|
23
|
+
Bolt::ResultSet.new(targets.map { |target| @data[target.name] || default_for(target) })
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def parameters
|
28
|
+
@invocation[:options]
|
29
|
+
end
|
30
|
+
|
31
|
+
def result_for(target, stdout: '', stderr: '')
|
32
|
+
Bolt::Result.for_command(target, stdout, stderr, 0)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public methods
|
36
|
+
|
37
|
+
def with_params(params)
|
38
|
+
@invocation[:options] = params
|
39
|
+
self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BoltSpec
|
4
|
+
module Plans
|
5
|
+
class ScriptStub < ActionStub
|
6
|
+
def matches(targets, _script, arguments, options)
|
7
|
+
if @invocation[:targets] && Set.new(@invocation[:targets]) != Set.new(targets.map(&:name))
|
8
|
+
return false
|
9
|
+
end
|
10
|
+
|
11
|
+
if @invocation[:arguments] && arguments != @invocation[:arguments]
|
12
|
+
return false
|
13
|
+
end
|
14
|
+
|
15
|
+
if @invocation[:options] && options != @invocation[:options]
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(targets, script, arguments, options)
|
23
|
+
@calls += 1
|
24
|
+
if @return_block
|
25
|
+
# Merge arguments and options into params to match puppet function signature.
|
26
|
+
params = options.merge('arguments' => arguments)
|
27
|
+
check_resultset(@return_block.call(targets: targets, script: script, params: params), script)
|
28
|
+
else
|
29
|
+
Bolt::ResultSet.new(targets.map { |target| @data[target.name] || default_for(target) })
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def parameters
|
34
|
+
@invocation[:arguments] + @invocation[:options]
|
35
|
+
end
|
36
|
+
|
37
|
+
def result_for(target, stdout: '', stderr: '')
|
38
|
+
Bolt::Result.for_command(target, stdout, stderr, 0)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public methods
|
42
|
+
|
43
|
+
def with_params(params)
|
44
|
+
@invocation[:arguments] = params['arguments']
|
45
|
+
@invocation[:options] = params.select { |k, _v| k.start_with?('_') }
|
46
|
+
self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BoltSpec
|
4
|
+
module Plans
|
5
|
+
class TaskStub < ActionStub
|
6
|
+
def matches(targets, _task, arguments, options)
|
7
|
+
if @invocation[:targets] && Set.new(@invocation[:targets]) != Set.new(targets.map(&:name))
|
8
|
+
return false
|
9
|
+
end
|
10
|
+
|
11
|
+
if @invocation[:arguments] && arguments != @invocation[:arguments]
|
12
|
+
return false
|
13
|
+
end
|
14
|
+
|
15
|
+
if @invocation[:options] && options != @invocation[:options]
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(targets, task, arguments, options)
|
23
|
+
@calls += 1
|
24
|
+
if @return_block
|
25
|
+
# Merge arguments and options into params to match puppet function signature.
|
26
|
+
check_resultset(@return_block.call(targets: targets, task: task, params: arguments.merge(options)), task)
|
27
|
+
else
|
28
|
+
Bolt::ResultSet.new(targets.map { |target| @data[target.name] || default_for(target) })
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def parameters
|
33
|
+
@invocation[:arguments] + @invocation[:options]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Allow any data.
|
37
|
+
def result_for(target, data)
|
38
|
+
Bolt::Result.new(target, value: Bolt::Util.walk_keys(data, &:to_s))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public methods
|
42
|
+
|
43
|
+
# Restricts the stub to only match invocations with certain parameters.
|
44
|
+
# All parameters must match exactly.
|
45
|
+
def with_params(params)
|
46
|
+
@invocation[:arguments] = params.reject { |k, _v| k.start_with?('_') }
|
47
|
+
@invocation[:options] = params.select { |k, _v| k.start_with?('_') }
|
48
|
+
self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BoltSpec
|
4
|
+
module Plans
|
5
|
+
class UploadStub < ActionStub
|
6
|
+
def matches(targets, _source, destination, options)
|
7
|
+
if @invocation[:targets] && Set.new(@invocation[:targets]) != Set.new(targets.map(&:name))
|
8
|
+
return false
|
9
|
+
end
|
10
|
+
|
11
|
+
if @invocation[:destination] && destination != @invocation[:destination]
|
12
|
+
return false
|
13
|
+
end
|
14
|
+
|
15
|
+
if @invocation[:options] && options != @invocation[:options]
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(targets, source, destination, options)
|
23
|
+
@calls += 1
|
24
|
+
if @return_block
|
25
|
+
results = @return_block.call(targets: targets, source: source, destination: destination, params: options)
|
26
|
+
check_resultset(results, source)
|
27
|
+
else
|
28
|
+
results = targets.map do |target|
|
29
|
+
if @data[:default].is_a?(Bolt::Error)
|
30
|
+
default_for(target)
|
31
|
+
else
|
32
|
+
Bolt::Result.for_upload(target, source, destination)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
Bolt::ResultSet.new(results)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parameters
|
40
|
+
@invocation[:options]
|
41
|
+
end
|
42
|
+
|
43
|
+
def result_for(_target, _data)
|
44
|
+
raise 'Upload result cannot be changed'
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public methods
|
48
|
+
|
49
|
+
def always_return(_data)
|
50
|
+
raise 'Upload result cannot be changed'
|
51
|
+
end
|
52
|
+
|
53
|
+
def with_destination(destination)
|
54
|
+
@invocation[:destination] = destination
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_params(params)
|
59
|
+
@invocation[:options] = params
|
60
|
+
self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,191 +1,71 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bolt_spec/plans/action_stubs'
|
3
4
|
require 'bolt/error'
|
4
5
|
require 'bolt/result_set'
|
5
6
|
require 'bolt/result'
|
7
|
+
require 'pathname'
|
6
8
|
require 'set'
|
7
9
|
|
8
10
|
module BoltSpec
|
9
11
|
module Plans
|
10
|
-
|
11
|
-
|
12
|
-
# Nothing in the TaskDouble is 'public'
|
13
|
-
class TaskDouble
|
14
|
-
def initialize
|
15
|
-
@stubs = []
|
16
|
-
end
|
17
|
-
|
18
|
-
def process(targets, task, arguments, options)
|
19
|
-
# TODO: should we bother matching at all? or just call each
|
20
|
-
# stub until one works?
|
21
|
-
matches = @stubs.select { |s| s.matches(targets, task, arguments, options) }
|
22
|
-
unless matches.empty?
|
23
|
-
matches[0].call(targets, task, arguments, options)
|
24
|
-
end
|
25
|
-
end
|
12
|
+
MOCKED_ACTIONS = %i[command script task upload].freeze
|
26
13
|
|
27
|
-
|
28
|
-
@stubs.each { |s| s.assert_called(taskname) }
|
29
|
-
end
|
30
|
-
|
31
|
-
def add_stub
|
32
|
-
stub = TaskStub.new
|
33
|
-
@stubs.unshift stub
|
34
|
-
stub
|
35
|
-
end
|
36
|
-
end
|
14
|
+
class UnexpectedInvocation < ArgumentError; end
|
37
15
|
|
38
|
-
|
39
|
-
|
16
|
+
# Nothing on the executor is 'public'
|
17
|
+
class MockExecutor
|
18
|
+
attr_reader :noop, :error_message
|
19
|
+
attr_accessor :run_as
|
40
20
|
|
41
|
-
def initialize(
|
42
|
-
@
|
43
|
-
@
|
44
|
-
@
|
45
|
-
|
46
|
-
@
|
47
|
-
|
48
|
-
@data = { default: {} }
|
21
|
+
def initialize(modulepath)
|
22
|
+
@noop = false
|
23
|
+
@run_as = nil
|
24
|
+
@error_message = nil
|
25
|
+
@allow_apply = false
|
26
|
+
@modulepath = [modulepath].flatten.map { |path| File.absolute_path(path) }
|
27
|
+
MOCKED_ACTIONS.each { |action| instance_variable_set(:"@#{action}_doubles", {}) }
|
49
28
|
end
|
50
29
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
end
|
30
|
+
def module_file_id(file)
|
31
|
+
modpath = @modulepath.select { |path| file =~ /^#{path}/ }
|
32
|
+
raise "Could not identify module path containing #{file}: #{modpath}" unless modpath.size == 1
|
55
33
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
if @invocation[:options] && options != @invocation[:options]
|
61
|
-
return false
|
62
|
-
end
|
63
|
-
|
64
|
-
true
|
34
|
+
path = Pathname.new(file)
|
35
|
+
relative = path.relative_path_from(Pathname.new(modpath.first))
|
36
|
+
segments = relative.to_path.split('/')
|
37
|
+
([segments[0]] + segments[2..-1]).join('/')
|
65
38
|
end
|
66
39
|
|
67
|
-
def
|
68
|
-
|
69
|
-
if @
|
70
|
-
|
71
|
-
result_set = @return_block.call(targets: targets, task: task, params: arguments.merge(options))
|
72
|
-
unless result_set.is_a?(Bolt::ResultSet)
|
73
|
-
raise "Return block for #{task} did not return a Bolt::ResultSet"
|
74
|
-
end
|
75
|
-
result_set
|
76
|
-
else
|
77
|
-
results = targets.map do |target|
|
78
|
-
val = @data[target.name] || @data[:default]
|
79
|
-
Bolt::Result.new(target, value: val)
|
80
|
-
end
|
81
|
-
Bolt::ResultSet.new(results)
|
40
|
+
def run_command(targets, command, options = {})
|
41
|
+
result = nil
|
42
|
+
if (doub = @command_doubles[command] || @command_doubles[:default])
|
43
|
+
result = doub.process(targets, command, options)
|
82
44
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
(@expected_calls.nil? && @calls > 0) || @calls == @expected_calls
|
88
|
-
else
|
89
|
-
@expected_calls.nil? || @calls <= @expected_calls
|
90
|
-
end
|
91
|
-
unless satisfied
|
92
|
-
unless (times = @expected_calls)
|
93
|
-
times = @expect ? "at least one" : "any number of"
|
94
|
-
end
|
95
|
-
message = "Expected #{taskname} to be called #{times} times"
|
96
|
-
message += " with targets #{@invocation[:targets]}" if @invocation[:targets]
|
97
|
-
message += " with parameters #{@invocations[:parameters]}" if @invocation[:parameters]
|
98
|
-
raise message
|
45
|
+
unless result
|
46
|
+
targets = targets.map(&:name)
|
47
|
+
@error_message = "Unexpected call to 'run_command(#{command}, #{targets}, #{options})'"
|
48
|
+
raise UnexpectedInvocation, @error_message
|
99
49
|
end
|
50
|
+
result
|
100
51
|
end
|
101
52
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
# Below here are the intended 'public' methods of the stub
|
110
|
-
|
111
|
-
# Restricts the stub to only match invocations with
|
112
|
-
# the correct targets
|
113
|
-
def with_targets(targets)
|
114
|
-
targets = [targets] unless targets.is_a? Array
|
115
|
-
@invocation[:targets] = targets.map do |target|
|
116
|
-
if target.is_a? String
|
117
|
-
target
|
118
|
-
else
|
119
|
-
target.name
|
120
|
-
end
|
53
|
+
def run_script(targets, script_path, arguments, options = {})
|
54
|
+
script = module_file_id(script_path)
|
55
|
+
result = nil
|
56
|
+
if (doub = @script_doubles[script] || @script_doubles[:default])
|
57
|
+
result = doub.process(targets, script, arguments, options)
|
121
58
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
# treated differently at the executor this won't work with some '_*' options
|
128
|
-
# TODO: Fix handling of '_*' options probably by breaking them into other helpers
|
129
|
-
def with_params(params)
|
130
|
-
@invocation[:parameters] = params
|
131
|
-
@invocation[:arguments] = params.reject { |k, _v| k.start_with?('_') }
|
132
|
-
@invocation[:options] = params.select { |k, _v| k.start_with?('_') }
|
133
|
-
self
|
134
|
-
end
|
135
|
-
|
136
|
-
# limit the maximum number of times an allow stub may be called or
|
137
|
-
# specify how many times an expect stub must be called.
|
138
|
-
def be_called_times(times)
|
139
|
-
@expected_calls = times
|
140
|
-
self
|
141
|
-
end
|
142
|
-
|
143
|
-
# error if the stub is called at all.
|
144
|
-
def not_be_called
|
145
|
-
@expected_calls = 0
|
146
|
-
self
|
147
|
-
end
|
148
|
-
|
149
|
-
def return(&block)
|
150
|
-
raise "Cannot set return values and return block." if @data_set
|
151
|
-
@return_block = block
|
152
|
-
self
|
153
|
-
end
|
154
|
-
|
155
|
-
# Set different result values for each target
|
156
|
-
def return_for_targets(data)
|
157
|
-
data.each do |target, result|
|
158
|
-
raise "Mocked results must be hashes: #{target}: #{result}" unless result.is_a? Hash
|
59
|
+
unless result
|
60
|
+
targets = targets.map(&:name)
|
61
|
+
params = options.merge('arguments' => arguments)
|
62
|
+
@error_message = "Unexpected call to 'run_script(#{script}, #{targets}, #{params})'"
|
63
|
+
raise UnexpectedInvocation, @error_message
|
159
64
|
end
|
160
|
-
|
161
|
-
@data = data
|
162
|
-
@data_set = true
|
163
|
-
self
|
164
|
-
end
|
165
|
-
|
166
|
-
# Set a default return value for all targets, specific targets may be overridden with return_for_targets
|
167
|
-
def always_return(default_data)
|
168
|
-
return_for_targets(default: default_data)
|
169
|
-
end
|
170
|
-
|
171
|
-
# Set a default error result for all targets.
|
172
|
-
def error_with(error_data)
|
173
|
-
always_return("_error" => error_data)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
# Nothing on the executor is 'public'
|
178
|
-
class MockExecutor
|
179
|
-
attr_reader :noop, :error_message
|
180
|
-
|
181
|
-
def initialize
|
182
|
-
@noop = false
|
183
|
-
@task_doubles = {}
|
184
|
-
@allow_any_task = true
|
185
|
-
@error_message = nil
|
65
|
+
result
|
186
66
|
end
|
187
67
|
|
188
|
-
def run_task(targets, task, arguments, options)
|
68
|
+
def run_task(targets, task, arguments, options = {})
|
189
69
|
result = nil
|
190
70
|
if (doub = @task_doubles[task.name] || @task_doubles[:default])
|
191
71
|
result = doub.process(targets, task.name, arguments, options)
|
@@ -199,18 +79,44 @@ module BoltSpec
|
|
199
79
|
result
|
200
80
|
end
|
201
81
|
|
82
|
+
def upload_file(targets, source_path, destination, options = {})
|
83
|
+
source = module_file_id(source_path)
|
84
|
+
result = nil
|
85
|
+
if (doub = @upload_doubles[source] || @upload_doubles[:default])
|
86
|
+
result = doub.process(targets, source, destination, options)
|
87
|
+
end
|
88
|
+
unless result
|
89
|
+
targets = targets.map(&:name)
|
90
|
+
@error_message = "Unexpected call to 'upload_file(#{source}, #{destination}, #{targets}, #{options})'"
|
91
|
+
raise UnexpectedInvocation, @error_message
|
92
|
+
end
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
202
96
|
def assert_call_expectations
|
203
|
-
|
204
|
-
|
97
|
+
MOCKED_ACTIONS.each do |action|
|
98
|
+
instance_variable_get(:"@#{action}_doubles").map do |object, doub|
|
99
|
+
doub.assert_called(object)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
MOCKED_ACTIONS.each do |action|
|
105
|
+
define_method(:"stub_#{action}") do |object|
|
106
|
+
instance_variable_get(:"@#{action}_doubles")[object] ||= ActionDouble.new(:"#{action.capitalize}Stub")
|
205
107
|
end
|
206
108
|
end
|
207
109
|
|
208
|
-
def
|
209
|
-
@
|
110
|
+
def stub_apply
|
111
|
+
@allow_apply = true
|
210
112
|
end
|
211
113
|
|
212
114
|
def wait_until_available(targets, _options)
|
213
|
-
targets.map { |target| Bolt::Result.new(target) }
|
115
|
+
Bolt::ResultSet.new(targets.map { |target| Bolt::Result.new(target) })
|
116
|
+
end
|
117
|
+
|
118
|
+
def log_action(*_args)
|
119
|
+
yield
|
214
120
|
end
|
215
121
|
|
216
122
|
def log_plan(_plan_name)
|
@@ -224,6 +130,24 @@ module BoltSpec
|
|
224
130
|
def report_function_call(_function); end
|
225
131
|
|
226
132
|
def report_bundled_content(_mode, _name); end
|
133
|
+
|
134
|
+
def report_apply(_statements, _resources); end
|
135
|
+
|
136
|
+
# Mocked for Apply so it does not compile and execute.
|
137
|
+
def with_node_logging(_description, targets)
|
138
|
+
raise "Unexpected call to apply(#{targets})" unless @allow_apply
|
139
|
+
end
|
140
|
+
|
141
|
+
def queue_execute(targets)
|
142
|
+
raise "Unexpected call to apply(#{targets})" unless @allow_apply
|
143
|
+
targets
|
144
|
+
end
|
145
|
+
|
146
|
+
def await_results(promises)
|
147
|
+
raise "Unexpected call to apply(#{targets})" unless @allow_apply
|
148
|
+
Bolt::ResultSet.new(promises.map { |target| Bolt::ApplyResult.new(target) })
|
149
|
+
end
|
150
|
+
# End Apply mocking
|
227
151
|
end
|
228
152
|
end
|
229
153
|
end
|
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.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -283,6 +283,7 @@ files:
|
|
283
283
|
- bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb
|
284
284
|
- bolt-modules/boltlib/lib/puppet/datatypes/target.rb
|
285
285
|
- bolt-modules/boltlib/lib/puppet/functions/add_facts.rb
|
286
|
+
- bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb
|
286
287
|
- bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb
|
287
288
|
- bolt-modules/boltlib/lib/puppet/functions/facts.rb
|
288
289
|
- bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb
|
@@ -360,6 +361,11 @@ files:
|
|
360
361
|
- lib/bolt_server/schemas/winrm-run_task.json
|
361
362
|
- lib/bolt_server/transport_app.rb
|
362
363
|
- lib/bolt_spec/plans.rb
|
364
|
+
- lib/bolt_spec/plans/action_stubs.rb
|
365
|
+
- lib/bolt_spec/plans/action_stubs/command_stub.rb
|
366
|
+
- lib/bolt_spec/plans/action_stubs/script_stub.rb
|
367
|
+
- lib/bolt_spec/plans/action_stubs/task_stub.rb
|
368
|
+
- lib/bolt_spec/plans/action_stubs/upload_stub.rb
|
363
369
|
- lib/bolt_spec/plans/mock_executor.rb
|
364
370
|
- lib/bolt_spec/run.rb
|
365
371
|
- libexec/apply_catalog.rb
|