bolt 2.4.0 → 2.5.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.

@@ -102,11 +102,11 @@ module Bolt
102
102
 
103
103
  remote_task_path = conn.write_remote_executable(task_dir, executable)
104
104
 
105
- if STDIN_METHODS.include?(input_method)
105
+ if Bolt::Task::STDIN_METHODS.include?(input_method)
106
106
  stdin = JSON.dump(arguments)
107
107
  end
108
108
 
109
- if ENVIRONMENT_METHODS.include?(input_method)
109
+ if Bolt::Task::ENVIRONMENT_METHODS.include?(input_method)
110
110
  envify_params(arguments).each do |(arg, val)|
111
111
  cmd = Powershell.set_env(arg, val)
112
112
  result = conn.execute(cmd)
data/lib/bolt/util.rb CHANGED
@@ -170,7 +170,22 @@ module Bolt
170
170
  error_types << Java::JavaLang::CloneNotSupportedException if RUBY_PLATFORM == 'java'
171
171
 
172
172
  begin
173
- cl = obj.clone
173
+ # We can't recurse on frozen objects to populate them with cloned
174
+ # data. Instead we store the freeze-state of the original object,
175
+ # deep_clone, then set the cloned object to frozen if the original
176
+ # object was frozen
177
+ frozen = obj.frozen?
178
+ cl = begin
179
+ obj.clone(freeze: false)
180
+ # Some datatypes, such as FalseClass, can't be unfrozen. These
181
+ # aren't the types we recurse on, so we can leave them frozen
182
+ rescue ArgumentError => e
183
+ if e.message =~ /can't unfreeze/
184
+ obj.clone
185
+ else
186
+ raise e
187
+ end
188
+ end
174
189
  rescue *error_types
175
190
  cloned[obj.object_id] = obj
176
191
  obj
@@ -192,6 +207,7 @@ module Bolt
192
207
  cl.instance_variable_set(var, v_cl)
193
208
  end
194
209
 
210
+ cl.freeze if frozen
195
211
  cl
196
212
  end
197
213
  end
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.4.0'
4
+ VERSION = '2.5.0'
5
5
  end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt_spec/plans/mock_executor'
4
+ require 'bolt/config'
5
+ require 'bolt/inventory'
6
+ require 'bolt/pal'
7
+ require 'bolt/plugin'
8
+
9
+ # This helper is used to create the Bolt context necessary to load Bolt plan
10
+ # datatypes and functions. It accomplishes this by replacing bolt's executor
11
+ # with a mock executor. The mock executor allows calls to run_* functions to be
12
+ # stubbed out for testing. By default this executor will fail on any run_* call
13
+ # but stubs can be set up with allow_* and expect_* functions.
14
+ #
15
+ # Stub matching
16
+ #
17
+ # Stubs match invocations of run_* functions by default matching any call but
18
+ # with_targets and with_params helpers can further restrict the stub to match
19
+ # more exact invocations. It's possible a call to run_* could match multiple
20
+ # stubs. In this case the mock executor will first check for stubs specifically
21
+ # matching the task being run after which it will use the last stub that
22
+ # matched
23
+ #
24
+ #
25
+ # allow vs expect
26
+ #
27
+ # Stubs have two general modes bases on whether the test is making assertions
28
+ # on whether function was called. Allow stubs allow the run_* invocation to
29
+ # be called any number of times while expect stubs will fail if no run_*
30
+ # invocation matches them. The be_called_times(n) stub method can be used to
31
+ # ensure an allow stub is not called more than n times or that an expect stub
32
+ # is called exactly n times.
33
+ #
34
+ # Configuration
35
+ #
36
+ # By default the plan helpers use the modulepath set up for rspec-puppet and
37
+ # an otherwise empty bolt config and inventory. To create your own values for
38
+ # these override the modulepath, config, or inventory methods.
39
+ #
40
+ # Stubs:
41
+ # - allow_command(cmd), expect_command(cmd): expect the exact command
42
+ # - allow_script(script), expect_script(script): expect the script as <module>/path/to/file
43
+ # - allow_task(task), expect_task(task): expect the named task
44
+ # - allow_upload(file), expect_upload(file): expect the identified source file
45
+ # - allow_out_message, expect_out_message: expect a message to be passed to out::message (only modifiers are
46
+ # be_called_times(n), with_params(params), and not_be_called)
47
+ #
48
+ # Stub modifiers:
49
+ # - be_called_times(n): if allowed, fail if the action is called more than 'n' times
50
+ # if expected, fail unless the action is called 'n' times
51
+ # - not_be_called: fail if the action is called
52
+ # - with_targets(targets): target or list of targets that you expect to be passed to the action
53
+ # - with_params(params): list of params and metaparams (or options) that you expect to be passed to the action.
54
+ # Corresponds to the action's last argument.
55
+ # - with_destination(dest): for upload_file, the expected destination path
56
+ # - always_return(value): return a Bolt::ResultSet of Bolt::Result objects with the specified value Hash
57
+ # command and script: only accept 'stdout' and 'stderr' keys
58
+ # upload: does not support this modifier
59
+ # - return_for_targets(targets_to_values): return a Bolt::ResultSet of Bolt::Result objects from the Hash mapping
60
+ # targets to their value Hashes
61
+ # command and script: only accept 'stdout' and 'stderr' keys
62
+ # upload: does not support this modifier
63
+ # - return(&block): invoke the block to construct a Bolt::ResultSet. The blocks parameters differ based on action
64
+ # command: `{ |targets:, command:, params:| ... }`
65
+ # script: `{ |targets:, script:, params:| ... }`
66
+ # task: `{ |targets:, task:, params:| ... }`
67
+ # upload: `{ |targets:, source:, destination:, params:| ... }`
68
+ # - error_with(err): return a failing Bolt::ResultSet, with Bolt::Result objects with the identified err hash
69
+ #
70
+ # Example:
71
+ #
72
+ # describe "mymod::myfunction" do
73
+ # include BoltSpec::BoltContext
74
+ #
75
+ # around :each do |example|
76
+ # in_bolt_context do
77
+ # example.run
78
+ # end
79
+ # end
80
+ #
81
+ # it "bolt_context runs a Puppet function with Bolt datatypes" do
82
+ # expect_out_message.with_params("Loaded TargetSpec localhost")
83
+ # is_expected.to run.with_params('localhost').and_return('localhost')
84
+ # end
85
+ # end
86
+
87
+ module BoltSpec
88
+ module BoltContext
89
+ def setup
90
+ unless @loaded
91
+ # This is slow so don't do it until we have to
92
+ Bolt::PAL.load_puppet
93
+ @loaded = true
94
+ end
95
+ end
96
+
97
+ def in_bolt_context(&block)
98
+ setup
99
+ old_modpath = RSpec.configuration.module_path
100
+ old_tasks = Puppet[:tasks]
101
+
102
+ # Set the things
103
+ Puppet[:tasks] = true
104
+ RSpec.configuration.module_path = [modulepath, Bolt::PAL::BOLTLIB_PATH].join(File::PATH_SEPARATOR)
105
+ opts = {
106
+ bolt_executor: executor,
107
+ bolt_inventory: inventory,
108
+ bolt_pdb_client: nil,
109
+ apply_executor: nil
110
+ }
111
+ Puppet.override(opts, &block)
112
+
113
+ # Unset the things
114
+ RSpec.configuration.module_path = old_modpath
115
+ Puppet[:tasks] = old_tasks
116
+ end
117
+
118
+ # Override in your tests if needed
119
+ def modulepath
120
+ [RSpec.configuration.module_path]
121
+ rescue NoMethodError
122
+ raise "RSpec.configuration.module_path not defined set up rspec puppet or define modulepath for this test"
123
+ end
124
+
125
+ def executor
126
+ @executor ||= BoltSpec::Plans::MockExecutor.new(modulepath)
127
+ end
128
+
129
+ # Override in your tests
130
+ def inventory_data
131
+ {}
132
+ end
133
+
134
+ def inventory
135
+ @inventory ||= Bolt::Inventory.create_version(inventory_data, config.transport, config.transports, plugins)
136
+ end
137
+
138
+ # Override in your tests
139
+ def config
140
+ @config ||= begin
141
+ conf = Bolt::Config.new(Bolt::Boltdir.new('.'), {})
142
+ conf.modulepath = [modulepath].flatten
143
+ conf
144
+ end
145
+ end
146
+
147
+ def plugins
148
+ @plugins ||= Bolt::Plugin.setup(config,
149
+ pal,
150
+ nil,
151
+ Bolt::Analytics::NoopClient.new)
152
+ end
153
+
154
+ def pal
155
+ @pal ||= Bolt::PAL.new(config.modulepath, config.hiera_config, config.boltdir.resource_types)
156
+ end
157
+
158
+ BoltSpec::Plans::MOCKED_ACTIONS.each do |action|
159
+ # Allowed action stubs can be called up to be_called_times number of times
160
+ define_method :"allow_#{action}" do |object|
161
+ executor.send(:"stub_#{action}", object).add_stub
162
+ end
163
+
164
+ # Expected action stubs must be called exactly the expected number of times
165
+ # or at least once without be_called_times
166
+ define_method :"expect_#{action}" do |object|
167
+ send(:"allow_#{action}", object).expect_call
168
+ end
169
+
170
+ # This stub will catch any action call if there are no stubs specifically for that task
171
+ define_method :"allow_any_#{action}" do
172
+ executor.send(:"stub_#{action}", :default).add_stub
173
+ end
174
+ end
175
+
176
+ # Does this belong here?
177
+ def allow_out_message
178
+ executor.stub_out_message.add_stub
179
+ end
180
+ alias allow_any_out_message allow_out_message
181
+
182
+ def expect_out_message
183
+ allow_out_message.expect_call
184
+ end
185
+
186
+ # Example helpers to mock other run functions
187
+ # The with_targets method makes sense for all stubs
188
+ # with_params could be reused for options
189
+ # They probably need special stub methods for other arguments through
190
+
191
+ # Scripts can be mocked like tasks by their name
192
+ # arguments is an array instead of a hash though
193
+ # so it probably should be set separately
194
+ # def allow_script(script_name)
195
+ #
196
+ # file uploads have a single destination and no arguments
197
+ # def allow_file_upload(source_name)
198
+ #
199
+ # Most of the information in commands is in the command string itself
200
+ # we may need more flexible allows than just the name/command string
201
+ # Only option params exist on a command.
202
+ # def allow_command(command)
203
+ # def allow_command_matching(command_regex)
204
+ # def allow_command(&block)
205
+ end
206
+ end
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bolt_spec/bolt_context'
3
4
  require 'bolt_spec/plans/mock_executor'
4
- require 'bolt/config'
5
- require 'bolt/inventory'
6
5
  require 'bolt/pal'
7
- require 'bolt/plugin'
8
6
 
9
7
  # These helpers are intended to be used for plan unit testing without calling
10
- # out to target nodes. It accomplishes this by replacing bolt's executor with a
11
- # mock executor. The mock executor allows calls to run_* functions to be
12
- # stubbed out for testing. By default this executor will fail on any run_*
13
- # call but stubs can be set up with allow_* and expect_* functions.
8
+ # out to target nodes. It uses the BoltContext helper to set up a mock executor
9
+ # which allows calls to run_* functions to be stubbed for testing. The context
10
+ # helper also loads Bolt datatypes and plan functions to be used by the code
11
+ # being tested.
14
12
  #
15
13
  # Stub matching
16
14
  #
@@ -42,7 +40,6 @@ require 'bolt/plugin'
42
40
  # an otherwise empty bolt config and inventory. To create your own values for
43
41
  # these override the modulepath, config, or inventory methods.
44
42
  #
45
- #
46
43
  # TODO:
47
44
  # - Allow description based stub matching
48
45
  # - Better testing of plan errors
@@ -142,6 +139,8 @@ require 'bolt/plugin'
142
139
  # See spec/bolt_spec/plan_spec.rb for more examples.
143
140
  module BoltSpec
144
141
  module Plans
142
+ include BoltSpec::BoltContext
143
+
145
144
  def self.init
146
145
  # Ensure tasks are enabled when rspec-puppet sets up an environment so we get task loaders.
147
146
  # Note that this is probably not safe to do in modules that also test Puppet manifest code.
@@ -152,38 +151,6 @@ module BoltSpec
152
151
  Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
153
152
  end
154
153
 
155
- # Override in your tests if needed
156
- def modulepath
157
- [RSpec.configuration.module_path]
158
- rescue NoMethodError
159
- raise "RSpec.configuration.module_path not defined set up rspec puppet or define modulepath for this test"
160
- end
161
-
162
- def plugins
163
- @plugins ||= Bolt::Plugin.setup(config,
164
- pal,
165
- puppetdb_client,
166
- Bolt::Analytics::NoopClient.new)
167
- end
168
-
169
- # Override in your tests
170
- def config
171
- @config ||= begin
172
- conf = Bolt::Config.new(Bolt::Boltdir.new('.'), {})
173
- conf.modulepath = [modulepath].flatten
174
- conf
175
- end
176
- end
177
-
178
- # Override in your tests
179
- def inventory_data
180
- {}
181
- end
182
-
183
- def inventory
184
- @inventory ||= Bolt::Inventory.create_version(inventory_data, config.transport, config.transports, plugins)
185
- end
186
-
187
154
  # Provided as a class so expectations can be placed on it.
188
155
  class MockPuppetDBClient
189
156
  attr_reader :config
@@ -197,10 +164,6 @@ module BoltSpec
197
164
  @puppetdb_client ||= MockPuppetDBClient.new(Bolt::PuppetDB::Config.new({}))
198
165
  end
199
166
 
200
- def pal
201
- @pal ||= Bolt::PAL.new(config.modulepath, config.hiera_config, config.boltdir.resource_types)
202
- end
203
-
204
167
  def run_plan(name, params)
205
168
  pal = Bolt::PAL.new(config.modulepath, config.hiera_config, config.boltdir.resource_types)
206
169
  result = pal.run_plan(name, params, executor, inventory, puppetdb_client)
@@ -218,24 +181,6 @@ module BoltSpec
218
181
  result
219
182
  end
220
183
 
221
- MOCKED_ACTIONS.each do |action|
222
- # Allowed action stubs can be called up to be_called_times number of times
223
- define_method :"allow_#{action}" do |object|
224
- executor.send(:"stub_#{action}", object).add_stub
225
- end
226
-
227
- # Expected action stubs must be called exactly the expected number of times
228
- # or at least once without be_called_times
229
- define_method :"expect_#{action}" do |object|
230
- send(:"allow_#{action}", object).expect_call
231
- end
232
-
233
- # This stub will catch any action call if there are no stubs specifically for that task
234
- define_method :"allow_any_#{action}" do
235
- executor.send(:"stub_#{action}", :default).add_stub
236
- end
237
- end
238
-
239
184
  def allow_apply_prep
240
185
  allow_task('apply_helpers::custom_facts')
241
186
  nil
@@ -251,42 +196,10 @@ module BoltSpec
251
196
  nil
252
197
  end
253
198
 
254
- def allow_out_message
255
- executor.stub_out_message.add_stub
256
- end
257
- alias allow_any_out_message allow_out_message
258
-
259
- def expect_out_message
260
- allow_out_message.expect_call
261
- end
262
-
263
- # Example helpers to mock other run functions
264
- # The with_targets method makes sense for all stubs
265
- # with_params could be reused for options
266
- # They probably need special stub methods for other arguments through
267
-
268
- # Scripts can be mocked like tasks by their name
269
- # arguments is an array instead of a hash though
270
- # so it probably should be set separately
271
- # def allow_script(script_name)
272
- #
273
- # file uploads have a single destination and no arguments
274
- # def allow_file_upload(source_name)
275
- #
276
- # Most of the information in commands is in the command string itself
277
- # we may need more flexible allows than just the name/command string
278
- # Only option params exist on a command.
279
- # def allow_command(command)
280
- # def allow_command_matching(command_regex)
281
- # def allow_command(&block)
282
- #
283
199
  # Plan execution does not flow through the executor mocking may make sense but
284
200
  # will be a separate effort.
285
201
  # def allow_plan(plan_name)
286
202
 
287
203
  # intended to be private below here
288
- def executor
289
- @executor ||= BoltSpec::Plans::MockExecutor.new(modulepath)
290
- end
291
204
  end
292
205
  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: 2.4.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-06 00:00:00.000000000 Z
11
+ date: 2020-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -468,6 +468,9 @@ files:
468
468
  - lib/bolt/result_set.rb
469
469
  - lib/bolt/secret.rb
470
470
  - lib/bolt/secret/base.rb
471
+ - lib/bolt/shell.rb
472
+ - lib/bolt/shell/bash.rb
473
+ - lib/bolt/shell/bash/tmpdir.rb
471
474
  - lib/bolt/target.rb
472
475
  - lib/bolt/task.rb
473
476
  - lib/bolt/task/puppet_server.rb
@@ -476,17 +479,15 @@ files:
476
479
  - lib/bolt/transport/docker.rb
477
480
  - lib/bolt/transport/docker/connection.rb
478
481
  - lib/bolt/transport/local.rb
479
- - lib/bolt/transport/local/shell.rb
482
+ - lib/bolt/transport/local/connection.rb
480
483
  - lib/bolt/transport/local_windows.rb
481
484
  - lib/bolt/transport/orch.rb
482
485
  - lib/bolt/transport/orch/connection.rb
483
486
  - lib/bolt/transport/powershell.rb
484
487
  - lib/bolt/transport/remote.rb
488
+ - lib/bolt/transport/simple.rb
485
489
  - lib/bolt/transport/ssh.rb
486
490
  - lib/bolt/transport/ssh/connection.rb
487
- - lib/bolt/transport/sudoable.rb
488
- - lib/bolt/transport/sudoable/connection.rb
489
- - lib/bolt/transport/sudoable/tmpdir.rb
490
491
  - lib/bolt/transport/winrm.rb
491
492
  - lib/bolt/transport/winrm/connection.rb
492
493
  - lib/bolt/util.rb
@@ -509,6 +510,7 @@ files:
509
510
  - lib/bolt_server/schemas/transport-ssh.json
510
511
  - lib/bolt_server/schemas/transport-winrm.json
511
512
  - lib/bolt_server/transport_app.rb
513
+ - lib/bolt_spec/bolt_context.rb
512
514
  - lib/bolt_spec/plans.rb
513
515
  - lib/bolt_spec/plans/action_stubs.rb
514
516
  - lib/bolt_spec/plans/action_stubs/command_stub.rb