puppet_litmus 0.17.0 → 0.18.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -16
- data/lib/puppet_litmus/inventory_manipulation.rb +10 -2
- data/lib/puppet_litmus/puppet_helpers.rb +44 -40
- data/lib/puppet_litmus/rake_helper.rb +146 -71
- data/lib/puppet_litmus/rake_tasks.rb +95 -101
- data/lib/puppet_litmus/spec_helper_acceptance.rb +1 -1
- data/lib/puppet_litmus/version.rb +1 -1
- data/spec/lib/puppet_litmus/inventory_manipulation_spec.rb +16 -16
- data/spec/lib/puppet_litmus/puppet_helpers_spec.rb +99 -108
- data/spec/lib/puppet_litmus/rake_helper_spec.rb +28 -23
- data/spec/lib/puppet_litmus/rake_tasks_spec.rb +14 -12
- data/spec/spec_helper.rb +7 -6
- metadata +27 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60fc5eef658171b749a429d8d302e94c05e8812858206124e90c915a035a8e4e
|
4
|
+
data.tar.gz: 2016992ffee484e5c2ab2b92e0011d98678b21bc1c0e62dd1d75c857ad50db65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4bb7954ab40c5345dacb291f0fd82a745c7ac5da4e939e07323e23f6c06b7c4758303cc5f9faccf75f67134337ad409edb429ebb438ffe879682460722cacb3
|
7
|
+
data.tar.gz: 3367726403b6691edbb3439377617f894fdffec96f964965bb1e62aa175ade0494920ec166b49da367449c331a9ca6f8e7f87ed2ede707bb7cd7410077e63c38
|
data/README.md
CHANGED
@@ -8,27 +8,23 @@
|
|
8
8
|
</div>
|
9
9
|
|
10
10
|
## Overview
|
11
|
-
Litmus provides a simple command line tool for Puppet content creators, to enable both simple and complex test deployments against specifically configured target systems. It is available as a gem, and can be installed by running ```gem install puppet_litmus```.
|
12
11
|
|
13
|
-
Litmus allows
|
14
|
-
* provision targets to test against,
|
15
|
-
* install the Puppet Agent,
|
16
|
-
* install a module,
|
17
|
-
* run tests, and
|
18
|
-
* tear down the infrastructure.
|
12
|
+
Litmus is a command line tool that allows you to run acceptance tests against Puppet modules.
|
19
13
|
|
20
|
-
|
14
|
+
Litmus allows you to:
|
15
|
+
* Provision targets to test against
|
16
|
+
* Install a Puppet agent
|
17
|
+
* Install a module
|
18
|
+
* Run tests
|
19
|
+
* Tear down the infrastructure
|
21
20
|
|
22
|
-
|
21
|
+
Litmus also facilitates parallel test runs and running tests in isolation. Each step is standalone, allowing other operations between test runs, such as debugging or configuration updates on the test targets.
|
22
|
+
|
23
|
+
Install Litmus as a gem by running ```gem install puppet_litmus```.
|
23
24
|
|
24
|
-
|
25
|
+
## Documentation
|
25
26
|
|
26
|
-
|
27
|
-
* [Architecture](https://github.com/puppetlabs/puppet_litmus/wiki/Architecture-of-puppet-litmus) with an explanation of what's going on under the hood
|
28
|
-
* [Step-by-step guide](https://github.com/puppetlabs/puppet_litmus/wiki/Tutorial:-use-Litmus-to-execute-acceptance-tests-with-a-sample-module-(MoTD)) of how to use Litmus with the popular and simple [MoTD Puppet module](https://forge.puppet.com/puppetlabs/motd).
|
29
|
-
* [How to guide](https://github.com/puppetlabs/puppet_litmus/wiki/Converting-a-module-to-use-Litmus) walking through how to use Litmus in a module
|
30
|
-
* [Helper functions](https://github.com/puppetlabs/puppet_litmus/wiki/Helper-Functions-for-Litmus) a guide to the various helper functions within Litmus.
|
31
|
-
## Known issues
|
27
|
+
For documentation, see our [Litmus Docs Site](https://puppetlabs.github.io/litmus/).
|
32
28
|
|
33
29
|
## Other Resources
|
34
30
|
|
@@ -37,7 +37,6 @@ module PuppetLitmus::InventoryManipulation
|
|
37
37
|
'uri' => 'litmus_localhost',
|
38
38
|
'config' => { 'transport' => 'local' },
|
39
39
|
'feature' => 'puppet-agent',
|
40
|
-
'facts' => { 'platform' => 'localhost' },
|
41
40
|
},
|
42
41
|
],
|
43
42
|
},
|
@@ -236,7 +235,7 @@ module PuppetLitmus::InventoryManipulation
|
|
236
235
|
#
|
237
236
|
# @param inventory_hash [Hash] hash of the inventory.yaml file
|
238
237
|
# @param feature_name [String] feature to locate in the node
|
239
|
-
# node_name [String] node of nodes to limit the search for the node_name in
|
238
|
+
# @param node_name [String] node of nodes to limit the search for the node_name in
|
240
239
|
# @return inventory.yaml file with feature removed from the node.
|
241
240
|
# @return [Hash] inventory_hash with feature added to node if node_name exists in inventory hash.
|
242
241
|
def remove_feature_from_node(inventory_hash, feature_name, node_name)
|
@@ -258,4 +257,13 @@ module PuppetLitmus::InventoryManipulation
|
|
258
257
|
def write_to_inventory_file(inventory_hash, inventory_full_path)
|
259
258
|
File.open(inventory_full_path, 'wb+') { |f| f.write(inventory_hash.to_yaml) }
|
260
259
|
end
|
260
|
+
|
261
|
+
# Add the `litmus.platform` with platform information for the target
|
262
|
+
#
|
263
|
+
# @param inventory_hash [Hash] hash of the inventory.yaml file
|
264
|
+
# @param node_name [String] node of nodes to limit the search for the node_name in
|
265
|
+
def add_platform_field(inventory_hash, node_name)
|
266
|
+
facts = facts_from_node(inventory_hash, node_name)
|
267
|
+
Honeycomb.current_span.add_field('litmus.platform', facts&.dig('platform'))
|
268
|
+
end
|
261
269
|
end
|
@@ -9,7 +9,7 @@ module PuppetLitmus::PuppetHelpers
|
|
9
9
|
# @return [Boolean] The result of the 2 apply manifests.
|
10
10
|
def idempotent_apply(manifest)
|
11
11
|
Honeycomb.start_span(name: 'litmus.idempotent_apply') do |span|
|
12
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
12
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
13
13
|
manifest_file_location = create_manifest_file(manifest)
|
14
14
|
apply_manifest(nil, expect_failures: false, manifest_file_location: manifest_file_location)
|
15
15
|
apply_manifest(nil, catch_changes: true, manifest_file_location: manifest_file_location)
|
@@ -32,13 +32,14 @@ module PuppetLitmus::PuppetHelpers
|
|
32
32
|
# :manifest_file_location [Path] The place on the target system.
|
33
33
|
# :hiera_config [Path] The path to the hiera.yaml configuration on the runner.
|
34
34
|
# :prefix_command [String] prefixes the puppet apply command; eg "export LANGUAGE='ja'".
|
35
|
+
# :trace [Boolean] run puppet apply with the trace flag (defaults to `true`).
|
35
36
|
# :debug [Boolean] run puppet apply with the debug flag.
|
36
37
|
# :noop [Boolean] run puppet apply with the noop flag.
|
37
38
|
# @yieldreturn [Block] this method will yield to a block of code passed by the caller; this can be used for additional validation, etc.
|
38
39
|
# @return [Object] A result object from the apply.
|
39
40
|
def apply_manifest(manifest, opts = {})
|
40
41
|
Honeycomb.start_span(name: 'litmus.apply_manifest') do |span|
|
41
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
42
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
42
43
|
span.add_field('litmus.manifest', manifest)
|
43
44
|
span.add_field('litmus.opts', opts)
|
44
45
|
|
@@ -48,6 +49,8 @@ module PuppetLitmus::PuppetHelpers
|
|
48
49
|
raise 'please specify only one of `catch_changes`, `expect_changes`, `catch_failures` or `expect_failures`' if
|
49
50
|
[opts[:catch_changes], opts[:expect_changes], opts[:catch_failures], opts[:expect_failures]].compact.length > 1
|
50
51
|
|
52
|
+
opts = { trace: true }.merge(opts)
|
53
|
+
|
51
54
|
if opts[:catch_changes]
|
52
55
|
use_detailed_exit_codes = true
|
53
56
|
acceptable_exit_codes = [0]
|
@@ -70,9 +73,10 @@ module PuppetLitmus::PuppetHelpers
|
|
70
73
|
raise "Target '#{target_node_name}' not found in inventory.yaml" unless target_in_inventory?(inventory_hash, target_node_name)
|
71
74
|
|
72
75
|
span.add_field('litmus.node_name', target_node_name)
|
73
|
-
|
76
|
+
add_platform_field(inventory_hash, target_node_name)
|
74
77
|
|
75
78
|
command_to_run = "#{opts[:prefix_command]} puppet apply #{manifest_file_location}"
|
79
|
+
command_to_run += ' --trace' if !opts[:trace].nil? && (opts[:trace] == true)
|
76
80
|
command_to_run += " --modulepath #{Dir.pwd}/spec/fixtures/modules" if target_node_name == 'litmus_localhost'
|
77
81
|
command_to_run += " --hiera_config='#{opts[:hiera_config]}'" unless opts[:hiera_config].nil?
|
78
82
|
command_to_run += ' --debug' if !opts[:debug].nil? && (opts[:debug] == true)
|
@@ -84,9 +88,9 @@ module PuppetLitmus::PuppetHelpers
|
|
84
88
|
bolt_result = run_command(command_to_run, target_node_name, config: nil, inventory: inventory_hash)
|
85
89
|
span.add_field('litmus.bolt_result', bolt_result)
|
86
90
|
|
87
|
-
result = OpenStruct.new(exit_code: bolt_result.first['
|
88
|
-
stdout: bolt_result.first['
|
89
|
-
stderr: bolt_result.first['
|
91
|
+
result = OpenStruct.new(exit_code: bolt_result.first['value']['exit_code'],
|
92
|
+
stdout: bolt_result.first['value']['stdout'],
|
93
|
+
stderr: bolt_result.first['value']['stderr'])
|
90
94
|
span.add_field('litmus.result', result.to_h)
|
91
95
|
|
92
96
|
status = result.exit_code
|
@@ -113,7 +117,7 @@ module PuppetLitmus::PuppetHelpers
|
|
113
117
|
# @return [String] The path to the location of the manifest.
|
114
118
|
def create_manifest_file(manifest)
|
115
119
|
Honeycomb.start_span(name: 'litmus.create_manifest_file') do |span|
|
116
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
120
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
117
121
|
span.add_field('litmus.manifest', manifest)
|
118
122
|
|
119
123
|
require 'tmpdir'
|
@@ -129,12 +133,12 @@ module PuppetLitmus::PuppetHelpers
|
|
129
133
|
# transfer to TARGET_HOST
|
130
134
|
inventory_hash = inventory_hash_from_inventory_file
|
131
135
|
span.add_field('litmus.node_name', target_node_name)
|
132
|
-
|
136
|
+
add_platform_field(inventory_hash, target_node_name)
|
133
137
|
|
134
|
-
manifest_file_location =
|
138
|
+
manifest_file_location = File.basename(manifest_file)
|
135
139
|
bolt_result = upload_file(manifest_file.path, manifest_file_location, target_node_name, options: {}, config: nil, inventory: inventory_hash)
|
136
140
|
span.add_field('litmus.bolt_result', bolt_result)
|
137
|
-
raise bolt_result.first['
|
141
|
+
raise bolt_result.first['value'].to_s unless bolt_result.first['status'] == 'success'
|
138
142
|
end
|
139
143
|
|
140
144
|
span.add_field('litmus.manifest_file_location', manifest_file_location)
|
@@ -151,7 +155,7 @@ module PuppetLitmus::PuppetHelpers
|
|
151
155
|
# @return [Object] A result object from the command.
|
152
156
|
def run_shell(command_to_run, opts = {})
|
153
157
|
Honeycomb.start_span(name: 'litmus.run_shell') do |span|
|
154
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
158
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
155
159
|
span.add_field('litmus.command_to_run', command_to_run)
|
156
160
|
span.add_field('litmus.opts', opts)
|
157
161
|
|
@@ -160,19 +164,19 @@ module PuppetLitmus::PuppetHelpers
|
|
160
164
|
raise "Target '#{target_node_name}' not found in inventory.yaml" unless target_in_inventory?(inventory_hash, target_node_name)
|
161
165
|
|
162
166
|
span.add_field('litmus.node_name', target_node_name)
|
163
|
-
|
167
|
+
add_platform_field(inventory_hash, target_node_name)
|
164
168
|
|
165
169
|
bolt_result = run_command(command_to_run, target_node_name, config: nil, inventory: inventory_hash)
|
166
170
|
span.add_field('litmus.bolt_result', bolt_result)
|
167
171
|
|
168
|
-
if bolt_result.first['
|
172
|
+
if bolt_result.first['value']['exit_code'] != 0 && opts[:expect_failures] != true
|
169
173
|
raise "shell failed\n`#{command_to_run}`\n======\n#{bolt_result}"
|
170
174
|
end
|
171
175
|
|
172
|
-
result = OpenStruct.new(exit_code: bolt_result.first['
|
173
|
-
exit_status: bolt_result.first['
|
174
|
-
stdout: bolt_result.first['
|
175
|
-
stderr: bolt_result.first['
|
176
|
+
result = OpenStruct.new(exit_code: bolt_result.first['value']['exit_code'],
|
177
|
+
exit_status: bolt_result.first['value']['exit_code'],
|
178
|
+
stdout: bolt_result.first['value']['stdout'],
|
179
|
+
stderr: bolt_result.first['value']['stderr'])
|
176
180
|
span.add_field('litmus.result', result.to_h)
|
177
181
|
yield result if block_given?
|
178
182
|
result
|
@@ -188,7 +192,7 @@ module PuppetLitmus::PuppetHelpers
|
|
188
192
|
# @return [Object] A result object from the command.
|
189
193
|
def bolt_upload_file(source, destination, opts = {}, options = {})
|
190
194
|
Honeycomb.start_span(name: 'litmus.bolt_upload_file') do |span|
|
191
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
195
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
192
196
|
span.add_field('litmus.source', source)
|
193
197
|
span.add_field('litmus.destination', destination)
|
194
198
|
span.add_field('litmus.opts', opts)
|
@@ -199,16 +203,16 @@ module PuppetLitmus::PuppetHelpers
|
|
199
203
|
raise "Target '#{target_node_name}' not found in inventory.yaml" unless target_in_inventory?(inventory_hash, target_node_name)
|
200
204
|
|
201
205
|
span.add_field('litmus.node_name', target_node_name)
|
202
|
-
|
206
|
+
add_platform_field(inventory_hash, target_node_name)
|
203
207
|
|
204
208
|
bolt_result = upload_file(source, destination, target_node_name, options: options, config: nil, inventory: inventory_hash)
|
205
209
|
span.add_field('litmus.bolt_result', bolt_result)
|
206
210
|
|
207
211
|
result_obj = {
|
208
212
|
exit_code: 0,
|
209
|
-
stdout: bolt_result.first['
|
213
|
+
stdout: bolt_result.first['value']['_output'],
|
210
214
|
stderr: nil,
|
211
|
-
result: bolt_result.first['
|
215
|
+
result: bolt_result.first['value'],
|
212
216
|
}
|
213
217
|
|
214
218
|
if bolt_result.first['status'] != 'success'
|
@@ -218,7 +222,7 @@ module PuppetLitmus::PuppetHelpers
|
|
218
222
|
end
|
219
223
|
|
220
224
|
result_obj[:exit_code] = 255
|
221
|
-
result_obj[:stderr] = bolt_result.first['
|
225
|
+
result_obj[:stderr] = bolt_result.first['value']['_error']['msg']
|
222
226
|
end
|
223
227
|
|
224
228
|
result = OpenStruct.new(exit_code: result_obj[:exit_code],
|
@@ -240,7 +244,7 @@ module PuppetLitmus::PuppetHelpers
|
|
240
244
|
# @return [Object] A result object from the task.The values available are stdout, stderr and result.
|
241
245
|
def run_bolt_task(task_name, params = {}, opts = {})
|
242
246
|
Honeycomb.start_span(name: 'litmus.run_task') do |span|
|
243
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
247
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
244
248
|
span.add_field('litmus.task_name', task_name)
|
245
249
|
span.add_field('litmus.params', params)
|
246
250
|
span.add_field('litmus.opts', opts)
|
@@ -257,22 +261,22 @@ module PuppetLitmus::PuppetHelpers
|
|
257
261
|
raise "Target '#{target_node_name}' not found in inventory.yaml" unless target_in_inventory?(inventory_hash, target_node_name)
|
258
262
|
|
259
263
|
span.add_field('litmus.node_name', target_node_name)
|
260
|
-
|
264
|
+
add_platform_field(inventory_hash, target_node_name)
|
261
265
|
|
262
266
|
bolt_result = run_task(task_name, target_node_name, params, config: config_data, inventory: inventory_hash)
|
263
267
|
result_obj = {
|
264
268
|
exit_code: 0,
|
265
269
|
stdout: nil,
|
266
270
|
stderr: nil,
|
267
|
-
result: bolt_result.first['
|
271
|
+
result: bolt_result.first['value'],
|
268
272
|
}
|
269
273
|
|
270
274
|
if bolt_result.first['status'] == 'success'
|
271
275
|
# stdout returns unstructured data if structured data is not available
|
272
|
-
result_obj[:stdout] = if bolt_result.first['
|
273
|
-
bolt_result.first['
|
276
|
+
result_obj[:stdout] = if bolt_result.first['value']['_output'].nil?
|
277
|
+
bolt_result.first['value'].to_s
|
274
278
|
else
|
275
|
-
bolt_result.first['
|
279
|
+
bolt_result.first['value']['_output']
|
276
280
|
end
|
277
281
|
|
278
282
|
else
|
@@ -281,12 +285,12 @@ module PuppetLitmus::PuppetHelpers
|
|
281
285
|
raise "task failed\n`#{task_name}`\n======\n#{bolt_result}"
|
282
286
|
end
|
283
287
|
|
284
|
-
result_obj[:exit_code] = if bolt_result.first['
|
288
|
+
result_obj[:exit_code] = if bolt_result.first['value']['_error']['details'].nil?
|
285
289
|
255
|
286
290
|
else
|
287
|
-
bolt_result.first['
|
291
|
+
bolt_result.first['value']['_error']['details'].fetch('exitcode', 255)
|
288
292
|
end
|
289
|
-
result_obj[:stderr] = bolt_result.first['
|
293
|
+
result_obj[:stderr] = bolt_result.first['value']['_error']['msg']
|
290
294
|
end
|
291
295
|
|
292
296
|
result = OpenStruct.new(exit_code: result_obj[:exit_code],
|
@@ -308,7 +312,7 @@ module PuppetLitmus::PuppetHelpers
|
|
308
312
|
# @return [Object] A result object from the script run.
|
309
313
|
def bolt_run_script(script, opts = {}, arguments: [])
|
310
314
|
Honeycomb.start_span(name: 'litmus.bolt_run_script') do |span|
|
311
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
315
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
312
316
|
span.add_field('litmus.script', script)
|
313
317
|
span.add_field('litmus.opts', opts)
|
314
318
|
span.add_field('litmus.arguments', arguments)
|
@@ -318,18 +322,18 @@ module PuppetLitmus::PuppetHelpers
|
|
318
322
|
raise "Target '#{target_node_name}' not found in inventory.yaml" unless target_in_inventory?(inventory_hash, target_node_name)
|
319
323
|
|
320
324
|
span.add_field('litmus.node_name', target_node_name)
|
321
|
-
|
325
|
+
add_platform_field(inventory_hash, target_node_name)
|
322
326
|
|
323
327
|
bolt_result = run_script(script, target_node_name, arguments, options: opts, config: nil, inventory: inventory_hash)
|
324
328
|
|
325
|
-
if bolt_result.first['
|
329
|
+
if bolt_result.first['value']['exit_code'] != 0 && opts[:expect_failures] != true
|
326
330
|
span.add_field('litmus_runscriptfailure', bolt_result)
|
327
331
|
raise "script run failed\n`#{script}`\n======\n#{bolt_result}"
|
328
332
|
end
|
329
333
|
|
330
|
-
result = OpenStruct.new(exit_code: bolt_result.first['
|
331
|
-
stdout: bolt_result.first['
|
332
|
-
stderr: bolt_result.first['
|
334
|
+
result = OpenStruct.new(exit_code: bolt_result.first['value']['exit_code'],
|
335
|
+
stdout: bolt_result.first['value']['stdout'],
|
336
|
+
stderr: bolt_result.first['value']['stderr'])
|
333
337
|
yield result if block_given?
|
334
338
|
span.add_field('litmus.result', result.to_h)
|
335
339
|
result
|
@@ -353,7 +357,7 @@ module PuppetLitmus::PuppetHelpers
|
|
353
357
|
puppet_apply_error = <<~ERROR
|
354
358
|
apply manifest failed
|
355
359
|
`#{command}`
|
356
|
-
with exit code #{bolt_result.first['
|
360
|
+
with exit code #{bolt_result.first['value']['exit_code']} (expected: #{acceptable_exit_codes})
|
357
361
|
====== Start output of failed Puppet apply ======
|
358
362
|
#{puppet_output(bolt_result)}
|
359
363
|
====== End output of failed Puppet apply ======
|
@@ -378,8 +382,8 @@ module PuppetLitmus::PuppetHelpers
|
|
378
382
|
|
379
383
|
# Return the stdout of the puppet run
|
380
384
|
def puppet_output(bolt_result)
|
381
|
-
bolt_result.dig(0, '
|
382
|
-
bolt_result.dig(0, '
|
385
|
+
bolt_result.dig(0, 'value', 'stderr').to_s << \
|
386
|
+
bolt_result.dig(0, 'value', 'stdout').to_s
|
383
387
|
end
|
384
388
|
|
385
389
|
# Checks a puppet return status and returns true if it both
|
@@ -1,13 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require 'bolt_spec/run'
|
5
4
|
require 'honeycomb-beeline'
|
5
|
+
require 'puppet_litmus/version'
|
6
6
|
Honeycomb.configure do |config|
|
7
|
+
# override client if no configuration is provided, so that the pesky libhoney warning about lack of configuration is not shown
|
8
|
+
unless ENV['HONEYCOMB_WRITEKEY'] && ENV['HONEYCOMB_DATASET']
|
9
|
+
config.client = Libhoney::NullClient.new
|
10
|
+
end
|
7
11
|
end
|
8
|
-
process_span = Honeycomb.start_span(name: '
|
9
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = process_span.to_trace_header
|
12
|
+
process_span = Honeycomb.start_span(name: "litmus: #{([$PROGRAM_NAME] + ($ARGV || [])).join(' ')}", serialized_trace: ENV['HTTP_X_HONEYCOMB_TRACE'])
|
13
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = process_span.to_trace_header
|
10
14
|
Honeycomb.add_field_to_trace('litmus.pid', Process.pid)
|
15
|
+
if defined? PuppetLitmus::VERSION
|
16
|
+
Honeycomb.add_field_to_trace('litmus.version', PuppetLitmus::VERSION)
|
17
|
+
else
|
18
|
+
Honeycomb.add_field_to_trace('litmus.version', 'undefined')
|
19
|
+
end
|
11
20
|
if ENV['CI'] == 'true' && ENV['TRAVIS'] == 'true'
|
12
21
|
Honeycomb.add_field_to_trace('module_name', ENV['TRAVIS_REPO_SLUG'])
|
13
22
|
Honeycomb.add_field_to_trace('ci.provider', 'travis')
|
@@ -37,7 +46,8 @@ end
|
|
37
46
|
|
38
47
|
# helper methods for the litmus rake tasks
|
39
48
|
module PuppetLitmus::RakeHelper
|
40
|
-
DEFAULT_CONFIG_DATA
|
49
|
+
# DEFAULT_CONFIG_DATA should be frozen for our safety, but it needs to work around https://github.com/puppetlabs/bolt/pull/1696
|
50
|
+
DEFAULT_CONFIG_DATA ||= { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') } # .freeze # rubocop:disable Style/MutableConstant
|
41
51
|
SUPPORTED_PROVISIONERS ||= %w[abs docker docker_exp vagrant vmpooler].freeze
|
42
52
|
|
43
53
|
# Gets a string representing the operating system and version.
|
@@ -86,7 +96,7 @@ module PuppetLitmus::RakeHelper
|
|
86
96
|
# @return [Object] the standard out stream.
|
87
97
|
def run_local_command(command)
|
88
98
|
Honeycomb.start_span(name: 'litmus.run_local_command') do |span|
|
89
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
99
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
90
100
|
span.add_field('litmus.command', command)
|
91
101
|
|
92
102
|
require 'open3'
|
@@ -99,32 +109,8 @@ module PuppetLitmus::RakeHelper
|
|
99
109
|
end
|
100
110
|
end
|
101
111
|
|
102
|
-
# Builds all the modules in a specified module
|
103
|
-
#
|
104
|
-
# @param source_folder [String] the folder to get the modules from
|
105
|
-
# @return [Array] an array of module tar's
|
106
|
-
def build_modules_in_folder(source_folder)
|
107
|
-
folder_list = Dir.entries(source_folder).reject { |f| File.directory? f }
|
108
|
-
module_tars = []
|
109
|
-
folder_list.each do |folder|
|
110
|
-
folder_handle = Dir.open(File.join(source_folder, folder))
|
111
|
-
next if File.symlink?(folder_handle)
|
112
|
-
|
113
|
-
module_dir = folder_handle.path
|
114
|
-
target_dir = File.join(Dir.pwd, 'pkg')
|
115
|
-
# remove old build folder if exists, before we build afresh
|
116
|
-
FileUtils.rm_rf(target_dir) if File.directory?(target_dir)
|
117
|
-
|
118
|
-
# build_module
|
119
|
-
module_tar = build_module(module_dir, target_dir)
|
120
|
-
module_tars.push(File.new(module_tar))
|
121
|
-
end
|
122
|
-
module_tars
|
123
|
-
end
|
124
|
-
|
125
112
|
def provision(provisioner, platform, inventory_vars)
|
126
|
-
|
127
|
-
include BoltSpec::Run
|
113
|
+
include ::BoltSpec::Run
|
128
114
|
raise "the provision module was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" unless
|
129
115
|
File.directory?(File.join(DEFAULT_CONFIG_DATA['modulepath'], 'provision'))
|
130
116
|
|
@@ -133,14 +119,14 @@ module PuppetLitmus::RakeHelper
|
|
133
119
|
|
134
120
|
Honeycomb.add_field_to_trace('litmus.provisioner', provisioner)
|
135
121
|
Honeycomb.start_span(name: 'litmus.provision') do |span|
|
136
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
122
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
137
123
|
span.add_field('litmus.platform', platform)
|
138
124
|
span.add_field('litmus.inventory', params['inventory'])
|
139
125
|
span.add_field('litmus.config', DEFAULT_CONFIG_DATA)
|
140
126
|
|
141
127
|
bolt_result = run_task(provisioner_task(provisioner), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
|
142
|
-
|
143
|
-
|
128
|
+
span.add_field('litmus.node_name', bolt_result&.first&.dig('value', 'node_name'))
|
129
|
+
raise_bolt_errors(bolt_result, "provisioning of #{platform} failed.")
|
144
130
|
|
145
131
|
bolt_result
|
146
132
|
end
|
@@ -162,11 +148,10 @@ module PuppetLitmus::RakeHelper
|
|
162
148
|
|
163
149
|
def tear_down_nodes(targets, inventory_hash)
|
164
150
|
Honeycomb.start_span(name: 'litmus.tear_down_nodes') do |span|
|
165
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
151
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
166
152
|
span.add_field('litmus.targets', targets)
|
167
153
|
|
168
|
-
|
169
|
-
include BoltSpec::Run
|
154
|
+
include ::BoltSpec::Run
|
170
155
|
config_data = { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') }
|
171
156
|
raise "the provision module was not found in #{config_data['modulepath']}, please amend the .fixtures.yml file" unless File.directory?(File.join(config_data['modulepath'], 'provision'))
|
172
157
|
|
@@ -183,26 +168,27 @@ module PuppetLitmus::RakeHelper
|
|
183
168
|
|
184
169
|
def tear_down(node_name, inventory_hash)
|
185
170
|
Honeycomb.start_span(name: 'litmus.tear_down') do |span|
|
186
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
171
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
187
172
|
# how do we know what provisioner to use
|
188
|
-
node_facts = facts_from_node(inventory_hash, node_name)
|
189
173
|
|
190
174
|
span.add_field('litmus.node_name', node_name)
|
191
|
-
|
175
|
+
add_platform_field(inventory_hash, node_name)
|
192
176
|
|
193
177
|
params = { 'action' => 'tear_down', 'node_name' => node_name, 'inventory' => Dir.pwd }
|
194
|
-
|
178
|
+
node_facts = facts_from_node(inventory_hash, node_name)
|
179
|
+
bolt_result = run_task(provisioner_task(node_facts['provisioner']), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
|
180
|
+
raise_bolt_errors(bolt_result, "tear_down of #{node_name} failed.")
|
181
|
+
bolt_result
|
195
182
|
end
|
196
183
|
end
|
197
184
|
|
198
185
|
def install_agent(collection, targets, inventory_hash)
|
199
186
|
Honeycomb.start_span(name: 'litmus.install_agent') do |span|
|
200
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
187
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
201
188
|
span.add_field('litmus.collection', collection)
|
202
189
|
span.add_field('litmus.targets', targets)
|
203
190
|
|
204
|
-
|
205
|
-
include BoltSpec::Run
|
191
|
+
include ::BoltSpec::Run
|
206
192
|
params = if collection.nil?
|
207
193
|
{}
|
208
194
|
else
|
@@ -213,7 +199,9 @@ module PuppetLitmus::RakeHelper
|
|
213
199
|
unless File.directory?(File.join(DEFAULT_CONFIG_DATA['modulepath'], 'puppet_agent'))
|
214
200
|
|
215
201
|
# using boltspec, when the runner is called it changes the inventory_hash dropping the version field. The clone works around this
|
216
|
-
run_task('puppet_agent::install', targets, params, config: DEFAULT_CONFIG_DATA, inventory: inventory_hash.clone)
|
202
|
+
bolt_result = run_task('puppet_agent::install', targets, params, config: DEFAULT_CONFIG_DATA, inventory: inventory_hash.clone)
|
203
|
+
raise_bolt_errors(bolt_result, 'Installation of agent failed.')
|
204
|
+
bolt_result
|
217
205
|
end
|
218
206
|
end
|
219
207
|
|
@@ -227,40 +215,86 @@ module PuppetLitmus::RakeHelper
|
|
227
215
|
results
|
228
216
|
end
|
229
217
|
|
218
|
+
# Build the module in `module_dir` and put the resulting compressed tarball into `target_dir`.
|
219
|
+
#
|
230
220
|
# @param opts Hash of options to build the module
|
231
221
|
# @param module_dir [String] The path of the module to build. If missing defaults to Dir.pwd
|
232
|
-
# @param target_dir [String] The path the module will be built into. The default is <
|
222
|
+
# @param target_dir [String] The path the module will be built into. The default is <module_dir>/pkg
|
233
223
|
# @return [String] The path to the built module
|
234
224
|
def build_module(module_dir = nil, target_dir = nil)
|
235
225
|
require 'puppet/modulebuilder'
|
236
226
|
|
237
|
-
|
238
|
-
|
227
|
+
module_dir ||= Dir.pwd
|
228
|
+
target_dir ||= File.join(source_dir, 'pkg')
|
229
|
+
|
230
|
+
puts "Building '#{module_dir}' into '#{target_dir}''"
|
231
|
+
builder = Puppet::Modulebuilder::Builder.new(module_dir, target_dir, nil)
|
239
232
|
|
240
|
-
builder = Puppet::Modulebuilder::Builder.new(source_dir, dest_dir, nil)
|
241
233
|
# Force the metadata to be read. Raises if metadata could not be found
|
242
234
|
_metadata = builder.metadata
|
243
235
|
|
244
236
|
builder.build
|
245
237
|
end
|
246
238
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
239
|
+
# Builds all the modules in a specified directory
|
240
|
+
#
|
241
|
+
# @param source_dir [String] the directory to get the modules from
|
242
|
+
# @param target_dir [String] temporary location to store tarballs before uploading. This directory will be cleaned before use. The default is <source_dir>/pkg
|
243
|
+
# @return [Array] an array of module tars' filenames
|
244
|
+
def build_modules_in_dir(source_dir, target_dir = nil)
|
245
|
+
target_dir ||= File.join(Dir.pwd, 'pkg')
|
246
|
+
# remove old build dir if exists, before we build afresh
|
247
|
+
FileUtils.rm_rf(target_dir) if File.directory?(target_dir)
|
248
|
+
|
249
|
+
module_tars = Dir.entries(source_dir).map do |entry|
|
250
|
+
next if ['.', '..'].include? entry
|
251
|
+
|
252
|
+
module_dir = File.join(source_dir, entry)
|
253
|
+
next unless File.directory? module_dir
|
254
|
+
|
255
|
+
build_module(module_dir, target_dir)
|
256
|
+
end
|
257
|
+
module_tars.compact
|
258
|
+
end
|
259
|
+
|
260
|
+
# @deprecated Use `build_modules_in_dir` instead
|
261
|
+
def build_modules_in_folder(source_folder)
|
262
|
+
build_modules_in_dir(source_folder)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Install a specific module tarball to the specified target.
|
266
|
+
# This method installs dependencies using a forge repository.
|
267
|
+
#
|
268
|
+
# @param inventory_hash [Hash] the pre-loaded inventory
|
269
|
+
# @param target_node_name [String] the name of the target where the module should be installed
|
270
|
+
# @param module_tar [String] the filename of the module tarball to upload
|
271
|
+
# @param module_repository [String] the URL for the forge to use for downloading modules. Defaults to the public Forge API.
|
272
|
+
# @return a bolt result
|
273
|
+
def install_module(inventory_hash, target_node_name, module_tar, module_repository = nil)
|
258
274
|
Honeycomb.start_span(name: 'install_module') do |span|
|
259
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
260
|
-
span.add_field('litmus.
|
275
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
276
|
+
span.add_field('litmus.target_node_name', target_node_name)
|
277
|
+
span.add_field('litmus.module_tar', module_tar)
|
278
|
+
|
279
|
+
# make sure the module to install is not installed
|
280
|
+
# otherwise `puppet module install` might silently skip it
|
281
|
+
module_name = File.basename(module_tar, '.tar.gz').split('-', 3)[0..1].join('-')
|
282
|
+
uninstall_module(inventory_hash.clone, target_node_name, module_name, force: true)
|
283
|
+
|
284
|
+
include ::BoltSpec::Run
|
285
|
+
|
286
|
+
target_nodes = find_targets(inventory_hash, target_node_name)
|
261
287
|
span.add_field('litmus.target_nodes', target_nodes)
|
288
|
+
bolt_result = upload_file(module_tar, File.basename(module_tar), target_nodes, options: {}, config: nil, inventory: inventory_hash.clone)
|
289
|
+
raise_bolt_errors(bolt_result, 'Failed to upload module.')
|
290
|
+
|
291
|
+
module_repository_opts = "--module_repository '#{module_repository}'" unless module_repository.nil?
|
292
|
+
install_module_command = "puppet module install #{module_repository_opts} #{File.basename(module_tar)}"
|
293
|
+
span.add_field('litmus.install_module_command', install_module_command)
|
262
294
|
|
263
|
-
run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
|
295
|
+
bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash.clone)
|
296
|
+
raise_bolt_errors(bolt_result, "Installation of package #{File.basename(module_tar)} failed.")
|
297
|
+
bolt_result
|
264
298
|
end
|
265
299
|
end
|
266
300
|
|
@@ -274,26 +308,33 @@ module PuppetLitmus::RakeHelper
|
|
274
308
|
metadata['name']
|
275
309
|
end
|
276
310
|
|
277
|
-
|
278
|
-
|
279
|
-
|
311
|
+
# Uninstall a module from a specified target
|
312
|
+
# @param inventory_hash [Hash] the pre-loaded inventory
|
313
|
+
# @param target_node_name [String] the name of the target where the module should be uninstalled
|
314
|
+
# @param module_to_remove [String] the name of the module to remove. Defaults to the module under test.
|
315
|
+
# @param opts [Hash] additional options to pass on to `puppet module uninstall`
|
316
|
+
def uninstall_module(inventory_hash, target_node_name, module_to_remove = nil, **opts)
|
317
|
+
include ::BoltSpec::Run
|
280
318
|
module_name = module_to_remove || metadata_module_name
|
281
319
|
target_nodes = find_targets(inventory_hash, target_node_name)
|
282
320
|
install_module_command = "puppet module uninstall #{module_name}"
|
283
|
-
|
321
|
+
install_module_command += ' --force' if opts[:force]
|
322
|
+
bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
|
323
|
+
# `puppet module uninstall --force` fails if the module is not installed. Ignore errors when force is set
|
324
|
+
raise_bolt_errors(bolt_result, "uninstalling #{module_name} failed.") unless opts[:force]
|
325
|
+
bolt_result
|
284
326
|
end
|
285
327
|
|
286
328
|
def check_connectivity?(inventory_hash, target_node_name)
|
287
329
|
Honeycomb.start_span(name: 'litmus.check_connectivity') do |span|
|
288
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
330
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
289
331
|
# if we're only checking connectivity for a single node
|
290
332
|
if target_node_name
|
291
333
|
span.add_field('litmus.node_name', target_node_name)
|
292
|
-
|
334
|
+
add_platform_field(inventory_hash, target_node_name)
|
293
335
|
end
|
294
336
|
|
295
|
-
|
296
|
-
include BoltSpec::Run
|
337
|
+
include ::BoltSpec::Run
|
297
338
|
target_nodes = find_targets(inventory_hash, target_node_name)
|
298
339
|
results = run_command('cd .', target_nodes, config: nil, inventory: inventory_hash)
|
299
340
|
span.add_field('litmus.bolt_result', results)
|
@@ -316,4 +357,38 @@ module PuppetLitmus::RakeHelper
|
|
316
357
|
provisioner.to_s
|
317
358
|
end
|
318
359
|
end
|
360
|
+
|
361
|
+
# Parse out errors messages in result set returned by Bolt command.
|
362
|
+
#
|
363
|
+
# @param result_set [Array] result set returned by Bolt command.
|
364
|
+
# @return [Hash] Errors grouped by target.
|
365
|
+
def check_bolt_errors(result_set)
|
366
|
+
errors = {}
|
367
|
+
# iterate through each error
|
368
|
+
result_set.each do |target_result|
|
369
|
+
status = target_result['status']
|
370
|
+
# jump to the next one when there is not fail
|
371
|
+
next if status != 'failure'
|
372
|
+
|
373
|
+
target = target_result['target']
|
374
|
+
# get some info from error
|
375
|
+
errors[target] = target_result['value']
|
376
|
+
end
|
377
|
+
errors
|
378
|
+
end
|
379
|
+
|
380
|
+
# Parse out errors messages in result set returned by Bolt command. If there are errors, raise them.
|
381
|
+
#
|
382
|
+
# @param result_set [Array] result set returned by Bolt command.
|
383
|
+
# @param error_msg [String] error message to raise when errors are detected. The actual errors will be appended.
|
384
|
+
def raise_bolt_errors(result_set, error_msg)
|
385
|
+
errors = check_bolt_errors(result_set)
|
386
|
+
|
387
|
+
unless errors.empty?
|
388
|
+
formatted_results = errors.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
|
389
|
+
raise "#{error_msg}\nResults:\n#{formatted_results}}"
|
390
|
+
end
|
391
|
+
|
392
|
+
nil
|
393
|
+
end
|
319
394
|
end
|