puppet_litmus 0.18.2 → 0.21.0
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 +1 -1
- data/exe/matrix_from_metadata +99 -0
- data/lib/puppet_litmus/inventory_manipulation.rb +12 -8
- data/lib/puppet_litmus/puppet_helpers.rb +44 -8
- data/lib/puppet_litmus/rake_helper.rb +163 -55
- data/lib/puppet_litmus/rake_tasks.rb +170 -140
- data/lib/puppet_litmus/spec_helper_acceptance.rb +4 -1
- data/lib/puppet_litmus/version.rb +1 -1
- data/spec/lib/puppet_litmus/puppet_helpers_spec.rb +47 -9
- data/spec/lib/puppet_litmus/{version_spec.rb → puppet_litmus_version_spec.rb} +0 -0
- data/spec/lib/puppet_litmus/rake_helper_spec.rb +40 -4
- data/spec/lib/puppet_litmus/rake_tasks_spec.rb +17 -10
- metadata +36 -54
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f31e0247c29a20874c704110cac6c6f9c9724624a3fc4bbe1dafd872eee85131
|
4
|
+
data.tar.gz: b940b92982bd7d55861d963ee99f3bc653888063f5d1a304686b276350b18e0e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c0272e6b4f6b840b1a294832fa4a5ec8e7b1159f3c08105fbc490e40dbc7535c1297d0f544ac19ce7730b78232944edc06951c0eb3393d39d5fc66b5f49198e
|
7
|
+
data.tar.gz: 9a29fff33eddf104445edbf5b9c7e2ce31f0a67d9da9d9e4d52eb32699f822062e9dee75a2d7c52aecb7bd2a1de6d6aff866708b5dd3d719b96df0af00906b88
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ Install Litmus as a gem by running ```gem install puppet_litmus```.
|
|
24
24
|
|
25
25
|
## Documentation
|
26
26
|
|
27
|
-
For documentation, see our [Litmus
|
27
|
+
For documentation, see our [Litmus Docs Site](https://puppetlabs.github.io/litmus/).
|
28
28
|
|
29
29
|
## Other Resources
|
30
30
|
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# this script creates a build matrix for github actions from the claimed supported platforms and puppet versions in metadata.json
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
IMAGE_TABLE = {
|
9
|
+
'RedHat-6' => 'rhel-6',
|
10
|
+
'RedHat-7' => 'rhel-7',
|
11
|
+
'RedHat-8' => 'rhel-8',
|
12
|
+
'SLES-12' => 'sles-12',
|
13
|
+
'SLES-15' => 'sles-15',
|
14
|
+
'Windows-2012 R2' => 'windows-2012-r2-core',
|
15
|
+
'Windows-2016' => 'windows-2016',
|
16
|
+
'Windows-2019' => 'windows-2019-core',
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
DOCKER_PLATFORMS = [
|
20
|
+
'CentOS-6',
|
21
|
+
'CentOS-7',
|
22
|
+
'CentOS-8',
|
23
|
+
'Debian-10',
|
24
|
+
'Debian-8',
|
25
|
+
'Debian-9',
|
26
|
+
'OracleLinux-6',
|
27
|
+
'OracleLinux-7',
|
28
|
+
'Scientific-6',
|
29
|
+
'Scientific-7',
|
30
|
+
'Ubuntu-14.04',
|
31
|
+
'Ubuntu-16.04',
|
32
|
+
'Ubuntu-18.04',
|
33
|
+
'Ubuntu-20.04',
|
34
|
+
].freeze
|
35
|
+
|
36
|
+
# This table uses the latest version in each collection for accurate
|
37
|
+
# comparison when evaluating puppet requirements from the metadata
|
38
|
+
COLLECTION_TABLE = {
|
39
|
+
'5.5.22' => 'puppet5',
|
40
|
+
'6.19.1' => 'puppet6',
|
41
|
+
'7.0.0' => 'puppet7-nightly',
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
matrix = {
|
45
|
+
platform: [],
|
46
|
+
collection: [],
|
47
|
+
}
|
48
|
+
|
49
|
+
metadata = JSON.parse(File.read('metadata.json'))
|
50
|
+
# Set platforms based on declared operating system support
|
51
|
+
metadata['operatingsystem_support'].sort_by { |a| a['operatingsystem'] }.each do |sup|
|
52
|
+
os = sup['operatingsystem']
|
53
|
+
sup['operatingsystemrelease'].sort_by { |a| a.to_i }.each do |ver|
|
54
|
+
image_key = "#{os}-#{ver}"
|
55
|
+
if IMAGE_TABLE.key? image_key
|
56
|
+
matrix[:platform] << IMAGE_TABLE[image_key]
|
57
|
+
elsif DOCKER_PLATFORMS.include? image_key
|
58
|
+
puts "Expecting #{image_key} test using docker on travis"
|
59
|
+
else
|
60
|
+
puts "::warning::Cannot find image for #{image_key}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Set collections based on puppet version requirements
|
66
|
+
if metadata.key?('requirements') && metadata['requirements'].length.positive?
|
67
|
+
metadata['requirements'].each do |req|
|
68
|
+
next unless req.key?('name') && req.key?('version_requirement') && req['name'] == 'puppet'
|
69
|
+
|
70
|
+
ver_regexp = %r{^([>=<]{1,2})\s*([\d.]+)\s+([>=<]{1,2})\s*([\d.]+)$}
|
71
|
+
match = ver_regexp.match(req['version_requirement'])
|
72
|
+
if match.nil?
|
73
|
+
puts "::warning::Didn't recognize version_requirement '#{req['version_requirement']}'"
|
74
|
+
break
|
75
|
+
end
|
76
|
+
|
77
|
+
cmp_one, ver_one, cmp_two, ver_two = match.captures
|
78
|
+
reqs = ["#{cmp_one} #{ver_one}", "#{cmp_two} #{ver_two}"]
|
79
|
+
|
80
|
+
COLLECTION_TABLE.each do |key, val|
|
81
|
+
if Gem::Requirement.create(reqs).satisfied_by?(Gem::Version.new(key))
|
82
|
+
matrix[:collection] << val
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Set to defaults (all collections) if no matches are found
|
89
|
+
if matrix[:collection].empty?
|
90
|
+
matrix[:collection] = COLLECTION_TABLE.values
|
91
|
+
end
|
92
|
+
|
93
|
+
# Just to make sure there aren't any duplicates
|
94
|
+
matrix[:platform] = matrix[:platform].uniq.sort
|
95
|
+
matrix[:collection] = matrix[:collection].uniq.sort
|
96
|
+
|
97
|
+
puts "::set-output name=matrix::#{JSON.generate(matrix)}"
|
98
|
+
|
99
|
+
puts "Created matrix with #{matrix[:platform].length * matrix[:collection].length} cells."
|
@@ -18,7 +18,7 @@ module PuppetLitmus::InventoryManipulation
|
|
18
18
|
raise "There is no inventory file at '#{inventory_full_path}'." unless File.exist?(inventory_full_path)
|
19
19
|
|
20
20
|
inventory_hash = YAML.load_file(inventory_full_path)
|
21
|
-
raise "Inventory file is incompatible (version 2 and up). Try the 'bolt project migrate' command." if inventory_hash
|
21
|
+
raise "Inventory file is incompatible (version 2 and up). Try the 'bolt project migrate' command." if inventory_hash['version'].nil? || (inventory_hash['version'] < 2)
|
22
22
|
|
23
23
|
inventory_hash
|
24
24
|
end
|
@@ -50,12 +50,11 @@ module PuppetLitmus::InventoryManipulation
|
|
50
50
|
# @param targets [Array]
|
51
51
|
# @return [Array] array of targets.
|
52
52
|
def find_targets(inventory_hash, targets)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
targets
|
53
|
+
if targets.nil?
|
54
|
+
inventory_hash.to_s.scan(%r{uri"=>"(\S*)"}).flatten
|
55
|
+
else
|
56
|
+
[targets]
|
57
|
+
end
|
59
58
|
end
|
60
59
|
|
61
60
|
# Determines if a node_name exists in a group in the inventory_hash.
|
@@ -263,7 +262,12 @@ module PuppetLitmus::InventoryManipulation
|
|
263
262
|
# @param inventory_hash [Hash] hash of the inventory.yaml file
|
264
263
|
# @param node_name [String] node of nodes to limit the search for the node_name in
|
265
264
|
def add_platform_field(inventory_hash, node_name)
|
266
|
-
facts =
|
265
|
+
facts = begin
|
266
|
+
facts_from_node(inventory_hash, node_name)
|
267
|
+
rescue StandardError => e
|
268
|
+
warn e
|
269
|
+
{}
|
270
|
+
end
|
267
271
|
Honeycomb.current_span.add_field('litmus.platform', facts&.dig('platform'))
|
268
272
|
end
|
269
273
|
end
|
@@ -9,7 +9,7 @@ module PuppetLitmus::PuppetHelpers
|
|
9
9
|
# @return [Boolean] The result of the 2 apply manifests.
|
10
10
|
def idempotent_apply(manifest)
|
11
11
|
Honeycomb.start_span(name: 'litmus.idempotent_apply') do |span|
|
12
|
-
ENV['
|
12
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
13
13
|
manifest_file_location = create_manifest_file(manifest)
|
14
14
|
apply_manifest(nil, expect_failures: false, manifest_file_location: manifest_file_location)
|
15
15
|
apply_manifest(nil, catch_changes: true, manifest_file_location: manifest_file_location)
|
@@ -39,7 +39,7 @@ module PuppetLitmus::PuppetHelpers
|
|
39
39
|
# @return [Object] A result object from the apply.
|
40
40
|
def apply_manifest(manifest, opts = {})
|
41
41
|
Honeycomb.start_span(name: 'litmus.apply_manifest') do |span|
|
42
|
-
ENV['
|
42
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
43
43
|
span.add_field('litmus.manifest', manifest)
|
44
44
|
span.add_field('litmus.opts', opts)
|
45
45
|
|
@@ -117,7 +117,7 @@ module PuppetLitmus::PuppetHelpers
|
|
117
117
|
# @return [String] The path to the location of the manifest.
|
118
118
|
def create_manifest_file(manifest)
|
119
119
|
Honeycomb.start_span(name: 'litmus.create_manifest_file') do |span|
|
120
|
-
ENV['
|
120
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
121
121
|
span.add_field('litmus.manifest', manifest)
|
122
122
|
|
123
123
|
require 'tmpdir'
|
@@ -147,6 +147,42 @@ module PuppetLitmus::PuppetHelpers
|
|
147
147
|
end
|
148
148
|
end
|
149
149
|
|
150
|
+
# Writes a string variable to a file on a target node at a specified path.
|
151
|
+
#
|
152
|
+
# @param content [String] String data to write to the file.
|
153
|
+
# @param destination [String] The path on the target node to write the file.
|
154
|
+
# @return [Bool] Success. The file was succesfully writtne on the target.
|
155
|
+
def write_file(content, destination)
|
156
|
+
Honeycomb.start_span(name: 'litmus.write_file') do |span|
|
157
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
158
|
+
span.add_field('litmus.destination', destination)
|
159
|
+
|
160
|
+
require 'tmpdir'
|
161
|
+
target_node_name = ENV['TARGET_HOST']
|
162
|
+
|
163
|
+
Tempfile.create('litmus') do |tmp_file|
|
164
|
+
tmp_file.write(content)
|
165
|
+
tmp_file.flush
|
166
|
+
if target_node_name.nil? || target_node_name == 'localhost'
|
167
|
+
require 'fileutils'
|
168
|
+
# no need to transfer
|
169
|
+
FileUtils.cp(tmp_file.path, destination)
|
170
|
+
else
|
171
|
+
# transfer to TARGET_HOST
|
172
|
+
inventory_hash = inventory_hash_from_inventory_file
|
173
|
+
span.add_field('litmus.node_name', target_node_name)
|
174
|
+
add_platform_field(inventory_hash, target_node_name)
|
175
|
+
|
176
|
+
bolt_result = upload_file(tmp_file.path, destination, target_node_name, options: {}, config: nil, inventory: inventory_hash)
|
177
|
+
span.add_field('litmus.bolt_result.file_upload', bolt_result)
|
178
|
+
raise bolt_result.first['value'].to_s unless bolt_result.first['status'] == 'success'
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
true
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
150
186
|
# Runs a command against the target system
|
151
187
|
#
|
152
188
|
# @param command_to_run [String] The command to execute.
|
@@ -155,7 +191,7 @@ module PuppetLitmus::PuppetHelpers
|
|
155
191
|
# @return [Object] A result object from the command.
|
156
192
|
def run_shell(command_to_run, opts = {})
|
157
193
|
Honeycomb.start_span(name: 'litmus.run_shell') do |span|
|
158
|
-
ENV['
|
194
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
159
195
|
span.add_field('litmus.command_to_run', command_to_run)
|
160
196
|
span.add_field('litmus.opts', opts)
|
161
197
|
|
@@ -192,7 +228,7 @@ module PuppetLitmus::PuppetHelpers
|
|
192
228
|
# @return [Object] A result object from the command.
|
193
229
|
def bolt_upload_file(source, destination, opts = {}, options = {})
|
194
230
|
Honeycomb.start_span(name: 'litmus.bolt_upload_file') do |span|
|
195
|
-
ENV['
|
231
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
196
232
|
span.add_field('litmus.source', source)
|
197
233
|
span.add_field('litmus.destination', destination)
|
198
234
|
span.add_field('litmus.opts', opts)
|
@@ -244,7 +280,7 @@ module PuppetLitmus::PuppetHelpers
|
|
244
280
|
# @return [Object] A result object from the task.The values available are stdout, stderr and result.
|
245
281
|
def run_bolt_task(task_name, params = {}, opts = {})
|
246
282
|
Honeycomb.start_span(name: 'litmus.run_task') do |span|
|
247
|
-
ENV['
|
283
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
248
284
|
span.add_field('litmus.task_name', task_name)
|
249
285
|
span.add_field('litmus.params', params)
|
250
286
|
span.add_field('litmus.opts', opts)
|
@@ -312,7 +348,7 @@ module PuppetLitmus::PuppetHelpers
|
|
312
348
|
# @return [Object] A result object from the script run.
|
313
349
|
def bolt_run_script(script, opts = {}, arguments: [])
|
314
350
|
Honeycomb.start_span(name: 'litmus.bolt_run_script') do |span|
|
315
|
-
ENV['
|
351
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
316
352
|
span.add_field('litmus.script', script)
|
317
353
|
span.add_field('litmus.opts', opts)
|
318
354
|
span.add_field('litmus.arguments', arguments)
|
@@ -382,7 +418,7 @@ module PuppetLitmus::PuppetHelpers
|
|
382
418
|
|
383
419
|
# Return the stdout of the puppet run
|
384
420
|
def puppet_output(bolt_result)
|
385
|
-
bolt_result.dig(0, 'value', 'stderr').to_s
|
421
|
+
bolt_result.dig(0, 'value', 'stderr').to_s + \
|
386
422
|
bolt_result.dig(0, 'value', 'stdout').to_s
|
387
423
|
end
|
388
424
|
|
@@ -2,15 +2,21 @@
|
|
2
2
|
|
3
3
|
require 'bolt_spec/run'
|
4
4
|
require 'honeycomb-beeline'
|
5
|
+
require 'puppet_litmus/version'
|
5
6
|
Honeycomb.configure do |config|
|
6
7
|
# override client if no configuration is provided, so that the pesky libhoney warning about lack of configuration is not shown
|
7
8
|
unless ENV['HONEYCOMB_WRITEKEY'] && ENV['HONEYCOMB_DATASET']
|
8
9
|
config.client = Libhoney::NullClient.new
|
9
10
|
end
|
10
11
|
end
|
11
|
-
process_span = Honeycomb.start_span(name: '
|
12
|
-
ENV['
|
12
|
+
process_span = Honeycomb.start_span(name: "litmus: #{([$PROGRAM_NAME] + ($ARGV || [])).join(' ')}", serialized_trace: ENV['HONEYCOMB_TRACE'])
|
13
|
+
ENV['HONEYCOMB_TRACE'] = process_span.to_trace_header
|
13
14
|
Honeycomb.add_field_to_trace('litmus.pid', Process.pid)
|
15
|
+
if defined? PuppetLitmus::VERSION
|
16
|
+
Honeycomb.add_field_to_trace('litmus.version', PuppetLitmus::VERSION)
|
17
|
+
else
|
18
|
+
Honeycomb.add_field_to_trace('litmus.version', 'undefined')
|
19
|
+
end
|
14
20
|
if ENV['CI'] == 'true' && ENV['TRAVIS'] == 'true'
|
15
21
|
Honeycomb.add_field_to_trace('module_name', ENV['TRAVIS_REPO_SLUG'])
|
16
22
|
Honeycomb.add_field_to_trace('ci.provider', 'travis')
|
@@ -35,6 +41,13 @@ elsif ENV['GITHUB_ACTIONS'] == 'true'
|
|
35
41
|
Honeycomb.add_field_to_trace('ci.sha', ENV['GITHUB_SHA'])
|
36
42
|
end
|
37
43
|
at_exit do
|
44
|
+
if $ERROR_INFO.is_a?(SystemExit)
|
45
|
+
process_span.add_field('process.exit_code', $ERROR_INFO.status)
|
46
|
+
elsif $ERROR_INFO
|
47
|
+
process_span.add_field('process.exit_code', $ERROR_INFO.class.name)
|
48
|
+
else
|
49
|
+
process_span.add_field('process.exit_code', 'unknown')
|
50
|
+
end
|
38
51
|
process_span.send
|
39
52
|
end
|
40
53
|
|
@@ -42,7 +55,7 @@ end
|
|
42
55
|
module PuppetLitmus::RakeHelper
|
43
56
|
# DEFAULT_CONFIG_DATA should be frozen for our safety, but it needs to work around https://github.com/puppetlabs/bolt/pull/1696
|
44
57
|
DEFAULT_CONFIG_DATA ||= { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') } # .freeze # rubocop:disable Style/MutableConstant
|
45
|
-
SUPPORTED_PROVISIONERS ||= %w[abs docker docker_exp vagrant vmpooler].freeze
|
58
|
+
SUPPORTED_PROVISIONERS ||= %w[abs docker docker_exp provision_service vagrant vmpooler].freeze
|
46
59
|
|
47
60
|
# Gets a string representing the operating system and version.
|
48
61
|
#
|
@@ -90,7 +103,7 @@ module PuppetLitmus::RakeHelper
|
|
90
103
|
# @return [Object] the standard out stream.
|
91
104
|
def run_local_command(command)
|
92
105
|
Honeycomb.start_span(name: 'litmus.run_local_command') do |span|
|
93
|
-
ENV['
|
106
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
94
107
|
span.add_field('litmus.command', command)
|
95
108
|
|
96
109
|
require 'open3'
|
@@ -113,13 +126,18 @@ module PuppetLitmus::RakeHelper
|
|
113
126
|
|
114
127
|
Honeycomb.add_field_to_trace('litmus.provisioner', provisioner)
|
115
128
|
Honeycomb.start_span(name: 'litmus.provision') do |span|
|
116
|
-
ENV['
|
129
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
117
130
|
span.add_field('litmus.platform', platform)
|
118
|
-
|
131
|
+
|
132
|
+
task_name = provisioner_task(provisioner)
|
133
|
+
span.add_field('litmus.task_name', task_name)
|
134
|
+
span.add_field('litmus.params', params)
|
119
135
|
span.add_field('litmus.config', DEFAULT_CONFIG_DATA)
|
120
136
|
|
121
|
-
bolt_result = run_task(
|
137
|
+
bolt_result = run_task(task_name, 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
|
138
|
+
span.add_field('litmus.result', bolt_result)
|
122
139
|
span.add_field('litmus.node_name', bolt_result&.first&.dig('value', 'node_name'))
|
140
|
+
|
123
141
|
raise_bolt_errors(bolt_result, "provisioning of #{platform} failed.")
|
124
142
|
|
125
143
|
bolt_result
|
@@ -142,7 +160,7 @@ module PuppetLitmus::RakeHelper
|
|
142
160
|
|
143
161
|
def tear_down_nodes(targets, inventory_hash)
|
144
162
|
Honeycomb.start_span(name: 'litmus.tear_down_nodes') do |span|
|
145
|
-
ENV['
|
163
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
146
164
|
span.add_field('litmus.targets', targets)
|
147
165
|
|
148
166
|
include ::BoltSpec::Run
|
@@ -154,6 +172,18 @@ module PuppetLitmus::RakeHelper
|
|
154
172
|
next if node_name == 'litmus_localhost'
|
155
173
|
|
156
174
|
result = tear_down(node_name, inventory_hash)
|
175
|
+
# Some provisioners tear_down targets that were created as a batch job.
|
176
|
+
# These provisioners should return the list of additional targets
|
177
|
+
# removed so that we do not attempt to process them.
|
178
|
+
if result != [] && result[0]['value'].key?('removed')
|
179
|
+
removed_targets = result[0]['value']['removed']
|
180
|
+
result[0]['value'].delete('removed')
|
181
|
+
removed_targets.each do |removed_target|
|
182
|
+
targets.delete(removed_target)
|
183
|
+
results[removed_target] = result
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
157
187
|
results[node_name] = result unless result == []
|
158
188
|
end
|
159
189
|
results
|
@@ -162,7 +192,7 @@ module PuppetLitmus::RakeHelper
|
|
162
192
|
|
163
193
|
def tear_down(node_name, inventory_hash)
|
164
194
|
Honeycomb.start_span(name: 'litmus.tear_down') do |span|
|
165
|
-
ENV['
|
195
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
166
196
|
# how do we know what provisioner to use
|
167
197
|
|
168
198
|
span.add_field('litmus.node_name', node_name)
|
@@ -178,7 +208,7 @@ module PuppetLitmus::RakeHelper
|
|
178
208
|
|
179
209
|
def install_agent(collection, targets, inventory_hash)
|
180
210
|
Honeycomb.start_span(name: 'litmus.install_agent') do |span|
|
181
|
-
ENV['
|
211
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
182
212
|
span.add_field('litmus.collection', collection)
|
183
213
|
span.add_field('litmus.targets', targets)
|
184
214
|
|
@@ -186,7 +216,6 @@ module PuppetLitmus::RakeHelper
|
|
186
216
|
params = if collection.nil?
|
187
217
|
{}
|
188
218
|
else
|
189
|
-
Honeycomb.current_span.add_field('litmus.collection', collection)
|
190
219
|
{ 'collection' => collection }
|
191
220
|
end
|
192
221
|
raise "puppet_agent was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" \
|
@@ -202,39 +231,83 @@ module PuppetLitmus::RakeHelper
|
|
202
231
|
def configure_path(inventory_hash)
|
203
232
|
results = []
|
204
233
|
# fix the path on ssh_nodes
|
205
|
-
unless inventory_hash['groups'].select { |group| group['name'] == 'ssh_nodes' }.size.zero?
|
206
|
-
results
|
207
|
-
|
234
|
+
unless inventory_hash['groups'].select { |group| group['name'] == 'ssh_nodes' && !group['targets'].empty? }.size.zero?
|
235
|
+
results << run_command('echo PATH="$PATH:/opt/puppetlabs/puppet/bin" > /etc/environment',
|
236
|
+
'ssh_nodes', config: nil, inventory: inventory_hash)
|
237
|
+
end
|
238
|
+
unless inventory_hash['groups'].select { |group| group['name'] == 'winrm_nodes' && !group['targets'].empty? }.size.zero?
|
239
|
+
results << run_command('[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\Puppet Labs\Puppet\bin;C:\Program Files (x86)\Puppet Labs\Puppet\bin", "Machine")',
|
240
|
+
'winrm_nodes', config: nil, inventory: inventory_hash)
|
208
241
|
end
|
209
242
|
results
|
210
243
|
end
|
211
244
|
|
245
|
+
# Build the module in `module_dir` and put the resulting compressed tarball into `target_dir`.
|
246
|
+
#
|
212
247
|
# @param opts Hash of options to build the module
|
213
248
|
# @param module_dir [String] The path of the module to build. If missing defaults to Dir.pwd
|
214
|
-
# @param target_dir [String] The path the module will be built into. The default is <
|
249
|
+
# @param target_dir [String] The path the module will be built into. The default is <module_dir>/pkg
|
215
250
|
# @return [String] The path to the built module
|
216
251
|
def build_module(module_dir = nil, target_dir = nil)
|
217
252
|
require 'puppet/modulebuilder'
|
218
253
|
|
219
|
-
|
220
|
-
|
254
|
+
module_dir ||= Dir.pwd
|
255
|
+
target_dir ||= File.join(source_dir, 'pkg')
|
256
|
+
|
257
|
+
puts "Building '#{module_dir}' into '#{target_dir}'"
|
258
|
+
builder = Puppet::Modulebuilder::Builder.new(module_dir, target_dir, nil)
|
221
259
|
|
222
|
-
builder = Puppet::Modulebuilder::Builder.new(source_dir, dest_dir, nil)
|
223
260
|
# Force the metadata to be read. Raises if metadata could not be found
|
224
261
|
_metadata = builder.metadata
|
225
262
|
|
226
263
|
builder.build
|
227
264
|
end
|
228
265
|
|
229
|
-
|
266
|
+
# Builds all the modules in a specified directory
|
267
|
+
#
|
268
|
+
# @param source_dir [String] the directory to get the modules from
|
269
|
+
# @param target_dir [String] temporary location to store tarballs before uploading. This directory will be cleaned before use. The default is <source_dir>/pkg
|
270
|
+
# @return [Array] an array of module tars' filenames
|
271
|
+
def build_modules_in_dir(source_dir, target_dir = nil)
|
272
|
+
target_dir ||= File.join(Dir.pwd, 'pkg')
|
273
|
+
# remove old build dir if exists, before we build afresh
|
274
|
+
FileUtils.rm_rf(target_dir) if File.directory?(target_dir)
|
275
|
+
|
276
|
+
module_tars = Dir.entries(source_dir).map do |entry|
|
277
|
+
next if ['.', '..'].include? entry
|
278
|
+
|
279
|
+
module_dir = File.join(source_dir, entry)
|
280
|
+
next unless File.directory? module_dir
|
281
|
+
|
282
|
+
build_module(module_dir, target_dir)
|
283
|
+
end
|
284
|
+
module_tars.compact
|
285
|
+
end
|
286
|
+
|
287
|
+
# @deprecated Use `build_modules_in_dir` instead
|
288
|
+
def build_modules_in_folder(source_folder)
|
289
|
+
build_modules_in_dir(source_folder)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Install a specific module tarball to the specified target.
|
293
|
+
# This method installs dependencies using a forge repository.
|
294
|
+
#
|
295
|
+
# @param inventory_hash [Hash] the pre-loaded inventory
|
296
|
+
# @param target_node_name [String] the name of the target where the module should be installed
|
297
|
+
# @param module_tar [String] the filename of the module tarball to upload
|
298
|
+
# @param module_repository [String] the URL for the forge to use for downloading modules. Defaults to the public Forge API.
|
299
|
+
# @param ignore_dependencies [Boolean] flag used to ignore module dependencies defaults to false.
|
300
|
+
# @return a bolt result
|
301
|
+
def install_module(inventory_hash, target_node_name, module_tar, module_repository = nil, ignore_dependencies = false) # rubocop:disable Style/OptionalBooleanParameter
|
230
302
|
Honeycomb.start_span(name: 'install_module') do |span|
|
231
|
-
ENV['
|
303
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
232
304
|
span.add_field('litmus.target_node_name', target_node_name)
|
233
305
|
span.add_field('litmus.module_tar', module_tar)
|
234
306
|
|
235
|
-
# make sure the
|
307
|
+
# make sure the module to install is not installed
|
236
308
|
# otherwise `puppet module install` might silently skip it
|
237
|
-
|
309
|
+
module_name = File.basename(module_tar, '.tar.gz').split('-', 3)[0..1].join('-')
|
310
|
+
uninstall_module(inventory_hash.clone, target_node_name, module_name, force: true)
|
238
311
|
|
239
312
|
include ::BoltSpec::Run
|
240
313
|
|
@@ -243,7 +316,9 @@ module PuppetLitmus::RakeHelper
|
|
243
316
|
bolt_result = upload_file(module_tar, File.basename(module_tar), target_nodes, options: {}, config: nil, inventory: inventory_hash.clone)
|
244
317
|
raise_bolt_errors(bolt_result, 'Failed to upload module.')
|
245
318
|
|
246
|
-
|
319
|
+
module_repository_opts = "--module_repository '#{module_repository}'" unless module_repository.nil?
|
320
|
+
install_module_command = "puppet module install #{module_repository_opts} #{File.basename(module_tar)}"
|
321
|
+
install_module_command += ' --ignore-dependencies --force' if ignore_dependencies.to_s.downcase == 'true'
|
247
322
|
span.add_field('litmus.install_module_command', install_module_command)
|
248
323
|
|
249
324
|
bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash.clone)
|
@@ -252,31 +327,6 @@ module PuppetLitmus::RakeHelper
|
|
252
327
|
end
|
253
328
|
end
|
254
329
|
|
255
|
-
# Builds all the modules in a specified module
|
256
|
-
#
|
257
|
-
# @param source_folder [String] the folder to get the modules from
|
258
|
-
# @return [Array] an array of module tar's
|
259
|
-
def build_modules_in_folder(source_folder)
|
260
|
-
folder_list = Dir.entries(source_folder).reject { |f| File.directory? f }
|
261
|
-
module_tars = []
|
262
|
-
|
263
|
-
target_dir = File.join(Dir.pwd, 'pkg')
|
264
|
-
# remove old build folder if exists, before we build afresh
|
265
|
-
FileUtils.rm_rf(target_dir) if File.directory?(target_dir)
|
266
|
-
|
267
|
-
folder_list.each do |folder|
|
268
|
-
folder_handle = Dir.open(File.join(source_folder, folder))
|
269
|
-
next if File.symlink?(folder_handle)
|
270
|
-
|
271
|
-
module_dir = folder_handle.path
|
272
|
-
|
273
|
-
# build_module
|
274
|
-
module_tar = build_module(module_dir, target_dir)
|
275
|
-
module_tars.push(File.new(module_tar))
|
276
|
-
end
|
277
|
-
module_tars
|
278
|
-
end
|
279
|
-
|
280
330
|
def metadata_module_name
|
281
331
|
require 'json'
|
282
332
|
raise 'Could not find metadata.json' unless File.exist?(File.join(Dir.pwd, 'metadata.json'))
|
@@ -287,6 +337,11 @@ module PuppetLitmus::RakeHelper
|
|
287
337
|
metadata['name']
|
288
338
|
end
|
289
339
|
|
340
|
+
# Uninstall a module from a specified target
|
341
|
+
# @param inventory_hash [Hash] the pre-loaded inventory
|
342
|
+
# @param target_node_name [String] the name of the target where the module should be uninstalled
|
343
|
+
# @param module_to_remove [String] the name of the module to remove. Defaults to the module under test.
|
344
|
+
# @param opts [Hash] additional options to pass on to `puppet module uninstall`
|
290
345
|
def uninstall_module(inventory_hash, target_node_name, module_to_remove = nil, **opts)
|
291
346
|
include ::BoltSpec::Run
|
292
347
|
module_name = module_to_remove || metadata_module_name
|
@@ -301,24 +356,30 @@ module PuppetLitmus::RakeHelper
|
|
301
356
|
|
302
357
|
def check_connectivity?(inventory_hash, target_node_name)
|
303
358
|
Honeycomb.start_span(name: 'litmus.check_connectivity') do |span|
|
304
|
-
ENV['
|
359
|
+
ENV['HONEYCOMB_TRACE'] = span.to_trace_header
|
305
360
|
# if we're only checking connectivity for a single node
|
306
361
|
if target_node_name
|
307
|
-
span.add_field('litmus.
|
362
|
+
span.add_field('litmus.target_node_name', target_node_name)
|
308
363
|
add_platform_field(inventory_hash, target_node_name)
|
309
364
|
end
|
310
365
|
|
311
366
|
include ::BoltSpec::Run
|
312
367
|
target_nodes = find_targets(inventory_hash, target_node_name)
|
368
|
+
puts "Checking connectivity for #{target_nodes.inspect}"
|
369
|
+
span.add_field('litmus.target_nodes', target_nodes)
|
370
|
+
|
313
371
|
results = run_command('cd .', target_nodes, config: nil, inventory: inventory_hash)
|
314
372
|
span.add_field('litmus.bolt_result', results)
|
315
373
|
failed = []
|
316
|
-
results.each do |result|
|
317
|
-
|
374
|
+
results.reject { |r| r['status'] == 'success' }.each do |result|
|
375
|
+
puts "Failure connecting to #{result['target']}:\n#{result.inspect}"
|
376
|
+
failed.push(result['target'])
|
318
377
|
end
|
319
|
-
span.add_field('litmus.
|
378
|
+
span.add_field('litmus.connectivity_success', results.select { |r| r['status'] == 'success' })
|
379
|
+
span.add_field('litmus.connectivity_failure', results.reject { |r| r['status'] == 'success' })
|
320
380
|
raise "Connectivity has failed on: #{failed}" unless failed.length.zero?
|
321
381
|
|
382
|
+
puts 'Connectivity check PASSED.'
|
322
383
|
true
|
323
384
|
end
|
324
385
|
end
|
@@ -346,7 +407,7 @@ module PuppetLitmus::RakeHelper
|
|
346
407
|
|
347
408
|
target = target_result['target']
|
348
409
|
# get some info from error
|
349
|
-
errors[target] = target_result['value']
|
410
|
+
errors[target] = target_result['value']
|
350
411
|
end
|
351
412
|
errors
|
352
413
|
end
|
@@ -359,9 +420,56 @@ module PuppetLitmus::RakeHelper
|
|
359
420
|
errors = check_bolt_errors(result_set)
|
360
421
|
|
361
422
|
unless errors.empty?
|
362
|
-
|
423
|
+
formatted_results = errors.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
|
424
|
+
raise "#{error_msg}\nResults:\n#{formatted_results}}"
|
363
425
|
end
|
364
426
|
|
365
427
|
nil
|
366
428
|
end
|
429
|
+
|
430
|
+
def start_spinner(message)
|
431
|
+
if (ENV['CI'] || '').downcase == 'true'
|
432
|
+
puts message
|
433
|
+
spinner = Thread.new do
|
434
|
+
# CI systems are strange beasts, we only output a '.' every wee while to keep the terminal alive.
|
435
|
+
loop do
|
436
|
+
printf '.'
|
437
|
+
sleep(10)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
else
|
441
|
+
require 'tty-spinner'
|
442
|
+
spinner = TTY::Spinner.new("[:spinner] #{message}")
|
443
|
+
spinner.auto_spin
|
444
|
+
end
|
445
|
+
spinner
|
446
|
+
end
|
447
|
+
|
448
|
+
def stop_spinner(spinner)
|
449
|
+
if (ENV['CI'] || '').downcase == 'true'
|
450
|
+
Thread.kill(spinner)
|
451
|
+
else
|
452
|
+
spinner.success
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
require 'retryable'
|
457
|
+
|
458
|
+
Retryable.configure do |config|
|
459
|
+
config.sleep = ->(n) { (1.5**n) + Random.rand(0.5) }
|
460
|
+
# config.log_method = ->(retries, exception) do
|
461
|
+
# Logger.new($stdout).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
|
462
|
+
# end
|
463
|
+
end
|
464
|
+
|
465
|
+
class LitmusTimeoutError < StandardError; end
|
466
|
+
|
467
|
+
def with_retries(options: { tries: Float::INFINITY }, max_wait_minutes: 5)
|
468
|
+
stop = Time.now + (max_wait_minutes * 60)
|
469
|
+
Retryable.retryable(options.merge(not: [LitmusTimeoutError])) do
|
470
|
+
raise LitmusTimeoutError if Time.now > stop
|
471
|
+
|
472
|
+
yield
|
473
|
+
end
|
474
|
+
end
|
367
475
|
end
|