puppet_litmus 0.16.0 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +12 -16
- data/lib/puppet_litmus.rb +0 -30
- data/lib/puppet_litmus/inventory_manipulation.rb +10 -1
- data/lib/puppet_litmus/puppet_helpers.rb +200 -137
- data/lib/puppet_litmus/rake_helper.rb +258 -96
- data/lib/puppet_litmus/rake_tasks.rb +96 -100
- 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 +103 -117
- data/spec/lib/puppet_litmus/rake_helper_spec.rb +79 -27
- data/spec/lib/puppet_litmus/rake_tasks_spec.rb +14 -12
- data/spec/spec_helper.rb +7 -6
- metadata +77 -17
@@ -1,11 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
8
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
61
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
127
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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 <
|
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
|
-
|
173
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
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
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
-
|
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.
|
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
|
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.
|
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['
|
60
|
+
failed_image_message += "=====\n#{result.first['target']}\n#{result.first['value']['_output']}\n#{result.inspect}"
|
61
61
|
else
|
62
|
-
STDOUT.puts "#{result.first['
|
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
|
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['
|
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
|
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['
|
123
|
-
raise "Failed on #{result['
|
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['
|
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['
|
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 '
|
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
|
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 '
|
174
|
-
task :
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
raise "
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
-
#
|
193
|
+
# Install the puppet modules from a source directory to nodes. It does not install dependencies.
|
210
194
|
#
|
211
|
-
# @param :
|
212
|
-
|
213
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
227
|
-
|
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
|
-
|
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
|
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['
|
244
|
+
puts "#{node['target']} failed #{node['value']}" if node['status'] != 'success'
|
247
245
|
end
|
248
246
|
|
249
|
-
puts '
|
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 '
|
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 '
|
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['
|
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?
|