puppet_litmus 0.18.4 → 0.23.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60fc5eef658171b749a429d8d302e94c05e8812858206124e90c915a035a8e4e
4
- data.tar.gz: 2016992ffee484e5c2ab2b92e0011d98678b21bc1c0e62dd1d75c857ad50db65
3
+ metadata.gz: 7c3c6a488e0f0aa8b61b2f1d6678fd54d093aa11693b8ab004f57205f72049c9
4
+ data.tar.gz: 65ce0b461f8c1b0683f9b608aa08ede12d44794d766d007da423472fbb856535
5
5
  SHA512:
6
- metadata.gz: f4bb7954ab40c5345dacb291f0fd82a745c7ac5da4e939e07323e23f6c06b7c4758303cc5f9faccf75f67134337ad409edb429ebb438ffe879682460722cacb3
7
- data.tar.gz: 3367726403b6691edbb3439377617f894fdffec96f964965bb1e62aa175ade0494920ec166b49da367449c331a9ca6f8e7f87ed2ede707bb7cd7410077e63c38
6
+ metadata.gz: 2191d39436024913ac79e2e4e1ea16306f4c4ece91c6ff3782a0d67eeda849ccc1079ac8976ed1c7e1edb5d9cb5fd92b86ef1a2f9261cde1134710267e08f29c
7
+ data.tar.gz: 0661730c8fd0a5e4152840ff84edf917bd76d46691d580981d1d265d957457358aa4afa04ba6f573db9a97dfec2cb10fee74a5638249e4f31e40f7893b21e5f1
@@ -0,0 +1,98 @@
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-7' => 'rhel-7',
10
+ 'RedHat-8' => 'rhel-8',
11
+ 'SLES-12' => 'sles-12',
12
+ 'SLES-15' => 'sles-15',
13
+ 'Windows-2012 R2' => 'windows-2012-r2-core',
14
+ 'Windows-2016' => 'windows-2016',
15
+ 'Windows-2019' => 'windows-2019-core',
16
+ }.freeze
17
+
18
+ DOCKER_PLATFORMS = [
19
+ 'CentOS-6',
20
+ 'CentOS-7',
21
+ 'CentOS-8',
22
+ 'Debian-10',
23
+ 'Debian-8',
24
+ 'Debian-9',
25
+ 'OracleLinux-6',
26
+ 'OracleLinux-7',
27
+ 'Scientific-6',
28
+ 'Scientific-7',
29
+ 'Ubuntu-14.04',
30
+ 'Ubuntu-16.04',
31
+ 'Ubuntu-18.04',
32
+ 'Ubuntu-20.04',
33
+ ].freeze
34
+
35
+ # This table uses the latest version in each collection for accurate
36
+ # comparison when evaluating puppet requirements from the metadata
37
+ COLLECTION_TABLE = {
38
+ '5.5.22' => 'puppet5',
39
+ '6.19.1' => 'puppet6-nightly',
40
+ '7.0.0' => 'puppet7-nightly',
41
+ }.freeze
42
+
43
+ matrix = {
44
+ platform: [],
45
+ collection: [],
46
+ }
47
+
48
+ metadata = JSON.parse(File.read('metadata.json'))
49
+ # Set platforms based on declared operating system support
50
+ metadata['operatingsystem_support'].sort_by { |a| a['operatingsystem'] }.each do |sup|
51
+ os = sup['operatingsystem']
52
+ sup['operatingsystemrelease'].sort_by { |a| a.to_i }.each do |ver|
53
+ image_key = "#{os}-#{ver}"
54
+ if IMAGE_TABLE.key? image_key
55
+ matrix[:platform] << IMAGE_TABLE[image_key]
56
+ elsif DOCKER_PLATFORMS.include? image_key
57
+ puts "Expecting #{image_key} test using docker on travis"
58
+ else
59
+ puts "::warning::Cannot find image for #{image_key}"
60
+ end
61
+ end
62
+ end
63
+
64
+ # Set collections based on puppet version requirements
65
+ if metadata.key?('requirements') && metadata['requirements'].length.positive?
66
+ metadata['requirements'].each do |req|
67
+ next unless req.key?('name') && req.key?('version_requirement') && req['name'] == 'puppet'
68
+
69
+ ver_regexp = %r{^([>=<]{1,2})\s*([\d.]+)\s+([>=<]{1,2})\s*([\d.]+)$}
70
+ match = ver_regexp.match(req['version_requirement'])
71
+ if match.nil?
72
+ puts "::warning::Didn't recognize version_requirement '#{req['version_requirement']}'"
73
+ break
74
+ end
75
+
76
+ cmp_one, ver_one, cmp_two, ver_two = match.captures
77
+ reqs = ["#{cmp_one} #{ver_one}", "#{cmp_two} #{ver_two}"]
78
+
79
+ COLLECTION_TABLE.each do |key, val|
80
+ if Gem::Requirement.create(reqs).satisfied_by?(Gem::Version.new(key))
81
+ matrix[:collection] << val
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ # Set to defaults (all collections) if no matches are found
88
+ if matrix[:collection].empty?
89
+ matrix[:collection] = COLLECTION_TABLE.values
90
+ end
91
+
92
+ # Just to make sure there aren't any duplicates
93
+ matrix[:platform] = matrix[:platform].uniq.sort
94
+ matrix[:collection] = matrix[:collection].uniq.sort
95
+
96
+ puts "::set-output name=matrix::#{JSON.generate(matrix)}"
97
+
98
+ puts "Created matrix with #{matrix[:platform].length * matrix[:collection].length} cells."
@@ -17,10 +17,7 @@ module PuppetLitmus::InventoryManipulation
17
17
  end
18
18
  raise "There is no inventory file at '#{inventory_full_path}'." unless File.exist?(inventory_full_path)
19
19
 
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.dig('version').nil? || (inventory_hash['version'] < 2)
22
-
23
- inventory_hash
20
+ YAML.load_file(inventory_full_path)
24
21
  end
25
22
 
26
23
  # Provide a default hash for executing against localhost
@@ -28,7 +25,6 @@ module PuppetLitmus::InventoryManipulation
28
25
  # @return [Hash] inventory.yaml hash containing only an entry for localhost
29
26
  def localhost_inventory_hash
30
27
  {
31
- 'version' => 2,
32
28
  'groups' => [
33
29
  {
34
30
  'name' => 'local',
@@ -50,12 +46,11 @@ module PuppetLitmus::InventoryManipulation
50
46
  # @param targets [Array]
51
47
  # @return [Array] array of targets.
52
48
  def find_targets(inventory_hash, targets)
53
- targets = if targets.nil?
54
- inventory_hash.to_s.scan(%r{uri"=>"(\S*)"}).flatten
55
- else
56
- [targets]
57
- end
58
- targets
49
+ if targets.nil?
50
+ inventory_hash.to_s.scan(%r{uri"=>"(\S*)"}).flatten
51
+ else
52
+ [targets]
53
+ end
59
54
  end
60
55
 
61
56
  # Determines if a node_name exists in a group in the inventory_hash.
@@ -263,7 +258,12 @@ module PuppetLitmus::InventoryManipulation
263
258
  # @param inventory_hash [Hash] hash of the inventory.yaml file
264
259
  # @param node_name [String] node of nodes to limit the search for the node_name in
265
260
  def add_platform_field(inventory_hash, node_name)
266
- facts = facts_from_node(inventory_hash, node_name)
261
+ facts = begin
262
+ facts_from_node(inventory_hash, node_name)
263
+ rescue StandardError => e
264
+ warn e
265
+ {}
266
+ end
267
267
  Honeycomb.current_span.add_field('litmus.platform', facts&.dig('platform'))
268
268
  end
269
269
  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['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)
@@ -30,7 +30,7 @@ module PuppetLitmus::PuppetHelpers
30
30
  # :catch_failures [Boolean] (false) We're after only complete success so allow exit codes 0 and 2 only.
31
31
  # :expect_failures [Boolean] (false) We're after failures specifically so allow exit codes 1, 4, and 6 only.
32
32
  # :manifest_file_location [Path] The place on the target system.
33
- # :hiera_config [Path] The path to the hiera.yaml configuration on the runner.
33
+ # :hiera_config [Path] The path to the hiera.yaml configuration on the target.
34
34
  # :prefix_command [String] prefixes the puppet apply command; eg "export LANGUAGE='ja'".
35
35
  # :trace [Boolean] run puppet apply with the trace flag (defaults to `true`).
36
36
  # :debug [Boolean] run puppet apply with the debug flag.
@@ -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['HONEYCOMB_TRACE'] = span.to_trace_header
43
43
  span.add_field('litmus.manifest', manifest)
44
44
  span.add_field('litmus.opts', opts)
45
45
 
@@ -83,11 +83,22 @@ module PuppetLitmus::PuppetHelpers
83
83
  command_to_run += ' --noop' if !opts[:noop].nil? && (opts[:noop] == true)
84
84
  command_to_run += ' --detailed-exitcodes' if use_detailed_exit_codes == true
85
85
 
86
- span.add_field('litmus.command_to_run', command_to_run)
87
86
  span.add_field('litmus.target_node_name', target_node_name)
88
- bolt_result = run_command(command_to_run, target_node_name, config: nil, inventory: inventory_hash)
89
- span.add_field('litmus.bolt_result', bolt_result)
90
87
 
88
+ if os[:family] == 'windows'
89
+ # IAC-1365 - Workaround for BOLT-1535 and bolt issue #1650
90
+ command_to_run = "try { #{command_to_run}; exit $LASTEXITCODE } catch { write-error $_ ; exit 1 }"
91
+ span.add_field('litmus.command_to_run', command_to_run)
92
+ bolt_result = Tempfile.open(['temp', '.ps1']) do |script|
93
+ script.write(command_to_run)
94
+ script.close
95
+ run_script(script.path, target_node_name, [], options: {}, config: nil, inventory: inventory_hash)
96
+ end
97
+ else
98
+ span.add_field('litmus.command_to_run', command_to_run)
99
+ bolt_result = run_command(command_to_run, target_node_name, config: nil, inventory: inventory_hash)
100
+ end
101
+ span.add_field('litmus.bolt_result', bolt_result)
91
102
  result = OpenStruct.new(exit_code: bolt_result.first['value']['exit_code'],
92
103
  stdout: bolt_result.first['value']['stdout'],
93
104
  stderr: bolt_result.first['value']['stderr'])
@@ -117,7 +128,7 @@ module PuppetLitmus::PuppetHelpers
117
128
  # @return [String] The path to the location of the manifest.
118
129
  def create_manifest_file(manifest)
119
130
  Honeycomb.start_span(name: 'litmus.create_manifest_file') do |span|
120
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
131
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
121
132
  span.add_field('litmus.manifest', manifest)
122
133
 
123
134
  require 'tmpdir'
@@ -147,6 +158,42 @@ module PuppetLitmus::PuppetHelpers
147
158
  end
148
159
  end
149
160
 
161
+ # Writes a string variable to a file on a target node at a specified path.
162
+ #
163
+ # @param content [String] String data to write to the file.
164
+ # @param destination [String] The path on the target node to write the file.
165
+ # @return [Bool] Success. The file was succesfully writtne on the target.
166
+ def write_file(content, destination)
167
+ Honeycomb.start_span(name: 'litmus.write_file') do |span|
168
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
169
+ span.add_field('litmus.destination', destination)
170
+
171
+ require 'tmpdir'
172
+ target_node_name = ENV['TARGET_HOST']
173
+
174
+ Tempfile.create('litmus') do |tmp_file|
175
+ tmp_file.write(content)
176
+ tmp_file.flush
177
+ if target_node_name.nil? || target_node_name == 'localhost'
178
+ require 'fileutils'
179
+ # no need to transfer
180
+ FileUtils.cp(tmp_file.path, destination)
181
+ else
182
+ # transfer to TARGET_HOST
183
+ inventory_hash = inventory_hash_from_inventory_file
184
+ span.add_field('litmus.node_name', target_node_name)
185
+ add_platform_field(inventory_hash, target_node_name)
186
+
187
+ bolt_result = upload_file(tmp_file.path, destination, target_node_name, options: {}, config: nil, inventory: inventory_hash)
188
+ span.add_field('litmus.bolt_result.file_upload', bolt_result)
189
+ raise bolt_result.first['value'].to_s unless bolt_result.first['status'] == 'success'
190
+ end
191
+ end
192
+
193
+ true
194
+ end
195
+ end
196
+
150
197
  # Runs a command against the target system
151
198
  #
152
199
  # @param command_to_run [String] The command to execute.
@@ -155,7 +202,7 @@ module PuppetLitmus::PuppetHelpers
155
202
  # @return [Object] A result object from the command.
156
203
  def run_shell(command_to_run, opts = {})
157
204
  Honeycomb.start_span(name: 'litmus.run_shell') do |span|
158
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
205
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
159
206
  span.add_field('litmus.command_to_run', command_to_run)
160
207
  span.add_field('litmus.opts', opts)
161
208
 
@@ -192,7 +239,7 @@ module PuppetLitmus::PuppetHelpers
192
239
  # @return [Object] A result object from the command.
193
240
  def bolt_upload_file(source, destination, opts = {}, options = {})
194
241
  Honeycomb.start_span(name: 'litmus.bolt_upload_file') do |span|
195
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
242
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
196
243
  span.add_field('litmus.source', source)
197
244
  span.add_field('litmus.destination', destination)
198
245
  span.add_field('litmus.opts', opts)
@@ -244,7 +291,7 @@ module PuppetLitmus::PuppetHelpers
244
291
  # @return [Object] A result object from the task.The values available are stdout, stderr and result.
245
292
  def run_bolt_task(task_name, params = {}, opts = {})
246
293
  Honeycomb.start_span(name: 'litmus.run_task') do |span|
247
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
294
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
248
295
  span.add_field('litmus.task_name', task_name)
249
296
  span.add_field('litmus.params', params)
250
297
  span.add_field('litmus.opts', opts)
@@ -312,7 +359,7 @@ module PuppetLitmus::PuppetHelpers
312
359
  # @return [Object] A result object from the script run.
313
360
  def bolt_run_script(script, opts = {}, arguments: [])
314
361
  Honeycomb.start_span(name: 'litmus.bolt_run_script') do |span|
315
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
362
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
316
363
  span.add_field('litmus.script', script)
317
364
  span.add_field('litmus.opts', opts)
318
365
  span.add_field('litmus.arguments', arguments)
@@ -382,7 +429,7 @@ module PuppetLitmus::PuppetHelpers
382
429
 
383
430
  # Return the stdout of the puppet run
384
431
  def puppet_output(bolt_result)
385
- bolt_result.dig(0, 'value', 'stderr').to_s << \
432
+ bolt_result.dig(0, 'value', 'stderr').to_s + \
386
433
  bolt_result.dig(0, 'value', 'stdout').to_s
387
434
  end
388
435
 
@@ -9,8 +9,8 @@ Honeycomb.configure do |config|
9
9
  config.client = Libhoney::NullClient.new
10
10
  end
11
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
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
14
14
  Honeycomb.add_field_to_trace('litmus.pid', Process.pid)
15
15
  if defined? PuppetLitmus::VERSION
16
16
  Honeycomb.add_field_to_trace('litmus.version', PuppetLitmus::VERSION)
@@ -41,6 +41,13 @@ elsif ENV['GITHUB_ACTIONS'] == 'true'
41
41
  Honeycomb.add_field_to_trace('ci.sha', ENV['GITHUB_SHA'])
42
42
  end
43
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
44
51
  process_span.send
45
52
  end
46
53
 
@@ -48,7 +55,7 @@ end
48
55
  module PuppetLitmus::RakeHelper
49
56
  # DEFAULT_CONFIG_DATA should be frozen for our safety, but it needs to work around https://github.com/puppetlabs/bolt/pull/1696
50
57
  DEFAULT_CONFIG_DATA ||= { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') } # .freeze # rubocop:disable Style/MutableConstant
51
- SUPPORTED_PROVISIONERS ||= %w[abs docker docker_exp vagrant vmpooler].freeze
58
+ SUPPORTED_PROVISIONERS ||= %w[abs docker docker_exp provision_service vagrant vmpooler].freeze
52
59
 
53
60
  # Gets a string representing the operating system and version.
54
61
  #
@@ -96,7 +103,7 @@ module PuppetLitmus::RakeHelper
96
103
  # @return [Object] the standard out stream.
97
104
  def run_local_command(command)
98
105
  Honeycomb.start_span(name: 'litmus.run_local_command') do |span|
99
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
106
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
100
107
  span.add_field('litmus.command', command)
101
108
 
102
109
  require 'open3'
@@ -119,13 +126,18 @@ module PuppetLitmus::RakeHelper
119
126
 
120
127
  Honeycomb.add_field_to_trace('litmus.provisioner', provisioner)
121
128
  Honeycomb.start_span(name: 'litmus.provision') do |span|
122
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
129
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
123
130
  span.add_field('litmus.platform', platform)
124
- span.add_field('litmus.inventory', params['inventory'])
131
+
132
+ task_name = provisioner_task(provisioner)
133
+ span.add_field('litmus.task_name', task_name)
134
+ span.add_field('litmus.params', params)
125
135
  span.add_field('litmus.config', DEFAULT_CONFIG_DATA)
126
136
 
127
- bolt_result = run_task(provisioner_task(provisioner), 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
137
+ bolt_result = run_task(task_name, 'localhost', params, config: DEFAULT_CONFIG_DATA, inventory: nil)
138
+ span.add_field('litmus.result', bolt_result)
128
139
  span.add_field('litmus.node_name', bolt_result&.first&.dig('value', 'node_name'))
140
+
129
141
  raise_bolt_errors(bolt_result, "provisioning of #{platform} failed.")
130
142
 
131
143
  bolt_result
@@ -148,7 +160,7 @@ module PuppetLitmus::RakeHelper
148
160
 
149
161
  def tear_down_nodes(targets, inventory_hash)
150
162
  Honeycomb.start_span(name: 'litmus.tear_down_nodes') do |span|
151
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
163
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
152
164
  span.add_field('litmus.targets', targets)
153
165
 
154
166
  include ::BoltSpec::Run
@@ -160,6 +172,18 @@ module PuppetLitmus::RakeHelper
160
172
  next if node_name == 'litmus_localhost'
161
173
 
162
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
+
163
187
  results[node_name] = result unless result == []
164
188
  end
165
189
  results
@@ -168,7 +192,7 @@ module PuppetLitmus::RakeHelper
168
192
 
169
193
  def tear_down(node_name, inventory_hash)
170
194
  Honeycomb.start_span(name: 'litmus.tear_down') do |span|
171
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
195
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
172
196
  # how do we know what provisioner to use
173
197
 
174
198
  span.add_field('litmus.node_name', node_name)
@@ -184,7 +208,7 @@ module PuppetLitmus::RakeHelper
184
208
 
185
209
  def install_agent(collection, targets, inventory_hash)
186
210
  Honeycomb.start_span(name: 'litmus.install_agent') do |span|
187
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
211
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
188
212
  span.add_field('litmus.collection', collection)
189
213
  span.add_field('litmus.targets', targets)
190
214
 
@@ -192,7 +216,6 @@ module PuppetLitmus::RakeHelper
192
216
  params = if collection.nil?
193
217
  {}
194
218
  else
195
- Honeycomb.current_span.add_field('litmus.collection', collection)
196
219
  { 'collection' => collection }
197
220
  end
198
221
  raise "puppet_agent was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" \
@@ -208,9 +231,13 @@ module PuppetLitmus::RakeHelper
208
231
  def configure_path(inventory_hash)
209
232
  results = []
210
233
  # fix the path on ssh_nodes
211
- unless inventory_hash['groups'].select { |group| group['name'] == 'ssh_nodes' }.size.zero?
212
- results = run_command('echo PATH="$PATH:/opt/puppetlabs/puppet/bin" > /etc/environment',
213
- 'ssh_nodes', config: nil, inventory: inventory_hash)
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)
214
241
  end
215
242
  results
216
243
  end
@@ -227,7 +254,7 @@ module PuppetLitmus::RakeHelper
227
254
  module_dir ||= Dir.pwd
228
255
  target_dir ||= File.join(source_dir, 'pkg')
229
256
 
230
- puts "Building '#{module_dir}' into '#{target_dir}''"
257
+ puts "Building '#{module_dir}' into '#{target_dir}'"
231
258
  builder = Puppet::Modulebuilder::Builder.new(module_dir, target_dir, nil)
232
259
 
233
260
  # Force the metadata to be read. Raises if metadata could not be found
@@ -269,10 +296,11 @@ module PuppetLitmus::RakeHelper
269
296
  # @param target_node_name [String] the name of the target where the module should be installed
270
297
  # @param module_tar [String] the filename of the module tarball to upload
271
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.
272
300
  # @return a bolt result
273
- def install_module(inventory_hash, target_node_name, module_tar, module_repository = nil)
301
+ def install_module(inventory_hash, target_node_name, module_tar, module_repository = nil, ignore_dependencies = false) # rubocop:disable Style/OptionalBooleanParameter
274
302
  Honeycomb.start_span(name: 'install_module') do |span|
275
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
303
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
276
304
  span.add_field('litmus.target_node_name', target_node_name)
277
305
  span.add_field('litmus.module_tar', module_tar)
278
306
 
@@ -290,6 +318,7 @@ module PuppetLitmus::RakeHelper
290
318
 
291
319
  module_repository_opts = "--module_repository '#{module_repository}'" unless module_repository.nil?
292
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'
293
322
  span.add_field('litmus.install_module_command', install_module_command)
294
323
 
295
324
  bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash.clone)
@@ -327,24 +356,30 @@ module PuppetLitmus::RakeHelper
327
356
 
328
357
  def check_connectivity?(inventory_hash, target_node_name)
329
358
  Honeycomb.start_span(name: 'litmus.check_connectivity') do |span|
330
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
359
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
331
360
  # if we're only checking connectivity for a single node
332
361
  if target_node_name
333
- span.add_field('litmus.node_name', target_node_name)
362
+ span.add_field('litmus.target_node_name', target_node_name)
334
363
  add_platform_field(inventory_hash, target_node_name)
335
364
  end
336
365
 
337
366
  include ::BoltSpec::Run
338
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
+
339
371
  results = run_command('cd .', target_nodes, config: nil, inventory: inventory_hash)
340
372
  span.add_field('litmus.bolt_result', results)
341
373
  failed = []
342
- results.each do |result|
343
- failed.push(result['target']) if result['status'] == 'failure'
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'])
344
377
  end
345
- span.add_field('litmus.connectivity_failed', failed)
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' })
346
380
  raise "Connectivity has failed on: #{failed}" unless failed.length.zero?
347
381
 
382
+ puts 'Connectivity check PASSED.'
348
383
  true
349
384
  end
350
385
  end
@@ -391,4 +426,50 @@ module PuppetLitmus::RakeHelper
391
426
 
392
427
  nil
393
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: 8)
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
394
475
  end
@@ -33,35 +33,35 @@ namespace :litmus do
33
33
  inventory_vars = provision_hash[args[:key]]['vars']
34
34
  # Splat the params into environment variables to pass to the provision task but only in this runspace
35
35
  provision_hash[args[:key]]['params']&.each { |k, value| ENV[k.upcase] = value.to_s }
36
- results = []
37
36
  failed_image_message = ''
38
- provision_hash[args[:key]]['images'].each do |image|
39
- if (ENV['CI'] == 'true') || !ENV['DISTELLI_BUILDNUM'].nil?
40
- progress = Thread.new do
41
- loop do
42
- printf '.'
43
- sleep(10)
44
- end
45
- end
46
- else
47
- require 'tty-spinner'
48
- spinner = TTY::Spinner.new("Provisioning #{image} using #{provisioner} provisioner.[:spinner]")
49
- spinner.auto_spin
50
- end
51
- result = provision(provisioner, image, inventory_vars)
52
-
53
- if (ENV['CI'] == 'true') || !ENV['DISTELLI_BUILDNUM'].nil?
54
- Thread.kill(progress)
55
- else
56
- spinner.success
37
+ if provision_hash[args[:key]]['images'].instance_of?(Hash)
38
+ begin
39
+ spinner = start_spinner("Provisioning multiple images using #{provisioner} provisioner.")
40
+ result = provision(provisioner, provision_hash[args[:key]]['images'], inventory_vars)
41
+ ensure
42
+ stop_spinner(spinner)
57
43
  end
58
44
 
59
45
  if result.first['status'] != 'success'
60
46
  failed_image_message += "=====\n#{result.first['target']}\n#{result.first['value']['_output']}\n#{result.inspect}"
61
47
  else
62
- STDOUT.puts "#{result.first['value']['node_name']}, #{image}"
48
+ $stdout.puts 'Success'
49
+ end
50
+ else
51
+ provision_hash[args[:key]]['images'].each do |image|
52
+ begin
53
+ spinner = start_spinner("Provisioning #{image} using #{provisioner} provisioner.")
54
+ result = provision(provisioner, image, inventory_vars)
55
+ ensure
56
+ stop_spinner(spinner)
57
+ end
58
+
59
+ if result.first['status'] != 'success'
60
+ failed_image_message += "=====\n#{result.first['target']}\n#{result.first['value']['_output']}\n#{result.inspect}"
61
+ else
62
+ $stdout.puts "#{result.first['value']['node_name']}, #{image}"
63
+ end
63
64
  end
64
- results << result
65
65
  end
66
66
 
67
67
  raise "Failed to provision with '#{provisioner}'\n #{failed_image_message}" unless failed_image_message.empty?
@@ -74,28 +74,35 @@ namespace :litmus do
74
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
- if (ENV['CI'] == 'true') || !ENV['DISTELLI_BUILDNUM'].nil?
78
- progress = Thread.new do
79
- loop do
80
- printf '.'
81
- sleep(10)
77
+
78
+ begin
79
+ spinner = start_spinner("Provisioning #{args[:platform]} using #{args[:provisioner]} provisioner.")
80
+
81
+ results = provision(args[:provisioner], args[:platform], args[:inventory_vars])
82
+
83
+ unless results.first['status'] == 'success'
84
+ raise "Failed provisioning #{args[:platform]} using #{args[:provisioner]}\n#{results.first}"
85
+ end
86
+
87
+ puts "Successfully provisioned #{args[:platform]} using #{args[:provisioner]}\n"
88
+
89
+ target_names = if results.first['value']['node']
90
+ [results.first['value']['node']['uri']]
91
+ else
92
+ results.first['value']['target_names'] || [] # provision_service multi-node provisioning
93
+ end
94
+ target_names.each do |target|
95
+ Honeycomb.start_span(name: 'litmus.provision.check_connectivity') do |span|
96
+ span.add_field('target_name', target)
97
+ with_retries do
98
+ check_connectivity?(inventory_hash_from_inventory_file, target)
99
+ end
82
100
  end
83
101
  end
84
- else
85
- require 'tty-spinner'
86
- spinner = TTY::Spinner.new("Provisioning #{args[:platform]} using #{args[:provisioner]} provisioner.[:spinner]")
87
- spinner.auto_spin
88
- end
89
- results = provision(args[:provisioner], args[:platform], args[:inventory_vars])
90
- if results.first['status'] != 'success'
91
- raise "Failed provisioning #{args[:platform]} using #{args[:provisioner]}\n#{results.first}"
102
+ ensure
103
+ stop_spinner(spinner)
92
104
  end
93
105
 
94
- if (ENV['CI'] == 'true') || !ENV['DISTELLI_BUILDNUM'].nil?
95
- Thread.kill(progress)
96
- else
97
- spinner.success
98
- end
99
106
  puts "#{results.first['value']['node_name']}, #{args[:platform]}"
100
107
  end
101
108
 
@@ -118,25 +125,39 @@ namespace :litmus do
118
125
 
119
126
  results = install_agent(args[:collection], targets, inventory_hash)
120
127
  results.each do |result|
121
- if result['status'] != 'success'
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
- else
125
- # add puppet-agent feature to successful nodes
126
- inventory_hash = add_feature_to_node(inventory_hash, 'puppet-agent', result['target'])
128
+ command_to_run = "bolt task run puppet_agent::install --targets #{result['target']} --inventoryfile inventory.yaml --modulepath #{DEFAULT_CONFIG_DATA['modulepath']}"
129
+ raise "Failed on #{result['target']}\n#{result}\ntry running '#{command_to_run}'" if result['status'] != 'success'
130
+
131
+ # validate successful install
132
+ puts "Successfull install result: #{result.inspect}" if ENV['DEBUG'] == 'true'
133
+ retries = 0
134
+ begin
135
+ responses = run_command('puppet --version', targets, options: {}, config: DEFAULT_CONFIG_DATA, inventory: inventory_hash.clone)
136
+ responses.each do |response|
137
+ raise "Error checking puppet version on #{response.to_json}" if response['status'] != 'success'
138
+ end
139
+ rescue StandardError => e
140
+ puts "ERROR:#{e}" if ENV['DEBUG'] == 'true'
141
+ # fix the path
142
+ path_changes = configure_path(inventory_hash)
143
+ if ENV['DEBUG'] == 'true'
144
+ path_changes.each do |change|
145
+ puts "Configuring puppet path result: #{change.inspect}"
146
+ end
147
+ end
148
+
149
+ retries += 1
150
+ sleep 3
151
+ retry if retries <= 300
152
+ raise 'Failed to detect installed puppet version after 5 minutes'
127
153
  end
154
+
155
+ # add puppet-agent feature to successful nodes
156
+ inventory_hash = add_feature_to_node(inventory_hash, 'puppet-agent', result['target'])
128
157
  end
158
+
129
159
  # update the inventory with the puppet-agent feature set per node
130
160
  write_to_inventory_file(inventory_hash, 'inventory.yaml')
131
-
132
- # fix the path on ssh_nodes
133
- results = configure_path(inventory_hash)
134
-
135
- results.each do |result|
136
- if result['status'] != 'success'
137
- puts "Failed on #{result['target']}\n#{result}"
138
- end
139
- end
140
161
  end
141
162
 
142
163
  # Add a given feature to a selection of nodes
@@ -195,8 +216,8 @@ namespace :litmus do
195
216
  # @param :source [String] source directory to look in (ignores symlinks) defaults do './spec/fixtures/modules'.
196
217
  # @param :target_node_name [Array] nodes on which to install a puppet module for testing.
197
218
  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)
219
+ task :install_modules_from_directory, [:source, :target_node_name, :module_repository, :ignore_dependencies] do |_task, args|
220
+ args.with_defaults(source: nil, target_node_name: nil, module_repository: nil, ignore_dependencies: false)
200
221
  inventory_hash = inventory_hash_from_inventory_file
201
222
  target_nodes = find_targets(inventory_hash, args[:target_node_name])
202
223
  if target_nodes.empty?
@@ -217,7 +238,7 @@ namespace :litmus do
217
238
  module_tars.each do |module_tar|
218
239
  puts "Installing '#{module_tar}'"
219
240
  target_nodes.each do |target_node_name|
220
- install_module(inventory_hash, target_node_name, module_tar, args[:module_repository])
241
+ install_module(inventory_hash, target_node_name, module_tar, args[:module_repository], args[:ignore_dependencies])
221
242
  puts "Installed '#{module_tar}' on #{target_node_name}"
222
243
  end
223
244
  end
@@ -321,15 +342,21 @@ namespace :litmus do
321
342
 
322
343
  # Run acceptance tests against all machines in the inventory file in parallel.
323
344
  desc 'Run tests in parallel against all machines in the inventory file'
324
- task :parallel do
345
+ task :parallel, [:tag] do |_task, args|
346
+ args.with_defaults(tag: nil)
325
347
  if targets.empty?
326
348
  puts 'No targets found'
327
349
  exit 0
328
350
  end
351
+ tag_value = if args[:tag].nil?
352
+ nil
353
+ else
354
+ "--tag #{args[:tag]}"
355
+ end
329
356
  payloads = []
330
357
  # Generate list of targets to provision
331
358
  targets.each do |target|
332
- test = 'bundle exec rspec ./spec/acceptance --format progress --require rspec_honeycomb_formatter --format RSpecHoneycombFormatter'
359
+ test = "bundle exec rspec ./spec/acceptance #{tag_value} --format progress --require rspec_honeycomb_formatter --format RSpecHoneycombFormatter"
333
360
  title = "#{target}, #{facts_from_node(inventory_hash, target)['platform']}"
334
361
  options = {
335
362
  env: {
@@ -343,7 +370,7 @@ namespace :litmus do
343
370
  success_list = []
344
371
  failure_list = []
345
372
  # Provision targets depending on what environment we're in
346
- if (ENV['CI'] == 'true') || !ENV['DISTELLI_BUILDNUM'].nil?
373
+ if ENV['CI'] == 'true'
347
374
  # CI systems are strange beasts, we only output a '.' every wee while to keep the terminal alive.
348
375
  puts "Running against #{targets.size} targets.\n"
349
376
  progress = Thread.new do
@@ -360,16 +387,16 @@ namespace :litmus do
360
387
  at_exit { exit! }
361
388
 
362
389
  env = options[:env].nil? ? {} : options[:env]
363
- env['HTTP_X_HONEYCOMB_TRACE'] = Honeycomb.current_span.to_trace_header
390
+ env['HONEYCOMB_TRACE'] = Honeycomb.current_span.to_trace_header
364
391
  stdout, stderr, status = Open3.capture3(env, test)
365
392
  ["\n================\n#{title}\n", stdout, stderr, status]
366
393
  end
367
394
  # because we cannot modify variables inside of Parallel
368
395
  results.each do |result|
369
396
  if result.last.to_i.zero?
370
- success_list.push(result.first.scan(%r{.*})[2])
397
+ success_list.push(result.first.scan(%r{.*})[3])
371
398
  else
372
- failure_list.push(result.first.scan(%r{.*})[2])
399
+ failure_list.push(result.first.scan(%r{.*})[3])
373
400
  end
374
401
  end
375
402
  Thread.kill(progress)
@@ -378,7 +405,7 @@ namespace :litmus do
378
405
  spinners = TTY::Spinner::Multi.new("[:spinner] Running against #{targets.size} targets.")
379
406
  payloads.each do |title, test, options|
380
407
  env = options[:env].nil? ? {} : options[:env]
381
- env['HTTP_X_HONEYCOMB_TRACE'] = Honeycomb.current_span.to_trace_header
408
+ env['HONEYCOMB_TRACE'] = Honeycomb.current_span.to_trace_header
382
409
  spinners.register("[:spinner] #{title}") do |sp|
383
410
  stdout, stderr, status = Open3.capture3(env, test)
384
411
  if status.to_i.zero?
@@ -411,8 +438,9 @@ namespace :litmus do
411
438
  desc "Run serverspec against #{target}"
412
439
  next if target == 'litmus_localhost'
413
440
 
414
- RSpec::Core::RakeTask.new(target.to_sym) do |t|
441
+ RSpec::Core::RakeTask.new(target.to_sym, :tag) do |t, args|
415
442
  t.pattern = 'spec/acceptance/**{,/*/**}/*_spec.rb'
443
+ t.rspec_opts = "--tag #{args[:tag]}" unless args[:tag].nil?
416
444
  ENV['TARGET_HOST'] = target
417
445
  end
418
446
  end
@@ -421,8 +449,9 @@ namespace :litmus do
421
449
  # add localhost separately
422
450
  desc 'Run serverspec against localhost, USE WITH CAUTION, this action can be potentially dangerous.'
423
451
  host = 'localhost'
424
- RSpec::Core::RakeTask.new(host.to_sym) do |t|
452
+ RSpec::Core::RakeTask.new(host.to_sym, :tag) do |t, args|
425
453
  t.pattern = 'spec/acceptance/**{,/*/**}/*_spec.rb'
454
+ t.rspec_opts = "--tag #{args[:tag]}" unless args[:tag].nil?
426
455
  Rake::Task['spec_prep'].invoke
427
456
  ENV['TARGET_HOST'] = host
428
457
  end
@@ -35,6 +35,7 @@ module PuppetLitmus
35
35
  options[:port] = node_config.dig('ssh', 'port') unless node_config.dig('ssh', 'port').nil?
36
36
  options[:keys] = node_config.dig('ssh', 'private-key') unless node_config.dig('ssh', 'private-key').nil?
37
37
  options[:password] = node_config.dig('ssh', 'password') unless node_config.dig('ssh', 'password').nil?
38
+ run_as = node_config.dig('ssh', 'run-as') unless node_config.dig('ssh', 'run-as').nil?
38
39
  # Support both net-ssh 4 and 5.
39
40
  # rubocop:disable Metrics/BlockNesting
40
41
  options[:verify_host_key] = if node_config.dig('ssh', 'host-key-check').nil?
@@ -67,6 +68,8 @@ module PuppetLitmus
67
68
  set :host, options[:host_name] || host
68
69
  set :ssh_options, options
69
70
  set :request_pty, false
71
+ set :sudo_password, options[:password] if run_as
72
+ puts "Running tests as #{run_as}" if run_as
70
73
  elsif target_in_group(inventory_hash, ENV['TARGET_HOST'], 'winrm_nodes')
71
74
  require 'winrm'
72
75
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # version of this gem
4
4
  module PuppetLitmus
5
- VERSION ||= '0.18.4'
5
+ VERSION ||= '0.23.0'
6
6
  end
@@ -113,10 +113,10 @@ RSpec.describe PuppetLitmus::PuppetHelpers do
113
113
  let(:local) { '/tmp' }
114
114
  let(:remote) { '/remote_tmp' }
115
115
  # Ignore rubocop because these hashes are representative of output from an external method and editing them leads to test failures.
116
- # rubocop:disable Layout/SpaceInsideHashLiteralBraces, Layout/SpaceInsideBlockBraces, Layout/SpaceAroundOperators, Layout/LineLength, Layout/SpaceAfterComma
117
- let(:result_success) {[{'target'=>'some.host','action'=>'upload','object'=>'C:\foo\bar.ps1','status'=>'success','value'=>{'_output'=>'Uploaded \'C:\foo\bar.ps1\' to \'some.host:C:\bar\''}}]}
118
- let(:result_failure) {[{'target'=>'some.host','action'=>nil,'object'=>nil,'status'=>'failure','value'=>{'_error'=>{'kind'=>'puppetlabs.tasks/task_file_error','msg'=>'No such file or directory @ rb_sysopen - /nonexistant/file/path','details'=>{},'issue_code'=>'WRITE_ERROR'}}}]}
119
- # rubocop:enable Layout/SpaceInsideHashLiteralBraces, Layout/SpaceInsideBlockBraces, Layout/SpaceAroundOperators, Layout/LineLength, Layout/SpaceAfterComma
116
+ # rubocop:disable Layout/SpaceAroundOperators, Layout/LineLength, Layout/SpaceAfterComma
117
+ let(:result_success) { [{ 'target'=>'some.host','action'=>'upload','object'=>'C:\foo\bar.ps1','status'=>'success','value'=>{ '_output'=>'Uploaded \'C:\foo\bar.ps1\' to \'some.host:C:\bar\'' } }] }
118
+ let(:result_failure) { [{ 'target'=>'some.host','action'=>nil,'object'=>nil,'status'=>'failure','value'=>{ '_error'=>{ 'kind'=>'puppetlabs.tasks/task_file_error','msg'=>'No such file or directory @ rb_sysopen - /nonexistant/file/path','details'=>{},'issue_code'=>'WRITE_ERROR' } } }] }
119
+ # rubocop:enable, Layout/SpaceAroundOperators, Layout/LineLength, Layout/SpaceAfterComma
120
120
 
121
121
  it 'responds to run_shell' do
122
122
  expect(self).to respond_to(:bolt_upload_file).with(2..3).arguments
@@ -212,11 +212,11 @@ RSpec.describe PuppetLitmus::PuppetHelpers do
212
212
  let(:params) { { 'action' => 'install', 'name' => 'foo' } }
213
213
  let(:config_data) { { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') } }
214
214
  # Ignore rubocop because these hashes are representative of output from an external method and editing them leads to test failures.
215
- # rubocop:disable Layout/SpaceInsideHashLiteralBraces, Layout/SpaceBeforeBlockBraces, Layout/SpaceInsideBlockBraces, Layout/SpaceAroundOperators, Layout/LineLength, Layout/SpaceAfterComma
216
- let(:result_unstructured_task_success){ [{'target'=>'some.host','action'=>'task','object'=>'testtask::unstructured','status'=>'success','value'=>{'_output'=>'SUCCESS!'}}]}
217
- let(:result_structured_task_success){ [{'target'=>'some.host','action'=>'task','object'=>'testtask::structured','status'=>'success','value'=>{'key1'=>'foo','key2'=>'bar'}}]}
218
- let(:result_failure) {[{'target'=>'some.host','action'=>'task','object'=>'testtask::unstructured','status'=>'failure','value'=>{'_error'=>{'msg'=>'FAILURE!','kind'=>'puppetlabs.tasks/task-error','details'=>{'exitcode'=>123}}}}]}
219
- # rubocop:enable Layout/SpaceInsideHashLiteralBraces, Layout/SpaceBeforeBlockBraces, Layout/SpaceInsideBlockBraces, Layout/SpaceAroundOperators, Layout/LineLength, Layout/SpaceAfterComma
215
+ # rubocop:disable Layout/SpaceBeforeBlockBraces
216
+ let(:result_unstructured_task_success){ [{ 'target'=>'some.host','action'=>'task','object'=>'testtask::unstructured','status'=>'success','value'=>{ '_output'=>'SUCCESS!' } }] }
217
+ let(:result_structured_task_success){ [{ 'target'=>'some.host','action'=>'task','object'=>'testtask::structured','status'=>'success','value'=>{ 'key1'=>'foo','key2'=>'bar' } }] }
218
+ let(:result_failure) { [{ 'target'=>'some.host','action'=>'task','object'=>'testtask::unstructured','status'=>'failure','value'=>{ '_error'=>{ 'msg'=>'FAILURE!','kind'=>'puppetlabs.tasks/task-error','details'=>{ 'exitcode'=>123 } } } }] }
219
+ # rubocop:enable Layout/SpaceBeforeBlockBraces, Layout/SpaceAroundOperators, Layout/LineLength, Layout/SpaceAfterComma
220
220
 
221
221
  it 'responds to bolt_run_task' do
222
222
  expect(self).to respond_to(:run_bolt_task).with(2..3).arguments
@@ -286,4 +286,42 @@ RSpec.describe PuppetLitmus::PuppetHelpers do
286
286
  end
287
287
  end
288
288
  end
289
+
290
+ describe '.write_file' do
291
+ let(:content) { 'foo' }
292
+ let(:destination) { '/tmp/foo' }
293
+ let(:owner) { 'foo:foo' }
294
+ let(:local_path) { '/tmp/local_foo' }
295
+
296
+ before(:each) do
297
+ allow_any_instance_of(File).to receive(:path).and_return(local_path)
298
+ end
299
+
300
+ it 'responds to write_file' do
301
+ expect(self).to respond_to(:write_file).with(2).arguments
302
+ end
303
+
304
+ context 'without setting owner' do
305
+ it 'call upload file with the correct params' do
306
+ stub_const('ENV', ENV.to_hash.merge('TARGET_HOST' => 'some.host'))
307
+ expect(self).to receive(:inventory_hash_from_inventory_file).and_return(inventory_hash)
308
+ result = instance_double('result')
309
+ allow(result).to receive(:first).and_return({ 'status' => 'success' })
310
+ expect(self).to receive(:upload_file).with(local_path, destination, 'some.host', options: {}, config: nil, inventory: inventory_hash).and_return(result)
311
+ result = write_file(content, destination)
312
+ expect(result).to be true
313
+ end
314
+ end
315
+
316
+ context 'when upload encounters an error' do
317
+ it 'call upload file with the correct params' do
318
+ stub_const('ENV', ENV.to_hash.merge('TARGET_HOST' => 'some.host'))
319
+ expect(self).to receive(:inventory_hash_from_inventory_file).and_return(inventory_hash)
320
+ result = instance_double('result')
321
+ allow(result).to receive(:first).and_return({ 'status' => 'failure', 'value' => 'foo error' })
322
+ expect(self).to receive(:upload_file).with(local_path, destination, 'some.host', options: {}, config: nil, inventory: inventory_hash).and_return(result)
323
+ expect { write_file(content, destination) }.to raise_error 'foo error'
324
+ end
325
+ end
326
+ end
289
327
  end
@@ -77,6 +77,42 @@ RSpec.describe PuppetLitmus::RakeHelper do
77
77
  end
78
78
  end
79
79
 
80
+ context 'with bulk tear_down' do
81
+ let(:inventory_hash) do
82
+ { 'groups' =>
83
+ [{ 'name' => 'ssh_nodes', 'targets' =>
84
+ [
85
+ { 'uri' => 'one.host', 'facts' => { 'provisioner' => 'abs', 'platform' => 'ubuntu-1604-x86_64', 'job_id' => 'iac-task-pid-21648' } },
86
+ { 'uri' => 'two.host', 'facts' => { 'provisioner' => 'abs', 'platform' => 'ubuntu-1804-x86_64', 'job_id' => 'iac-task-pid-21648' } },
87
+ { 'uri' => 'three.host', 'facts' => { 'provisioner' => 'abs', 'platform' => 'ubuntu-2004-x86_64', 'job_id' => 'iac-task-pid-21648' } },
88
+ { 'uri' => 'four.host', 'facts' => { 'provisioner' => 'abs', 'platform' => 'ubuntu-2004-x86_64', 'job_id' => 'iac-task-pid-21649' } },
89
+ ] }] }
90
+ end
91
+ let(:targets) { ['one.host'] }
92
+ let(:params) { { 'action' => 'tear_down', 'node_name' => 'one.host', 'inventory' => Dir.pwd } }
93
+
94
+ it 'calls function' do
95
+ allow(File).to receive(:directory?).with(File.join(described_class::DEFAULT_CONFIG_DATA['modulepath'], 'provision')).and_return(true)
96
+ allow_any_instance_of(BoltSpec::Run).to receive(:run_task).with('provision::abs', 'localhost', params, config: described_class::DEFAULT_CONFIG_DATA, inventory: nil).and_return(
97
+ [{ 'target' => 'localhost',
98
+ 'action' => 'task',
99
+ 'object' => 'provision::abs',
100
+ 'status' => 'success',
101
+ 'value' =>
102
+ { 'status' => 'ok',
103
+ 'removed' =>
104
+ ['one.host',
105
+ 'two.host',
106
+ 'three.host'] } }],
107
+ )
108
+ results = tear_down_nodes(targets, inventory_hash)
109
+ expect(results.keys).to eq(['one.host', 'two.host', 'three.host'])
110
+ results.each_value do |value|
111
+ expect(value[0]['value']).to eq({ 'status' => 'ok' })
112
+ end
113
+ end
114
+ end
115
+
80
116
  context 'with install_agent' do
81
117
  let(:inventory_hash) do
82
118
  { 'groups' =>
@@ -19,9 +19,9 @@ describe 'litmus rake tasks' do
19
19
  'template-ref' => 'heads/master-0-g7827fc2' }
20
20
  expect(File).to receive(:read).with(any_args).once
21
21
  expect(JSON).to receive(:parse).with(any_args).and_return(metadata)
22
- expect(STDOUT).to receive(:puts).with('redhat-5-x86_64')
23
- expect(STDOUT).to receive(:puts).with('ubuntu-1404-x86_64')
24
- expect(STDOUT).to receive(:puts).with('ubuntu-1804-x86_64')
22
+ expect($stdout).to receive(:puts).with('redhat-5-x86_64')
23
+ expect($stdout).to receive(:puts).with('ubuntu-1404-x86_64')
24
+ expect($stdout).to receive(:puts).with('ubuntu-1804-x86_64')
25
25
  Rake::Task['litmus:metadata'].invoke
26
26
  end
27
27
  end
@@ -39,11 +39,11 @@ describe 'litmus rake tasks' do
39
39
  expect_any_instance_of(PuppetLitmus::InventoryManipulation).to receive(:inventory_hash_from_inventory_file).and_return(inventory_hash)
40
40
  expect(File).to receive(:directory?).with(target_dir).and_return(true)
41
41
  expect_any_instance_of(Object).to receive(:build_modules_in_dir).with(target_dir).and_return([dummy_tar])
42
- expect(STDOUT).to receive(:puts).with(start_with('Building all modules in'))
42
+ expect($stdout).to receive(:puts).with(start_with('Building all modules in'))
43
43
  expect_any_instance_of(Object).to receive(:upload_file).once.and_return([])
44
- expect(STDOUT).to receive(:puts).with(start_with('Installing \'spec/data/doot.tar.gz\''))
44
+ expect($stdout).to receive(:puts).with(start_with('Installing \'spec/data/doot.tar.gz\''))
45
45
  expect_any_instance_of(Object).to receive(:run_command).twice.and_return([])
46
- expect(STDOUT).to receive(:puts).with(start_with('Installed \'spec/data/doot.tar.gz\''))
46
+ expect($stdout).to receive(:puts).with(start_with('Installed \'spec/data/doot.tar.gz\''))
47
47
  Rake::Task['litmus:install_modules_from_directory'].invoke('./spec/fixtures/modules')
48
48
  end
49
49
  end
@@ -68,7 +68,13 @@ describe 'litmus rake tasks' do
68
68
 
69
69
  allow(File).to receive(:directory?).with(any_args).and_return(true)
70
70
  allow_any_instance_of(BoltSpec::Run).to receive(:run_task).with(any_args).and_return(results)
71
- expect(STDOUT).to receive(:puts).with('localhost:2222, centos:7')
71
+ allow_any_instance_of(PuppetLitmus::InventoryManipulation).to receive(:inventory_hash_from_inventory_file).with(any_args).and_return({})
72
+ allow_any_instance_of(PuppetLitmus::RakeHelper).to receive(:check_connectivity?).with(any_args).and_return(true)
73
+
74
+ expect($stdout).to receive(:puts).with('Provisioning centos:7 using docker provisioner.')
75
+ expect($stdout).to receive(:puts).with("Successfully provisioned centos:7 using docker\n")
76
+ expect($stdout).to receive(:puts).with('localhost:2222, centos:7')
77
+
72
78
  Rake::Task['litmus:provision'].invoke('docker', 'centos:7')
73
79
  end
74
80
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppet_litmus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.4
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet, Inc.
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-01 00:00:00.000000000 Z
11
+ date: 2021-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bolt
@@ -79,7 +79,7 @@ dependencies:
79
79
  version: '1.34'
80
80
  - - "<"
81
81
  - !ruby/object:Gem::Version
82
- version: 2.0.0
82
+ version: 3.0.0
83
83
  type: :runtime
84
84
  prerelease: false
85
85
  version_requirements: !ruby/object:Gem::Requirement
@@ -89,7 +89,21 @@ dependencies:
89
89
  version: '1.34'
90
90
  - - "<"
91
91
  - !ruby/object:Gem::Version
92
- version: 2.0.0
92
+ version: 3.0.0
93
+ - !ruby/object:Gem::Dependency
94
+ name: retryable
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '3.0'
100
+ type: :runtime
101
+ prerelease: false
102
+ version_requirements: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - "~>"
105
+ - !ruby/object:Gem::Version
106
+ version: '3.0'
93
107
  - !ruby/object:Gem::Dependency
94
108
  name: parallel
95
109
  requirement: !ruby/object:Gem::Requirement
@@ -150,12 +164,14 @@ description: " Providing a simple command line tool for puppet content creato
150
164
  to enable simple and complex test deployments.\n"
151
165
  email:
152
166
  - info@puppet.com
153
- executables: []
167
+ executables:
168
+ - matrix_from_metadata
154
169
  extensions: []
155
170
  extra_rdoc_files: []
156
171
  files:
157
172
  - LICENSE
158
173
  - README.md
174
+ - exe/matrix_from_metadata
159
175
  - lib/puppet_litmus.rb
160
176
  - lib/puppet_litmus/inventory_manipulation.rb
161
177
  - lib/puppet_litmus/puppet_helpers.rb
@@ -169,10 +185,10 @@ files:
169
185
  - spec/data/jim.yaml
170
186
  - spec/lib/puppet_litmus/inventory_manipulation_spec.rb
171
187
  - spec/lib/puppet_litmus/puppet_helpers_spec.rb
188
+ - spec/lib/puppet_litmus/puppet_litmus_version_spec.rb
172
189
  - spec/lib/puppet_litmus/rake_helper_spec.rb
173
190
  - spec/lib/puppet_litmus/rake_tasks_spec.rb
174
191
  - spec/lib/puppet_litmus/util_spec.rb
175
- - spec/lib/puppet_litmus/version_spec.rb
176
192
  - spec/spec_helper.rb
177
193
  homepage: https://github.com/puppetlabs/puppet_litmus
178
194
  licenses:
@@ -186,14 +202,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
202
  requirements:
187
203
  - - ">="
188
204
  - !ruby/object:Gem::Version
189
- version: '0'
205
+ version: 2.5.0
190
206
  required_rubygems_version: !ruby/object:Gem::Requirement
191
207
  requirements:
192
208
  - - ">="
193
209
  - !ruby/object:Gem::Version
194
210
  version: '0'
195
211
  requirements: []
196
- rubygems_version: 3.1.2
212
+ rubygems_version: 3.0.6
197
213
  signing_key:
198
214
  specification_version: 4
199
215
  summary: Providing a simple command line tool for puppet content creators, to enable
@@ -201,7 +217,7 @@ summary: Providing a simple command line tool for puppet content creators, to en
201
217
  test_files:
202
218
  - spec/spec_helper.rb
203
219
  - spec/lib/puppet_litmus/rake_tasks_spec.rb
204
- - spec/lib/puppet_litmus/version_spec.rb
220
+ - spec/lib/puppet_litmus/puppet_litmus_version_spec.rb
205
221
  - spec/lib/puppet_litmus/util_spec.rb
206
222
  - spec/lib/puppet_litmus/inventory_manipulation_spec.rb
207
223
  - spec/lib/puppet_litmus/rake_helper_spec.rb