puppet_litmus 0.18.1 → 0.20.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 +62 -0
- data/lib/puppet_litmus/inventory_manipulation.rb +12 -8
- data/lib/puppet_litmus/puppet_helpers.rb +45 -9
- data/lib/puppet_litmus/rake_helper.rb +147 -60
- data/lib/puppet_litmus/rake_tasks.rb +147 -129
- 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/rake_helper_spec.rb +4 -4
- data/spec/lib/puppet_litmus/rake_tasks_spec.rb +14 -10
- metadata +34 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f012bba513b46a7010b134d2ca78b733c9bdad8320e13798a4439ae1a7b43e3
|
4
|
+
data.tar.gz: da8f952b4faf060ffe4ec4913235e065a17019ede06437dddb0077a123d61149
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1889d8192777dfb2e2a006c96c549e58b6c080d59a8c0279bf66ce07d048ebb03e5ce4cc694bb24e20ad37560bb19e2112e7030dad57dab8c9b8d142b1046f21
|
7
|
+
data.tar.gz: ffe79dd1557c4d40bc9f04eb737964cadd807c8b4f72a3615e1d13b7d6fb5e15c7c5cc8732f8e6b57aa8a36d8d370c83b6ae571ad68cfe217751cee3db3824a0
|
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,62 @@
|
|
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 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
|
+
matrix = {
|
37
|
+
platform: [],
|
38
|
+
collection: %w[
|
39
|
+
puppet5
|
40
|
+
puppet6
|
41
|
+
puppet7-nightly
|
42
|
+
],
|
43
|
+
}
|
44
|
+
|
45
|
+
metadata = JSON.parse(File.read('metadata.json'))
|
46
|
+
metadata['operatingsystem_support'].sort_by { |a| a['operatingsystem'] }.each do |sup|
|
47
|
+
os = sup['operatingsystem']
|
48
|
+
sup['operatingsystemrelease'].sort_by { |a| a.to_i }.each do |ver|
|
49
|
+
image_key = "#{os}-#{ver}"
|
50
|
+
if IMAGE_TABLE.key? image_key
|
51
|
+
matrix[:platform] << IMAGE_TABLE[image_key]
|
52
|
+
elsif DOCKER_PLATFORMS.include? image_key
|
53
|
+
puts "Expecting #{image_key} test using docker on travis"
|
54
|
+
else
|
55
|
+
puts "::warning::Cannot find image for #{image_key}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
puts "::set-output name=matrix::#{JSON.generate(matrix)}"
|
61
|
+
|
62
|
+
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['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
12
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
13
13
|
manifest_file_location = create_manifest_file(manifest)
|
14
14
|
apply_manifest(nil, expect_failures: false, manifest_file_location: manifest_file_location)
|
15
15
|
apply_manifest(nil, catch_changes: true, manifest_file_location: manifest_file_location)
|
@@ -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['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
42
|
+
ENV['HTTP_X_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['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
120
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
121
121
|
span.add_field('litmus.manifest', manifest)
|
122
122
|
|
123
123
|
require 'tmpdir'
|
@@ -135,7 +135,7 @@ module PuppetLitmus::PuppetHelpers
|
|
135
135
|
span.add_field('litmus.node_name', target_node_name)
|
136
136
|
add_platform_field(inventory_hash, target_node_name)
|
137
137
|
|
138
|
-
manifest_file_location =
|
138
|
+
manifest_file_location = File.basename(manifest_file)
|
139
139
|
bolt_result = upload_file(manifest_file.path, manifest_file_location, target_node_name, options: {}, config: nil, inventory: inventory_hash)
|
140
140
|
span.add_field('litmus.bolt_result', bolt_result)
|
141
141
|
raise bolt_result.first['value'].to_s unless bolt_result.first['status'] == 'success'
|
@@ -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['HTTP_X_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['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
194
|
+
ENV['HTTP_X_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['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
231
|
+
ENV['HTTP_X_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['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
283
|
+
ENV['HTTP_X_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['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
351
|
+
ENV['HTTP_X_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['HTTP_X_HONEYCOMB_TRACE'] = process_span.to_trace_header
|
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
|
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')
|
@@ -42,7 +48,7 @@ end
|
|
42
48
|
module PuppetLitmus::RakeHelper
|
43
49
|
# DEFAULT_CONFIG_DATA should be frozen for our safety, but it needs to work around https://github.com/puppetlabs/bolt/pull/1696
|
44
50
|
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
|
51
|
+
SUPPORTED_PROVISIONERS ||= %w[abs docker docker_exp provision_service vagrant vmpooler].freeze
|
46
52
|
|
47
53
|
# Gets a string representing the operating system and version.
|
48
54
|
#
|
@@ -90,7 +96,7 @@ module PuppetLitmus::RakeHelper
|
|
90
96
|
# @return [Object] the standard out stream.
|
91
97
|
def run_local_command(command)
|
92
98
|
Honeycomb.start_span(name: 'litmus.run_local_command') do |span|
|
93
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
99
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
94
100
|
span.add_field('litmus.command', command)
|
95
101
|
|
96
102
|
require 'open3'
|
@@ -113,14 +119,14 @@ module PuppetLitmus::RakeHelper
|
|
113
119
|
|
114
120
|
Honeycomb.add_field_to_trace('litmus.provisioner', provisioner)
|
115
121
|
Honeycomb.start_span(name: 'litmus.provision') do |span|
|
116
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
122
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
117
123
|
span.add_field('litmus.platform', platform)
|
118
124
|
span.add_field('litmus.inventory', params['inventory'])
|
119
125
|
span.add_field('litmus.config', DEFAULT_CONFIG_DATA)
|
120
126
|
|
121
127
|
bolt_result = run_task(provisioner_task(provisioner), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
|
122
|
-
|
123
128
|
span.add_field('litmus.node_name', bolt_result&.first&.dig('value', 'node_name'))
|
129
|
+
raise_bolt_errors(bolt_result, "provisioning of #{platform} failed.")
|
124
130
|
|
125
131
|
bolt_result
|
126
132
|
end
|
@@ -142,7 +148,7 @@ module PuppetLitmus::RakeHelper
|
|
142
148
|
|
143
149
|
def tear_down_nodes(targets, inventory_hash)
|
144
150
|
Honeycomb.start_span(name: 'litmus.tear_down_nodes') do |span|
|
145
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
151
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
146
152
|
span.add_field('litmus.targets', targets)
|
147
153
|
|
148
154
|
include ::BoltSpec::Run
|
@@ -162,7 +168,7 @@ module PuppetLitmus::RakeHelper
|
|
162
168
|
|
163
169
|
def tear_down(node_name, inventory_hash)
|
164
170
|
Honeycomb.start_span(name: 'litmus.tear_down') do |span|
|
165
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
171
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
166
172
|
# how do we know what provisioner to use
|
167
173
|
|
168
174
|
span.add_field('litmus.node_name', node_name)
|
@@ -170,13 +176,15 @@ module PuppetLitmus::RakeHelper
|
|
170
176
|
|
171
177
|
params = { 'action' => 'tear_down', 'node_name' => node_name, 'inventory' => Dir.pwd }
|
172
178
|
node_facts = facts_from_node(inventory_hash, node_name)
|
173
|
-
run_task(provisioner_task(node_facts['provisioner']), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
|
179
|
+
bolt_result = run_task(provisioner_task(node_facts['provisioner']), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
|
180
|
+
raise_bolt_errors(bolt_result, "tear_down of #{node_name} failed.")
|
181
|
+
bolt_result
|
174
182
|
end
|
175
183
|
end
|
176
184
|
|
177
185
|
def install_agent(collection, targets, inventory_hash)
|
178
186
|
Honeycomb.start_span(name: 'litmus.install_agent') do |span|
|
179
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
187
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
180
188
|
span.add_field('litmus.collection', collection)
|
181
189
|
span.add_field('litmus.targets', targets)
|
182
190
|
|
@@ -184,7 +192,6 @@ module PuppetLitmus::RakeHelper
|
|
184
192
|
params = if collection.nil?
|
185
193
|
{}
|
186
194
|
else
|
187
|
-
Honeycomb.current_span.add_field('litmus.collection', collection)
|
188
195
|
{ 'collection' => collection }
|
189
196
|
end
|
190
197
|
raise "puppet_agent was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" \
|
@@ -200,81 +207,102 @@ module PuppetLitmus::RakeHelper
|
|
200
207
|
def configure_path(inventory_hash)
|
201
208
|
results = []
|
202
209
|
# fix the path on ssh_nodes
|
203
|
-
unless inventory_hash['groups'].select { |group| group['name'] == 'ssh_nodes' }.size.zero?
|
204
|
-
results
|
205
|
-
|
210
|
+
unless inventory_hash['groups'].select { |group| group['name'] == 'ssh_nodes' && !group['targets'].empty? }.size.zero?
|
211
|
+
results << run_command('echo PATH="$PATH:/opt/puppetlabs/puppet/bin" > /etc/environment',
|
212
|
+
'ssh_nodes', config: nil, inventory: inventory_hash)
|
213
|
+
end
|
214
|
+
unless inventory_hash['groups'].select { |group| group['name'] == 'winrm_nodes' && !group['targets'].empty? }.size.zero?
|
215
|
+
results << run_command('[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\Puppet Labs\Puppet\bin;C:\Program Files (x86)\Puppet Labs\Puppet\bin", "Machine")',
|
216
|
+
'winrm_nodes', config: nil, inventory: inventory_hash)
|
206
217
|
end
|
207
218
|
results
|
208
219
|
end
|
209
220
|
|
221
|
+
# Build the module in `module_dir` and put the resulting compressed tarball into `target_dir`.
|
222
|
+
#
|
210
223
|
# @param opts Hash of options to build the module
|
211
224
|
# @param module_dir [String] The path of the module to build. If missing defaults to Dir.pwd
|
212
|
-
# @param target_dir [String] The path the module will be built into. The default is <
|
225
|
+
# @param target_dir [String] The path the module will be built into. The default is <module_dir>/pkg
|
213
226
|
# @return [String] The path to the built module
|
214
227
|
def build_module(module_dir = nil, target_dir = nil)
|
215
228
|
require 'puppet/modulebuilder'
|
216
229
|
|
217
|
-
|
218
|
-
|
230
|
+
module_dir ||= Dir.pwd
|
231
|
+
target_dir ||= File.join(source_dir, 'pkg')
|
232
|
+
|
233
|
+
puts "Building '#{module_dir}' into '#{target_dir}'"
|
234
|
+
builder = Puppet::Modulebuilder::Builder.new(module_dir, target_dir, nil)
|
219
235
|
|
220
|
-
builder = Puppet::Modulebuilder::Builder.new(source_dir, dest_dir, nil)
|
221
236
|
# Force the metadata to be read. Raises if metadata could not be found
|
222
237
|
_metadata = builder.metadata
|
223
238
|
|
224
239
|
builder.build
|
225
240
|
end
|
226
241
|
|
227
|
-
|
242
|
+
# Builds all the modules in a specified directory
|
243
|
+
#
|
244
|
+
# @param source_dir [String] the directory to get the modules from
|
245
|
+
# @param target_dir [String] temporary location to store tarballs before uploading. This directory will be cleaned before use. The default is <source_dir>/pkg
|
246
|
+
# @return [Array] an array of module tars' filenames
|
247
|
+
def build_modules_in_dir(source_dir, target_dir = nil)
|
248
|
+
target_dir ||= File.join(Dir.pwd, 'pkg')
|
249
|
+
# remove old build dir if exists, before we build afresh
|
250
|
+
FileUtils.rm_rf(target_dir) if File.directory?(target_dir)
|
251
|
+
|
252
|
+
module_tars = Dir.entries(source_dir).map do |entry|
|
253
|
+
next if ['.', '..'].include? entry
|
254
|
+
|
255
|
+
module_dir = File.join(source_dir, entry)
|
256
|
+
next unless File.directory? module_dir
|
257
|
+
|
258
|
+
build_module(module_dir, target_dir)
|
259
|
+
end
|
260
|
+
module_tars.compact
|
261
|
+
end
|
262
|
+
|
263
|
+
# @deprecated Use `build_modules_in_dir` instead
|
264
|
+
def build_modules_in_folder(source_folder)
|
265
|
+
build_modules_in_dir(source_folder)
|
266
|
+
end
|
267
|
+
|
268
|
+
# Install a specific module tarball to the specified target.
|
269
|
+
# This method installs dependencies using a forge repository.
|
270
|
+
#
|
271
|
+
# @param inventory_hash [Hash] the pre-loaded inventory
|
272
|
+
# @param target_node_name [String] the name of the target where the module should be installed
|
273
|
+
# @param module_tar [String] the filename of the module tarball to upload
|
274
|
+
# @param module_repository [String] the URL for the forge to use for downloading modules. Defaults to the public Forge API.
|
275
|
+
# @param ignore_dependencies [Boolean] flag used to ignore module dependencies defaults to false.
|
276
|
+
# @return a bolt result
|
277
|
+
def install_module(inventory_hash, target_node_name, module_tar, module_repository = nil, ignore_dependencies = false) # rubocop:disable Style/OptionalBooleanParameter
|
228
278
|
Honeycomb.start_span(name: 'install_module') do |span|
|
229
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
279
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
230
280
|
span.add_field('litmus.target_node_name', target_node_name)
|
231
281
|
span.add_field('litmus.module_tar', module_tar)
|
232
282
|
|
233
|
-
# make sure the
|
283
|
+
# make sure the module to install is not installed
|
234
284
|
# otherwise `puppet module install` might silently skip it
|
235
|
-
|
285
|
+
module_name = File.basename(module_tar, '.tar.gz').split('-', 3)[0..1].join('-')
|
286
|
+
uninstall_module(inventory_hash.clone, target_node_name, module_name, force: true)
|
236
287
|
|
237
288
|
include ::BoltSpec::Run
|
238
289
|
|
239
290
|
target_nodes = find_targets(inventory_hash, target_node_name)
|
240
291
|
span.add_field('litmus.target_nodes', target_nodes)
|
241
|
-
bolt_result = upload_file(module_tar,
|
292
|
+
bolt_result = upload_file(module_tar, File.basename(module_tar), target_nodes, options: {}, config: nil, inventory: inventory_hash.clone)
|
242
293
|
raise_bolt_errors(bolt_result, 'Failed to upload module.')
|
243
294
|
|
244
|
-
|
295
|
+
module_repository_opts = "--module_repository '#{module_repository}'" unless module_repository.nil?
|
296
|
+
install_module_command = "puppet module install #{module_repository_opts} #{File.basename(module_tar)}"
|
297
|
+
install_module_command += ' --ignore-dependencies --force' if ignore_dependencies.to_s.downcase == 'true'
|
245
298
|
span.add_field('litmus.install_module_command', install_module_command)
|
246
299
|
|
247
300
|
bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash.clone)
|
248
|
-
raise_bolt_errors(bolt_result, "Installation of package #{module_tar} failed.")
|
301
|
+
raise_bolt_errors(bolt_result, "Installation of package #{File.basename(module_tar)} failed.")
|
249
302
|
bolt_result
|
250
303
|
end
|
251
304
|
end
|
252
305
|
|
253
|
-
# Builds all the modules in a specified module
|
254
|
-
#
|
255
|
-
# @param source_folder [String] the folder to get the modules from
|
256
|
-
# @return [Array] an array of module tar's
|
257
|
-
def build_modules_in_folder(source_folder)
|
258
|
-
folder_list = Dir.entries(source_folder).reject { |f| File.directory? f }
|
259
|
-
module_tars = []
|
260
|
-
|
261
|
-
target_dir = File.join(Dir.pwd, 'pkg')
|
262
|
-
# remove old build folder if exists, before we build afresh
|
263
|
-
FileUtils.rm_rf(target_dir) if File.directory?(target_dir)
|
264
|
-
|
265
|
-
folder_list.each do |folder|
|
266
|
-
folder_handle = Dir.open(File.join(source_folder, folder))
|
267
|
-
next if File.symlink?(folder_handle)
|
268
|
-
|
269
|
-
module_dir = folder_handle.path
|
270
|
-
|
271
|
-
# build_module
|
272
|
-
module_tar = build_module(module_dir, target_dir)
|
273
|
-
module_tars.push(File.new(module_tar))
|
274
|
-
end
|
275
|
-
module_tars
|
276
|
-
end
|
277
|
-
|
278
306
|
def metadata_module_name
|
279
307
|
require 'json'
|
280
308
|
raise 'Could not find metadata.json' unless File.exist?(File.join(Dir.pwd, 'metadata.json'))
|
@@ -285,33 +313,46 @@ module PuppetLitmus::RakeHelper
|
|
285
313
|
metadata['name']
|
286
314
|
end
|
287
315
|
|
316
|
+
# Uninstall a module from a specified target
|
317
|
+
# @param inventory_hash [Hash] the pre-loaded inventory
|
318
|
+
# @param target_node_name [String] the name of the target where the module should be uninstalled
|
319
|
+
# @param module_to_remove [String] the name of the module to remove. Defaults to the module under test.
|
320
|
+
# @param opts [Hash] additional options to pass on to `puppet module uninstall`
|
288
321
|
def uninstall_module(inventory_hash, target_node_name, module_to_remove = nil, **opts)
|
289
322
|
include ::BoltSpec::Run
|
290
323
|
module_name = module_to_remove || metadata_module_name
|
291
324
|
target_nodes = find_targets(inventory_hash, target_node_name)
|
292
325
|
install_module_command = "puppet module uninstall #{module_name}"
|
293
326
|
install_module_command += ' --force' if opts[:force]
|
294
|
-
run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
|
327
|
+
bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash)
|
328
|
+
# `puppet module uninstall --force` fails if the module is not installed. Ignore errors when force is set
|
329
|
+
raise_bolt_errors(bolt_result, "uninstalling #{module_name} failed.") unless opts[:force]
|
330
|
+
bolt_result
|
295
331
|
end
|
296
332
|
|
297
333
|
def check_connectivity?(inventory_hash, target_node_name)
|
298
334
|
Honeycomb.start_span(name: 'litmus.check_connectivity') do |span|
|
299
|
-
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
335
|
+
ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
|
300
336
|
# if we're only checking connectivity for a single node
|
301
337
|
if target_node_name
|
302
|
-
span.add_field('litmus.
|
338
|
+
span.add_field('litmus.target_node_name', target_node_name)
|
303
339
|
add_platform_field(inventory_hash, target_node_name)
|
304
340
|
end
|
305
341
|
|
306
342
|
include ::BoltSpec::Run
|
307
343
|
target_nodes = find_targets(inventory_hash, target_node_name)
|
344
|
+
puts "Checking connectivity for #{target_nodes.inspect}"
|
345
|
+
span.add_field('litmus.target_nodes', target_nodes)
|
346
|
+
|
308
347
|
results = run_command('cd .', target_nodes, config: nil, inventory: inventory_hash)
|
309
348
|
span.add_field('litmus.bolt_result', results)
|
310
349
|
failed = []
|
311
|
-
results.each do |result|
|
312
|
-
|
350
|
+
results.reject { |r| r['status'] == 'success' }.each do |result|
|
351
|
+
puts "Failure connecting to #{result['target']}:\n#{result.inspect}"
|
352
|
+
failed.push(result['target'])
|
313
353
|
end
|
314
|
-
span.add_field('litmus.
|
354
|
+
span.add_field('litmus.connectivity_success', results.select { |r| r['status'] == 'success' })
|
355
|
+
span.add_field('litmus.connectivity_failure', results.reject { |r| r['status'] == 'success' })
|
315
356
|
raise "Connectivity has failed on: #{failed}" unless failed.length.zero?
|
316
357
|
|
317
358
|
true
|
@@ -330,7 +371,7 @@ module PuppetLitmus::RakeHelper
|
|
330
371
|
# Parse out errors messages in result set returned by Bolt command.
|
331
372
|
#
|
332
373
|
# @param result_set [Array] result set returned by Bolt command.
|
333
|
-
# @return [Hash]
|
374
|
+
# @return [Hash] Errors grouped by target.
|
334
375
|
def check_bolt_errors(result_set)
|
335
376
|
errors = {}
|
336
377
|
# iterate through each error
|
@@ -341,8 +382,7 @@ module PuppetLitmus::RakeHelper
|
|
341
382
|
|
342
383
|
target = target_result['target']
|
343
384
|
# get some info from error
|
344
|
-
|
345
|
-
errors[target] = error_msg
|
385
|
+
errors[target] = target_result['value']
|
346
386
|
end
|
347
387
|
errors
|
348
388
|
end
|
@@ -355,9 +395,56 @@ module PuppetLitmus::RakeHelper
|
|
355
395
|
errors = check_bolt_errors(result_set)
|
356
396
|
|
357
397
|
unless errors.empty?
|
358
|
-
|
398
|
+
formatted_results = errors.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
|
399
|
+
raise "#{error_msg}\nResults:\n#{formatted_results}}"
|
359
400
|
end
|
360
401
|
|
361
402
|
nil
|
362
403
|
end
|
404
|
+
|
405
|
+
def start_spinner(message)
|
406
|
+
if (ENV['CI'] || '').downcase == 'true'
|
407
|
+
puts message
|
408
|
+
spinner = Thread.new do
|
409
|
+
# CI systems are strange beasts, we only output a '.' every wee while to keep the terminal alive.
|
410
|
+
loop do
|
411
|
+
printf '.'
|
412
|
+
sleep(10)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
else
|
416
|
+
require 'tty-spinner'
|
417
|
+
spinner = TTY::Spinner.new("[:spinner] #{message}")
|
418
|
+
spinner.auto_spin
|
419
|
+
end
|
420
|
+
spinner
|
421
|
+
end
|
422
|
+
|
423
|
+
def stop_spinner(spinner)
|
424
|
+
if (ENV['CI'] || '').downcase == 'true'
|
425
|
+
Thread.kill(spinner)
|
426
|
+
else
|
427
|
+
spinner.success
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
require 'retryable'
|
432
|
+
|
433
|
+
Retryable.configure do |config|
|
434
|
+
config.sleep = ->(n) { (1.5**n) + Random.rand(0.5) }
|
435
|
+
# config.log_method = ->(retries, exception) do
|
436
|
+
# Logger.new($stdout).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
|
437
|
+
# end
|
438
|
+
end
|
439
|
+
|
440
|
+
class LitmusTimeoutError < StandardError; end
|
441
|
+
|
442
|
+
def with_retries(options: { tries: Float::INFINITY }, max_wait_minutes: 5)
|
443
|
+
stop = Time.now + (max_wait_minutes * 60)
|
444
|
+
Retryable.retryable(options.merge(not: [LitmusTimeoutError])) do
|
445
|
+
raise LitmusTimeoutError if Time.now > stop
|
446
|
+
|
447
|
+
yield
|
448
|
+
end
|
449
|
+
end
|
363
450
|
end
|