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.
- 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?
|