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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c81ccd0186b54db2eb71534a0cc10e5191e1b6103201e8ef44231cc017cf5faa
4
- data.tar.gz: dd027dff77884a4b09b66447c966c3636a2cf237e21c1b4f0ec1d28182188937
3
+ metadata.gz: 60fc5eef658171b749a429d8d302e94c05e8812858206124e90c915a035a8e4e
4
+ data.tar.gz: 2016992ffee484e5c2ab2b92e0011d98678b21bc1c0e62dd1d75c857ad50db65
5
5
  SHA512:
6
- metadata.gz: 38db1fa85c7ca655540aad80563bd095c8de46b492e618541a9dbd42b4a9305447ce2538c4ed30882f87582f4a21ebac7d0a1352288f3920d33b42af4d53fa4d
7
- data.tar.gz: 849c989f388d9b1b4dde8a44a2788657c015c50bbfb8ff399255d64c3390557bca7e319939134a2bb2918f3ffc48b0c59a27c29ab7d0779d480c07f9a392a3dc
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 Puppet module developers to:
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
- The tool facilitates parallel test runs, running tests in isolation, and each step is standalone, allowing other operations between test runs, such as debugging, or configuration updates on the test targets.
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
- ## Documentation
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
- All our documentation is currently available in the [Wiki](https://github.com/puppetlabs/puppet_litmus/wiki).
25
+ ## Documentation
25
26
 
26
- * [Overview](https://github.com/puppetlabs/puppet_litmus/wiki/Overview-of-Litmus) of the main functions
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- span.add_field('litmus.platform', facts_from_node(inventory_hash, target_node_name)['platform'])
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['result']['exit_code'],
88
- stdout: bolt_result.first['result']['stdout'],
89
- stderr: bolt_result.first['result']['stderr'])
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- span.add_field('litmus.platform', facts_from_node(inventory_hash, target_node_name)['platform'])
136
+ add_platform_field(inventory_hash, target_node_name)
133
137
 
134
- manifest_file_location = "/tmp/#{File.basename(manifest_file)}"
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['result'].to_s unless bolt_result.first['status'] == 'success'
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- span.add_field('litmus.platform', facts_from_node(inventory_hash, target_node_name)['platform'])
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['result']['exit_code'] != 0 && opts[:expect_failures] != true
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['result']['exit_code'],
173
- exit_status: bolt_result.first['result']['exit_code'],
174
- stdout: bolt_result.first['result']['stdout'],
175
- stderr: bolt_result.first['result']['stderr'])
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- span.add_field('litmus.platform', facts_from_node(inventory_hash, target_node_name)['platform'])
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['result']['_output'],
213
+ stdout: bolt_result.first['value']['_output'],
210
214
  stderr: nil,
211
- result: bolt_result.first['result'],
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['result']['_error']['msg']
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- span.add_field('litmus.platform', facts_from_node(inventory_hash, target_node_name)['platform'])
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['result'],
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['result']['_output'].nil?
273
- bolt_result.first['result'].to_s
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['result']['_output']
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['result']['_error']['details'].nil?
288
+ result_obj[:exit_code] = if bolt_result.first['value']['_error']['details'].nil?
285
289
  255
286
290
  else
287
- bolt_result.first['result']['_error']['details'].fetch('exitcode', 255)
291
+ bolt_result.first['value']['_error']['details'].fetch('exitcode', 255)
288
292
  end
289
- result_obj[:stderr] = bolt_result.first['result']['_error']['msg']
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- span.add_field('litmus.platform', facts_from_node(inventory_hash, target_node_name)['platform'])
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['result']['exit_code'] != 0 && opts[:expect_failures] != true
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['result']['exit_code'],
331
- stdout: bolt_result.first['result']['stdout'],
332
- stderr: bolt_result.first['result']['stderr'])
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['result']['exit_code']} (expected: #{acceptable_exit_codes})
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, 'result', 'stderr').to_s << \
382
- bolt_result.dig(0, 'result', 'stdout').to_s
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
- module PuppetLitmus; end # rubocop:disable Style/Documentation
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: 'Litmus Testing', serialized_trace: ENV['HTTP_X_HONEYCOMB_TRACE'])
9
- ENV['HTTP_X_HONEYCOMB_TRACE'] = process_span.to_trace_header unless ENV['HTTP_X_HONEYCOMB_TRACE']
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 ||= { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') }.freeze
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- require 'bolt_spec/run'
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- span.add_field('litmus.node_name', bolt_result&.first&.dig('result', 'node_name'))
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
151
+ ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
166
152
  span.add_field('litmus.targets', targets)
167
153
 
168
- require 'bolt_spec/run'
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- span.add_field('litmus.platform', node_facts['platform'])
175
+ add_platform_field(inventory_hash, node_name)
192
176
 
193
177
  params = { 'action' => 'tear_down', 'node_name' => node_name, 'inventory' => Dir.pwd }
194
- run_task(provisioner_task(node_facts['provisioner']), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- require 'bolt_spec/run'
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 <source_dir>/pkg
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
- source_dir = module_dir || Dir.pwd
238
- dest_dir = target_dir || File.join(source_dir, 'pkg')
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
- def install_module(inventory_hash, target_node_name, module_tar)
248
- require 'bolt_spec/run'
249
- include BoltSpec::Run
250
- target_nodes = find_targets(inventory_hash, target_node_name)
251
- target_string = if target_node_name.nil?
252
- 'all'
253
- else
254
- target_node_name
255
- end
256
- run_local_command("bundle exec bolt file upload \"#{module_tar}\" /tmp/#{File.basename(module_tar)} --nodes #{target_string} --inventoryfile inventory.yaml")
257
- install_module_command = "puppet module install /tmp/#{File.basename(module_tar)}"
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
260
- span.add_field('litmus.install_module_command', install_module_command)
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
- def uninstall_module(inventory_hash, target_node_name, module_to_remove = nil)
278
- require 'bolt_spec/run'
279
- include BoltSpec::Run
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
- run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
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 unless ENV['HTTP_X_HONEYCOMB_TRACE']
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
- span.add_field('litmus.platform', facts_from_node(inventory_hash, target_node_name)['platform'])
334
+ add_platform_field(inventory_hash, target_node_name)
293
335
  end
294
336
 
295
- require 'bolt_spec/run'
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