puppet_litmus 0.16.0 → 0.18.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module PuppetLitmus; end # rubocop:disable Style/Documentation
3
+ require 'bolt_spec/run'
4
+ require 'honeycomb-beeline'
5
+ require 'puppet_litmus/version'
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
11
+ end
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
14
+ Honeycomb.add_field_to_trace('litmus.pid', Process.pid)
15
+ Honeycomb.add_field_to_trace('litmus.version', PuppetLitmus::VERSION)
16
+ if ENV['CI'] == 'true' && ENV['TRAVIS'] == 'true'
17
+ Honeycomb.add_field_to_trace('module_name', ENV['TRAVIS_REPO_SLUG'])
18
+ Honeycomb.add_field_to_trace('ci.provider', 'travis')
19
+ Honeycomb.add_field_to_trace('ci.build_id', ENV['TRAVIS_BUILD_ID'])
20
+ Honeycomb.add_field_to_trace('ci.build_url', ENV['TRAVIS_BUILD_WEB_URL'])
21
+ Honeycomb.add_field_to_trace('ci.job_url', ENV['TRAVIS_JOB_WEB_URL'])
22
+ Honeycomb.add_field_to_trace('ci.commit_message', ENV['TRAVIS_COMMIT_MESSAGE'])
23
+ Honeycomb.add_field_to_trace('ci.sha', ENV['TRAVIS_PULL_REQUEST_SHA'] || ENV['TRAVIS_COMMIT'])
24
+ elsif ENV['CI'] == 'True' && ENV['APPVEYOR'] == 'True'
25
+ Honeycomb.add_field_to_trace('module_name', ENV['APPVEYOR_PROJECT_SLUG'])
26
+ Honeycomb.add_field_to_trace('ci.provider', 'appveyor')
27
+ Honeycomb.add_field_to_trace('ci.build_id', ENV['APPVEYOR_BUILD_ID'])
28
+ Honeycomb.add_field_to_trace('ci.build_url', "https://ci.appveyor.com/project/#{ENV['APPVEYOR_REPO_NAME']}/builds/#{ENV['APPVEYOR_BUILD_ID']}")
29
+ Honeycomb.add_field_to_trace('ci.job_url', "https://ci.appveyor.com/project/#{ENV['APPVEYOR_REPO_NAME']}/build/job/#{ENV['APPVEYOR_JOB_ID']}")
30
+ Honeycomb.add_field_to_trace('ci.commit_message', ENV['APPVEYOR_REPO_COMMIT_MESSAGE'])
31
+ Honeycomb.add_field_to_trace('ci.sha', ENV['APPVEYOR_PULL_REQUEST_HEAD_COMMIT'] || ENV['APPVEYOR_REPO_COMMIT'])
32
+ elsif ENV['GITHUB_ACTIONS'] == 'true'
33
+ Honeycomb.add_field_to_trace('module_name', ENV['GITHUB_REPOSITORY'])
34
+ Honeycomb.add_field_to_trace('ci.provider', 'github')
35
+ Honeycomb.add_field_to_trace('ci.build_id', ENV['GITHUB_RUN_ID'])
36
+ Honeycomb.add_field_to_trace('ci.build_url', "https://github.com/#{ENV['GITHUB_REPOSITORY']}/actions/runs/#{ENV['GITHUB_RUN_ID']}")
37
+ Honeycomb.add_field_to_trace('ci.sha', ENV['GITHUB_SHA'])
38
+ end
39
+ at_exit do
40
+ process_span.send
41
+ end
4
42
 
5
43
  # helper methods for the litmus rake tasks
6
44
  module PuppetLitmus::RakeHelper
7
- DEFAULT_CONFIG_DATA ||= { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') }.freeze
8
- VALID_PROVISIONERS ||= %w[abs docker docker_exp vagrant vmpooler].freeze
45
+ # DEFAULT_CONFIG_DATA should be frozen for our safety, but it needs to work around https://github.com/puppetlabs/bolt/pull/1696
46
+ DEFAULT_CONFIG_DATA ||= { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') } # .freeze # rubocop:disable Style/MutableConstant
47
+ SUPPORTED_PROVISIONERS ||= %w[abs docker docker_exp vagrant vmpooler].freeze
9
48
 
10
49
  # Gets a string representing the operating system and version.
11
50
  #
@@ -52,53 +91,41 @@ module PuppetLitmus::RakeHelper
52
91
  # @param command [String] command to execute.
53
92
  # @return [Object] the standard out stream.
54
93
  def run_local_command(command)
55
- require 'open3'
56
- stdout, stderr, status = Open3.capture3(command)
57
- error_message = "Attempted to run\ncommand:'#{command}'\nstdout:#{stdout}\nstderr:#{stderr}"
58
- raise error_message unless status.to_i.zero?
94
+ Honeycomb.start_span(name: 'litmus.run_local_command') do |span|
95
+ ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
96
+ span.add_field('litmus.command', command)
59
97
 
60
- stdout
61
- end
98
+ require 'open3'
99
+ stdout, stderr, status = Open3.capture3(command)
100
+ error_message = "Attempted to run\ncommand:'#{command}'\nstdout:#{stdout}\nstderr:#{stderr}"
62
101
 
63
- # Builds all the modules in a specified module
64
- #
65
- # @param source_folder [String] the folder to get the modules from
66
- # @return [Array] an array of module tar's
67
- def build_modules_in_folder(source_folder)
68
- folder_list = Dir.entries(source_folder).reject { |f| File.directory? f }
69
- module_tars = []
70
- folder_list.each do |folder|
71
- folder_handle = Dir.open(File.join(source_folder, folder))
72
- next if File.symlink?(folder_handle)
73
-
74
- module_dir = folder_handle.path
75
- target_dir = File.join(Dir.pwd, 'pkg')
76
- # remove old build folder if exists, before we build afresh
77
- FileUtils.rm_rf(target_dir) if File.directory?(target_dir)
78
-
79
- # build_module
80
- module_tar = build_module(module_dir, target_dir)
81
- module_tars.push(File.new(module_tar))
102
+ raise error_message unless status.to_i.zero?
103
+
104
+ stdout
82
105
  end
83
- module_tars
84
106
  end
85
107
 
86
108
  def provision(provisioner, platform, inventory_vars)
87
- require 'bolt_spec/run'
88
- include BoltSpec::Run
109
+ include ::BoltSpec::Run
89
110
  raise "the provision module was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" unless
90
111
  File.directory?(File.join(DEFAULT_CONFIG_DATA['modulepath'], 'provision'))
91
112
 
92
- unless VALID_PROVISIONERS.include?(provisioner)
93
- raise "Unknown provisioner '#{provisioner}', try #{VALID_PROVISIONERS.join('/')}"
94
- end
113
+ params = { 'action' => 'provision', 'platform' => platform, 'inventory' => Dir.pwd }
114
+ params['vars'] = inventory_vars unless inventory_vars.nil?
115
+
116
+ Honeycomb.add_field_to_trace('litmus.provisioner', provisioner)
117
+ Honeycomb.start_span(name: 'litmus.provision') do |span|
118
+ ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
119
+ span.add_field('litmus.platform', platform)
120
+ span.add_field('litmus.inventory', params['inventory'])
121
+ span.add_field('litmus.config', DEFAULT_CONFIG_DATA)
95
122
 
96
- params = if inventory_vars.nil?
97
- { 'action' => 'provision', 'platform' => platform, 'inventory' => Dir.pwd }
98
- else
99
- { 'action' => 'provision', 'platform' => platform, 'inventory' => Dir.pwd, 'vars' => inventory_vars }
100
- end
101
- run_task("provision::#{provisioner}", 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
123
+ bolt_result = run_task(provisioner_task(provisioner), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
124
+ span.add_field('litmus.node_name', bolt_result&.first&.dig('value', 'node_name'))
125
+ raise_bolt_errors(bolt_result, "provisioning of #{platform} failed.")
126
+
127
+ bolt_result
128
+ end
102
129
  end
103
130
 
104
131
  def provision_list(provision_hash, key)
@@ -107,6 +134,8 @@ module PuppetLitmus::RakeHelper
107
134
  # Splat the params into environment variables to pass to the provision task but only in this runspace
108
135
  provision_hash[key]['params']&.each { |k, value| ENV[k.upcase] = value.to_s }
109
136
  results = []
137
+
138
+ Honeycomb.current_span.add_field('litmus.images', provision_hash[key]['images'])
110
139
  provision_hash[key]['images'].each do |image|
111
140
  results << provision(provisioner, image, inventory_vars)
112
141
  end
@@ -114,42 +143,62 @@ module PuppetLitmus::RakeHelper
114
143
  end
115
144
 
116
145
  def tear_down_nodes(targets, inventory_hash)
117
- require 'bolt_spec/run'
118
- include BoltSpec::Run
119
- config_data = { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') }
120
- 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'))
146
+ Honeycomb.start_span(name: 'litmus.tear_down_nodes') do |span|
147
+ ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
148
+ span.add_field('litmus.targets', targets)
121
149
 
122
- results = {}
123
- targets.each do |node_name|
124
- next if node_name == 'litmus_localhost'
150
+ include ::BoltSpec::Run
151
+ config_data = { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') }
152
+ 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'))
125
153
 
126
- result = tear_down(node_name, inventory_hash)
127
- results[node_name] = result unless result == []
154
+ results = {}
155
+ targets.each do |node_name|
156
+ next if node_name == 'litmus_localhost'
157
+
158
+ result = tear_down(node_name, inventory_hash)
159
+ results[node_name] = result unless result == []
160
+ end
161
+ results
128
162
  end
129
- results
130
163
  end
131
164
 
132
165
  def tear_down(node_name, inventory_hash)
133
- # how do we know what provisioner to use
134
- node_facts = facts_from_node(inventory_hash, node_name)
135
- return [] unless VALID_PROVISIONERS.include?(node_facts['provisioner'])
136
-
137
- params = { 'action' => 'tear_down', 'node_name' => node_name, 'inventory' => Dir.pwd }
138
- run_task("provision::#{node_facts['provisioner']}", 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
166
+ Honeycomb.start_span(name: 'litmus.tear_down') do |span|
167
+ ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
168
+ # how do we know what provisioner to use
169
+
170
+ span.add_field('litmus.node_name', node_name)
171
+ add_platform_field(inventory_hash, node_name)
172
+
173
+ params = { 'action' => 'tear_down', 'node_name' => node_name, 'inventory' => Dir.pwd }
174
+ node_facts = facts_from_node(inventory_hash, node_name)
175
+ bolt_result = run_task(provisioner_task(node_facts['provisioner']), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
176
+ raise_bolt_errors(bolt_result, "tear_down of #{node_name} failed.")
177
+ bolt_result
178
+ end
139
179
  end
140
180
 
141
181
  def install_agent(collection, targets, inventory_hash)
142
- require 'bolt_spec/run'
143
- include BoltSpec::Run
144
- params = if collection.nil?
145
- {}
146
- else
147
- { 'collection' => collection }
148
- end
149
- raise "puppet_agent was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" unless File.directory?(File.join(DEFAULT_CONFIG_DATA['modulepath'], 'puppet_agent'))
150
-
151
- # using boltspec, when the runner is called it changes the inventory_hash dropping the version field. The clone works around this
152
- run_task('puppet_agent::install', targets, params, config: DEFAULT_CONFIG_DATA, inventory: inventory_hash.clone)
182
+ Honeycomb.start_span(name: 'litmus.install_agent') do |span|
183
+ ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
184
+ span.add_field('litmus.collection', collection)
185
+ span.add_field('litmus.targets', targets)
186
+
187
+ include ::BoltSpec::Run
188
+ params = if collection.nil?
189
+ {}
190
+ else
191
+ Honeycomb.current_span.add_field('litmus.collection', collection)
192
+ { 'collection' => collection }
193
+ end
194
+ raise "puppet_agent was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" \
195
+ unless File.directory?(File.join(DEFAULT_CONFIG_DATA['modulepath'], 'puppet_agent'))
196
+
197
+ # using boltspec, when the runner is called it changes the inventory_hash dropping the version field. The clone works around this
198
+ bolt_result = run_task('puppet_agent::install', targets, params, config: DEFAULT_CONFIG_DATA, inventory: inventory_hash.clone)
199
+ raise_bolt_errors(bolt_result, 'Installation of agent failed.')
200
+ bolt_result
201
+ end
153
202
  end
154
203
 
155
204
  def configure_path(inventory_hash)
@@ -162,35 +211,87 @@ module PuppetLitmus::RakeHelper
162
211
  results
163
212
  end
164
213
 
214
+ # Build the module in `module_dir` and put the resulting compressed tarball into `target_dir`.
215
+ #
165
216
  # @param opts Hash of options to build the module
166
217
  # @param module_dir [String] The path of the module to build. If missing defaults to Dir.pwd
167
- # @param target_dir [String] The path the module will be built into. The default is <source_dir>/pkg
218
+ # @param target_dir [String] The path the module will be built into. The default is <module_dir>/pkg
168
219
  # @return [String] The path to the built module
169
220
  def build_module(module_dir = nil, target_dir = nil)
170
221
  require 'puppet/modulebuilder'
171
222
 
172
- source_dir = module_dir || Dir.pwd
173
- dest_dir = target_dir || File.join(source_dir, 'pkg')
223
+ module_dir ||= Dir.pwd
224
+ target_dir ||= File.join(source_dir, 'pkg')
225
+
226
+ puts "Building '#{module_dir}' into '#{target_dir}''"
227
+ builder = Puppet::Modulebuilder::Builder.new(module_dir, target_dir, nil)
174
228
 
175
- builder = Puppet::Modulebuilder::Builder.new(source_dir, dest_dir, nil)
176
229
  # Force the metadata to be read. Raises if metadata could not be found
177
230
  _metadata = builder.metadata
178
231
 
179
232
  builder.build
180
233
  end
181
234
 
182
- def install_module(inventory_hash, target_node_name, module_tar)
183
- require 'bolt_spec/run'
184
- include BoltSpec::Run
185
- target_nodes = find_targets(inventory_hash, target_node_name)
186
- target_string = if target_node_name.nil?
187
- 'all'
188
- else
189
- target_node_name
190
- end
191
- run_local_command("bundle exec bolt file upload \"#{module_tar}\" /tmp/#{File.basename(module_tar)} --nodes #{target_string} --inventoryfile inventory.yaml")
192
- install_module_command = "puppet module install /tmp/#{File.basename(module_tar)}"
193
- run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
235
+ # Builds all the modules in a specified directory
236
+ #
237
+ # @param source_dir [String] the directory to get the modules from
238
+ # @param target_dir [String] temporary location to store tarballs before uploading. This directory will be cleaned before use. The default is <source_dir>/pkg
239
+ # @return [Array] an array of module tars' filenames
240
+ def build_modules_in_dir(source_dir, target_dir = nil)
241
+ target_dir ||= File.join(Dir.pwd, 'pkg')
242
+ # remove old build dir if exists, before we build afresh
243
+ FileUtils.rm_rf(target_dir) if File.directory?(target_dir)
244
+
245
+ module_tars = Dir.entries(source_dir).map do |entry|
246
+ next if ['.', '..'].include? entry
247
+
248
+ module_dir = File.join(source_dir, entry)
249
+ next unless File.directory? module_dir
250
+
251
+ build_module(module_dir, target_dir)
252
+ end
253
+ module_tars.compact
254
+ end
255
+
256
+ # @deprecated Use `build_modules_in_dir` instead
257
+ def build_modules_in_folder(source_folder)
258
+ build_modules_in_dir(source_folder)
259
+ end
260
+
261
+ # Install a specific module tarball to the specified target.
262
+ # This method installs dependencies using a forge repository.
263
+ #
264
+ # @param inventory_hash [Hash] the pre-loaded inventory
265
+ # @param target_node_name [String] the name of the target where the module should be installed
266
+ # @param module_tar [String] the filename of the module tarball to upload
267
+ # @param module_repository [String] the URL for the forge to use for downloading modules. Defaults to the public Forge API.
268
+ # @return a bolt result
269
+ def install_module(inventory_hash, target_node_name, module_tar, module_repository = nil)
270
+ Honeycomb.start_span(name: 'install_module') do |span|
271
+ ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
272
+ span.add_field('litmus.target_node_name', target_node_name)
273
+ span.add_field('litmus.module_tar', module_tar)
274
+
275
+ # make sure the module to install is not installed
276
+ # otherwise `puppet module install` might silently skip it
277
+ module_name = File.basename(module_tar, '.tar.gz').split('-', 3)[0..1].join('-')
278
+ uninstall_module(inventory_hash.clone, target_node_name, module_name, force: true)
279
+
280
+ include ::BoltSpec::Run
281
+
282
+ target_nodes = find_targets(inventory_hash, target_node_name)
283
+ span.add_field('litmus.target_nodes', target_nodes)
284
+ bolt_result = upload_file(module_tar, File.basename(module_tar), target_nodes, options: {}, config: nil, inventory: inventory_hash.clone)
285
+ raise_bolt_errors(bolt_result, 'Failed to upload module.')
286
+
287
+ module_repository_opts = "--module_repository '#{module_repository}'" unless module_repository.nil?
288
+ install_module_command = "puppet module install #{module_repository_opts} #{File.basename(module_tar)}"
289
+ span.add_field('litmus.install_module_command', install_module_command)
290
+
291
+ bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash.clone)
292
+ raise_bolt_errors(bolt_result, "Installation of package #{File.basename(module_tar)} failed.")
293
+ bolt_result
294
+ end
194
295
  end
195
296
 
196
297
  def metadata_module_name
@@ -203,26 +304,87 @@ module PuppetLitmus::RakeHelper
203
304
  metadata['name']
204
305
  end
205
306
 
206
- def uninstall_module(inventory_hash, target_node_name, module_to_remove = nil)
207
- require 'bolt_spec/run'
208
- include BoltSpec::Run
307
+ # Uninstall a module from a specified target
308
+ # @param inventory_hash [Hash] the pre-loaded inventory
309
+ # @param target_node_name [String] the name of the target where the module should be uninstalled
310
+ # @param module_to_remove [String] the name of the module to remove. Defaults to the module under test.
311
+ # @param opts [Hash] additional options to pass on to `puppet module uninstall`
312
+ def uninstall_module(inventory_hash, target_node_name, module_to_remove = nil, **opts)
313
+ include ::BoltSpec::Run
209
314
  module_name = module_to_remove || metadata_module_name
210
315
  target_nodes = find_targets(inventory_hash, target_node_name)
211
316
  install_module_command = "puppet module uninstall #{module_name}"
212
- run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
317
+ install_module_command += ' --force' if opts[:force]
318
+ bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
319
+ # `puppet module uninstall --force` fails if the module is not installed. Ignore errors when force is set
320
+ raise_bolt_errors(bolt_result, "uninstalling #{module_name} failed.") unless opts[:force]
321
+ bolt_result
213
322
  end
214
323
 
215
324
  def check_connectivity?(inventory_hash, target_node_name)
216
- require 'bolt_spec/run'
217
- include BoltSpec::Run
218
- target_nodes = find_targets(inventory_hash, target_node_name)
219
- results = run_command('cd .', target_nodes, config: nil, inventory: inventory_hash)
220
- failed = []
221
- results.each do |result|
222
- failed.push(result['target']) if result['status'] == 'failure'
325
+ Honeycomb.start_span(name: 'litmus.check_connectivity') do |span|
326
+ ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
327
+ # if we're only checking connectivity for a single node
328
+ if target_node_name
329
+ span.add_field('litmus.node_name', target_node_name)
330
+ add_platform_field(inventory_hash, target_node_name)
331
+ end
332
+
333
+ include ::BoltSpec::Run
334
+ target_nodes = find_targets(inventory_hash, target_node_name)
335
+ results = run_command('cd .', target_nodes, config: nil, inventory: inventory_hash)
336
+ span.add_field('litmus.bolt_result', results)
337
+ failed = []
338
+ results.each do |result|
339
+ failed.push(result['target']) if result['status'] == 'failure'
340
+ end
341
+ span.add_field('litmus.connectivity_failed', failed)
342
+ raise "Connectivity has failed on: #{failed}" unless failed.length.zero?
343
+
344
+ true
345
+ end
346
+ end
347
+
348
+ def provisioner_task(provisioner)
349
+ if SUPPORTED_PROVISIONERS.include?(provisioner)
350
+ "provision::#{provisioner}"
351
+ else
352
+ warn "WARNING: Unsuported provisioner '#{provisioner}', try #{SUPPORTED_PROVISIONERS.join('/')}"
353
+ provisioner.to_s
354
+ end
355
+ end
356
+
357
+ # Parse out errors messages in result set returned by Bolt command.
358
+ #
359
+ # @param result_set [Array] result set returned by Bolt command.
360
+ # @return [Hash] Errors grouped by target.
361
+ def check_bolt_errors(result_set)
362
+ errors = {}
363
+ # iterate through each error
364
+ result_set.each do |target_result|
365
+ status = target_result['status']
366
+ # jump to the next one when there is not fail
367
+ next if status != 'failure'
368
+
369
+ target = target_result['target']
370
+ # get some info from error
371
+ errors[target] = target_result['value']
372
+ end
373
+ errors
374
+ end
375
+
376
+ # Parse out errors messages in result set returned by Bolt command. If there are errors, raise them.
377
+ #
378
+ # @param result_set [Array] result set returned by Bolt command.
379
+ # @param error_msg [String] error message to raise when errors are detected. The actual errors will be appended.
380
+ def raise_bolt_errors(result_set, error_msg)
381
+ errors = check_bolt_errors(result_set)
382
+
383
+ unless errors.empty?
384
+ formatted_results = errors.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
385
+ raise "#{error_msg}\nResults:\n#{formatted_results}}"
223
386
  end
224
- raise "Connectivity has failed on: #{failed}" unless failed.length.zero?
225
387
 
226
- true
388
+ nil
227
389
  end
228
390
  end
@@ -17,15 +17,15 @@ namespace :litmus do
17
17
  end
18
18
 
19
19
  # Provisions a list of OSes from provision.yaml file e.g. 'bundle exec rake litmus:provision_list[default]'.
20
- # @See https://github.com/puppetlabs/puppet_litmus/wiki/Overview-of-Litmus#provisioning-via-yaml
20
+ # @See https://puppetlabs.github.io/litmus/Litmus-core-commands.html#provisioning-via-yaml
21
21
  #
22
22
  # @param :key [String] key that maps to a value for a provisioner and an image to be used for each OS provisioned.
23
- desc "provision list of machines from provision.yaml file. 'bundle exec rake 'litmus:provision_list[default]'"
23
+ desc 'provision list of machines from provision.yaml file'
24
24
  task :provision_list, [:key] do |_task, args|
25
25
  raise 'Cannot find provision.yaml file' unless File.file?('./provision.yaml')
26
26
 
27
27
  provision_hash = YAML.load_file('./provision.yaml')
28
- raise "No key #{args[:key]} in ./provision.yaml, see https://github.com/puppetlabs/puppet_litmus/wiki/Overview-of-Litmus#provisioning-via-yaml for examples" if provision_hash[args[:key]].nil?
28
+ raise "No key #{args[:key]} in ./provision.yaml, see https://puppetlabs.github.io/litmus/Litmus-core-commands.html#provisioning-via-yaml for examples" if provision_hash[args[:key]].nil?
29
29
 
30
30
  Rake::Task['spec_prep'].invoke
31
31
 
@@ -57,9 +57,9 @@ namespace :litmus do
57
57
  end
58
58
 
59
59
  if result.first['status'] != 'success'
60
- failed_image_message += "=====\n#{result.first['node']}\n#{result.first['result']['_output']}\n#{result.inspect}"
60
+ failed_image_message += "=====\n#{result.first['target']}\n#{result.first['value']['_output']}\n#{result.inspect}"
61
61
  else
62
- STDOUT.puts "#{result.first['result']['node_name']}, #{image}"
62
+ STDOUT.puts "#{result.first['value']['node_name']}, #{image}"
63
63
  end
64
64
  results << result
65
65
  end
@@ -71,7 +71,7 @@ namespace :litmus do
71
71
  #
72
72
  # @param :provisioner [String] provisioner to use in provisioning given platform.
73
73
  # @param :platform [String] OS platform for container or VM to use.
74
- desc "provision container/VM - abs/docker/vagrant/vmpooler eg 'bundle exec rake 'litmus:provision[vmpooler, ubuntu-1604-x86_64]'"
74
+ desc 'provision a test system using the given provisioner and platform name. See the puppetlabs-provision module tasks for more documentation'
75
75
  task :provision, [:provisioner, :platform, :inventory_vars] do |_task, args|
76
76
  Rake::Task['spec_prep'].invoke
77
77
  if (ENV['CI'] == 'true') || !ENV['DISTELLI_BUILDNUM'].nil?
@@ -96,14 +96,14 @@ namespace :litmus do
96
96
  else
97
97
  spinner.success
98
98
  end
99
- puts "#{results.first['result']['node_name']}, #{args[:platform]}"
99
+ puts "#{results.first['value']['node_name']}, #{args[:platform]}"
100
100
  end
101
101
 
102
102
  # Install puppet agent on a collection of nodes
103
103
  #
104
104
  # @param :collection [String] parameters to pass to the puppet agent install command.
105
105
  # @param :target_node_name [Array] nodes on which to install puppet agent.
106
- desc 'install puppet agent, [:collection, :target_node_name]'
106
+ desc 'install a puppet agent to all or a specified set of targets'
107
107
  task :install_agent, [:collection, :target_node_name] do |_task, args|
108
108
  inventory_hash = inventory_hash_from_inventory_file
109
109
  targets = find_targets(inventory_hash, args[:target_node_name])
@@ -119,11 +119,11 @@ namespace :litmus do
119
119
  results = install_agent(args[:collection], targets, inventory_hash)
120
120
  results.each do |result|
121
121
  if result['status'] != 'success'
122
- command_to_run = "bolt task run puppet_agent::install --targets #{result['node']} --inventoryfile inventory.yaml --modulepath #{DEFAULT_CONFIG_DATA['modulepath']}"
123
- raise "Failed on #{result['node']}\n#{result}\ntry running '#{command_to_run}'"
122
+ command_to_run = "bolt task run puppet_agent::install --targets #{result['target']} --inventoryfile inventory.yaml --modulepath #{DEFAULT_CONFIG_DATA['modulepath']}"
123
+ raise "Failed on #{result['target']}\n#{result}\ntry running '#{command_to_run}'"
124
124
  else
125
125
  # add puppet-agent feature to successful nodes
126
- inventory_hash = add_feature_to_node(inventory_hash, 'puppet-agent', result['node'])
126
+ inventory_hash = add_feature_to_node(inventory_hash, 'puppet-agent', result['target'])
127
127
  end
128
128
  end
129
129
  # update the inventory with the puppet-agent feature set per node
@@ -134,7 +134,7 @@ namespace :litmus do
134
134
 
135
135
  results.each do |result|
136
136
  if result['status'] != 'success'
137
- puts "Failed on #{result['node']}\n#{result}"
137
+ puts "Failed on #{result['target']}\n#{result}"
138
138
  end
139
139
  end
140
140
  end
@@ -143,7 +143,7 @@ namespace :litmus do
143
143
  #
144
144
  # @param :target_node_name [Array] nodes on which to add the feature.
145
145
  # @param :added_feature [String] the feature which you wish to add.
146
- desc 'add_feature, [:added_feature, :target_node_name]'
146
+ desc 'add a feature tag to a node'
147
147
  task :add_feature, [:added_feature, :target_node_name] do |_task, args|
148
148
  inventory_hash = inventory_hash_from_inventory_file
149
149
  targets = find_targets(inventory_hash, args[:target_node_name])
@@ -166,65 +166,69 @@ namespace :litmus do
166
166
  puts 'Feature added'
167
167
  end
168
168
 
169
- # Install the puppet modules from a source directory to nodes. It does not install dependencies.
169
+ # Install the puppet module under test on a collection of nodes
170
170
  #
171
- # @param :source [String] source directory to look in (ignores symlinks) defaults do './spec/fixtures/modules'.
172
171
  # @param :target_node_name [Array] nodes on which to install a puppet module for testing.
173
- desc 'install_module - build and install module'
174
- task :install_modules_from_directory, [:source, :target_node_name] do |_task, args|
172
+ desc 'build the module under test and install it onto targets'
173
+ task :install_module, [:target_node_name, :module_repository] do |_task, args|
174
+ args.with_defaults(target_node_name: nil, module_repository: nil)
175
175
  inventory_hash = inventory_hash_from_inventory_file
176
176
  target_nodes = find_targets(inventory_hash, args[:target_node_name])
177
177
  if target_nodes.empty?
178
178
  puts 'No targets found'
179
179
  exit 0
180
180
  end
181
- source_folder = if args[:source].nil?
182
- './spec/fixtures/modules'
183
- else
184
- File.expand_path(args[:source])
185
- end
186
- raise "Source folder doesnt exist #{source_folder}" unless File.directory?(source_folder)
187
-
188
- module_tars = build_modules_in_folder(source_folder)
189
- puts 'Building'
190
- module_tars.each do |module_tar|
191
- print "#{File.basename(module_tar)} "
192
- end
193
- require 'bolt_spec/run'
194
- include BoltSpec::Run
195
- puts "\nSending"
196
- module_tars.each do |module_tar|
197
- upload_file(module_tar.path, "/tmp/#{File.basename(module_tar)}", target_nodes, options: {}, config: nil, inventory: inventory_hash)
198
- print "#{File.basename(module_tar)} "
199
- end
200
- puts "\nInstalling"
201
- module_tars.each do |module_tar|
202
- # install_module
203
- install_module_command = "puppet module install --force /tmp/#{File.basename(module_tar)}"
204
- run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
205
- print "#{File.basename(module_tar)} "
206
- end
181
+
182
+ module_tar = build_module
183
+ puts "Built '#{module_tar}'"
184
+
185
+ # module_tar = Dir.glob('pkg/*.tar.gz').max_by { |f| File.mtime(f) }
186
+ raise "Unable to find package in 'pkg/*.tar.gz'" if module_tar.nil?
187
+
188
+ install_module(inventory_hash, args[:target_node_name], module_tar, args[:module_repository])
189
+
190
+ puts "Installed '#{module_tar}' on #{args[:target_node_name]}"
207
191
  end
208
192
 
209
- # Check that the nodes in the inventory are still contactable
193
+ # Install the puppet modules from a source directory to nodes. It does not install dependencies.
210
194
  #
211
- # @param :target_node_name [Array] nodes on which to check connnectivity
212
- desc 'check_connectivity - build and install module'
213
- task :check_connectivity, [:target_node_name] do |_task, args|
195
+ # @param :source [String] source directory to look in (ignores symlinks) defaults do './spec/fixtures/modules'.
196
+ # @param :target_node_name [Array] nodes on which to install a puppet module for testing.
197
+ desc 'build and install all modules from a directory'
198
+ task :install_modules_from_directory, [:source, :target_node_name, :module_repository] do |_task, args|
199
+ args.with_defaults(source: nil, target_node_name: nil, module_repository: nil)
214
200
  inventory_hash = inventory_hash_from_inventory_file
215
201
  target_nodes = find_targets(inventory_hash, args[:target_node_name])
216
202
  if target_nodes.empty?
217
203
  puts 'No targets found'
218
204
  exit 0
219
205
  end
220
- check_connectivity?(inventory_hash, args[:target_node_name])
206
+ source_dir = if args[:source].nil?
207
+ './spec/fixtures/modules'
208
+ else
209
+ File.expand_path(args[:source])
210
+ end
211
+ raise "Source directory doesn't exist #{source_dir}" unless File.directory?(source_dir)
212
+
213
+ puts "Building all modules in #{source_dir.inspect}"
214
+ module_tars = build_modules_in_dir(source_dir)
215
+ require 'bolt_spec/run'
216
+ include BoltSpec::Run
217
+ module_tars.each do |module_tar|
218
+ puts "Installing '#{module_tar}'"
219
+ target_nodes.each do |target_node_name|
220
+ install_module(inventory_hash, target_node_name, module_tar, args[:module_repository])
221
+ puts "Installed '#{module_tar}' on #{target_node_name}"
222
+ end
223
+ end
221
224
  end
222
225
 
223
- # Install the puppet module under test on a collection of nodes
226
+ # Uninstall the puppet module under test on a collection of nodes
224
227
  #
225
228
  # @param :target_node_name [Array] nodes on which to install a puppet module for testing.
226
- desc 'install_module - build and install module'
227
- task :install_module, [:target_node_name] do |_task, args|
229
+ # @param :module_name [String] module name to be uninstalled
230
+ desc 'uninstall a specific module'
231
+ task :uninstall_module, [:target_node_name, :module_name] do |_task, args|
228
232
  inventory_hash = inventory_hash_from_inventory_file
229
233
  target_nodes = find_targets(inventory_hash, args[:target_node_name])
230
234
  if target_nodes.empty?
@@ -232,39 +236,58 @@ namespace :litmus do
232
236
  exit 0
233
237
  end
234
238
 
235
- module_tar = build_module
236
- puts 'Built'
237
-
238
- # module_tar = Dir.glob('pkg/*.tar.gz').max_by { |f| File.mtime(f) }
239
- raise "Unable to find package in 'pkg/*.tar.gz'" if module_tar.nil?
240
-
241
- result = install_module(inventory_hash, args[:target_node_name], module_tar)
239
+ result = uninstall_module(inventory_hash, args[:target_node_name], args[:module_name])
242
240
 
243
- raise "Failed trying to run 'puppet module install /tmp/#{File.basename(module_tar)}' against inventory." unless result.is_a?(Array)
241
+ raise "Failed trying to run 'puppet module uninstall #{module_name}' against inventory." unless result.is_a?(Array)
244
242
 
245
243
  result.each do |node|
246
- puts "#{node['node']} failed #{node['result']}" if node['status'] != 'success'
244
+ puts "#{node['target']} failed #{node['value']}" if node['status'] != 'success'
247
245
  end
248
246
 
249
- puts 'Installed'
247
+ puts 'Uninstalled'
248
+ end
249
+
250
+ # Reinstall the puppet module under test on a collection of nodes
251
+ #
252
+ # @param :target_node_name [Array] nodes on which to install a puppet module for testing.
253
+ desc 'reinstall the module under test'
254
+ task :reinstall_module, [:target_node_name, :module_repository] do |_task, args|
255
+ args.with_defaults(target_node_name: nil, module_repository: nil)
256
+ Rake::Task['litmus:uninstall_module'].invoke(args[:target_node_name])
257
+ Rake::Task['litmus:install_module'].invoke(args[:target_node_name], args[:module_repository])
258
+ end
259
+
260
+ # Check that the nodes in the inventory are still contactable
261
+ #
262
+ # @param :target_node_name [Array] nodes on which to check connnectivity
263
+ desc 'check the connectivity to all provisioned targets'
264
+ task :check_connectivity, [:target_node_name] do |_task, args|
265
+ inventory_hash = inventory_hash_from_inventory_file
266
+ target_nodes = find_targets(inventory_hash, args[:target_node_name])
267
+ if target_nodes.empty?
268
+ puts 'No targets found'
269
+ exit 0
270
+ end
271
+ check_connectivity?(inventory_hash, args[:target_node_name])
250
272
  end
251
273
 
252
274
  # Provision a list of machines, install a puppet agent, and install the puppet module under test on a collection of nodes
253
275
  #
254
276
  # @param :key [String] key that maps to a value for a provisioner and an image to be used for each OS provisioned.
255
277
  # @param :collection [String] parameters to pass to the puppet agent install command.
256
- desc 'provision_install - provision a list of machines, install an agent, and the module.'
257
- task :provision_install, [:key, :collection] do |_task, args|
278
+ desc 'provision a list of machines, install an agent, and the module.'
279
+ task :provision_install, [:key, :collection, :module_repository] do |_task, args|
280
+ args.with_defaults(module_repository: nil)
258
281
  Rake::Task['spec_prep'].invoke
259
282
  Rake::Task['litmus:provision_list'].invoke(args[:key])
260
283
  Rake::Task['litmus:install_agent'].invoke(args[:collection])
261
- Rake::Task['litmus:install_module'].invoke
284
+ Rake::Task['litmus:install_module'].invoke(nil, args[:module_repository])
262
285
  end
263
286
 
264
287
  # Decommissions test machines.
265
288
  #
266
289
  # @param :target [Array] nodes to remove from test environemnt and decommission.
267
- desc 'tear-down - decommission machines'
290
+ desc 'destroy provisioned targets'
268
291
  task :tear_down, [:target] do |_task, args|
269
292
  inventory_hash = inventory_hash_from_inventory_file
270
293
  targets = find_targets(inventory_hash, args[:target])
@@ -277,7 +300,7 @@ namespace :litmus do
277
300
  results = tear_down_nodes(targets, inventory_hash)
278
301
  results.each do |node, result|
279
302
  if result.first['status'] != 'success'
280
- bad_results << "#{node}, #{result.first['result']['_error']['msg']}"
303
+ bad_results << "#{node}, #{result.first['value']['_error']['msg']}"
281
304
  else
282
305
  puts "#{node}: #{result.first['status']}"
283
306
  end
@@ -290,39 +313,6 @@ namespace :litmus do
290
313
  end
291
314
  end
292
315
 
293
- # Uninstall the puppet module under test on a collection of nodes
294
- #
295
- # @param :target_node_name [Array] nodes on which to install a puppet module for testing.
296
- # @param :module_name [String] module name to be uninstalled
297
- desc 'uninstall_module - uninstall module'
298
- task :uninstall_module, [:target_node_name, :module_name] do |_task, args|
299
- inventory_hash = inventory_hash_from_inventory_file
300
- target_nodes = find_targets(inventory_hash, args[:target_node_name])
301
- if target_nodes.empty?
302
- puts 'No targets found'
303
- exit 0
304
- end
305
-
306
- result = uninstall_module(inventory_hash, args[:target_node_name], args[:module_name])
307
-
308
- raise "Failed trying to run 'puppet module uninstall #{module_name}' against inventory." unless result.is_a?(Array)
309
-
310
- result.each do |node|
311
- puts "#{node['node']} failed #{node['result']}" if node['status'] != 'success'
312
- end
313
-
314
- puts 'Uninstalled'
315
- end
316
-
317
- # Reinstall the puppet module under test on a collection of nodes
318
- #
319
- # @param :target_node_name [Array] nodes on which to install a puppet module for testing.
320
- desc 'reinstall_module - reinstall module'
321
- task :reinstall_module, [:target_node_name] do |_task, args|
322
- Rake::Task['litmus:uninstall_module'].invoke(args[:target_node_name])
323
- Rake::Task['litmus:install_module'].invoke(args[:target_node_name])
324
- end
325
-
326
316
  namespace :acceptance do
327
317
  require 'rspec/core/rake_task'
328
318
  if File.file?('inventory.yaml')
@@ -339,7 +329,7 @@ namespace :litmus do
339
329
  payloads = []
340
330
  # Generate list of targets to provision
341
331
  targets.each do |target|
342
- test = 'bundle exec rspec ./spec/acceptance --format progress'
332
+ test = 'bundle exec rspec ./spec/acceptance --format progress --require rspec_honeycomb_formatter --format RSpecHoneycombFormatter'
343
333
  title = "#{target}, #{facts_from_node(inventory_hash, target)['platform']}"
344
334
  options = {
345
335
  env: {
@@ -365,7 +355,12 @@ namespace :litmus do
365
355
 
366
356
  require 'parallel'
367
357
  results = Parallel.map(payloads) do |title, test, options|
358
+ # avoid sending the parent process' main span in the sub-processes
359
+ # https://www.ruby-forum.com/t/at-exit-inherited-across-fork/122473/2
360
+ at_exit { exit! }
361
+
368
362
  env = options[:env].nil? ? {} : options[:env]
363
+ env['HTTP_X_HONEYCOMB_TRACE'] = Honeycomb.current_span.to_trace_header
369
364
  stdout, stderr, status = Open3.capture3(env, test)
370
365
  ["\n================\n#{title}\n", stdout, stderr, status]
371
366
  end
@@ -383,6 +378,7 @@ namespace :litmus do
383
378
  spinners = TTY::Spinner::Multi.new("[:spinner] Running against #{targets.size} targets.")
384
379
  payloads.each do |title, test, options|
385
380
  env = options[:env].nil? ? {} : options[:env]
381
+ env['HTTP_X_HONEYCOMB_TRACE'] = Honeycomb.current_span.to_trace_header
386
382
  spinners.register("[:spinner] #{title}") do |sp|
387
383
  stdout, stderr, status = Open3.capture3(env, test)
388
384
  if status.to_i.zero?