puppet_litmus 0.18.3 → 0.22.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: 7ace2d5a5ae33d1e795535b4704d84a54b9d97e48bd5a77b39866887c077e300
4
- data.tar.gz: 611dbd8bd462331462506280920ac3355a732c18ca2a6edc2ce15e4da5071214
3
+ metadata.gz: '09a793d09382a0f461b0390ebb71f94ae4a14df41873d44a77f2006ee15934bf'
4
+ data.tar.gz: eced3b1aceb10d8fc85f90dd15ab3cfc300f793b85e2f516548510d59bd9ab4c
5
5
  SHA512:
6
- metadata.gz: 3f2024d6a7afed0976cdc5ab48ab1fe1e42ff17f75a39637feed9cf102d0a8defc8fd2c8bf54dac45512f9849fd9ec1a1862691455c6246e79afc55008376d23
7
- data.tar.gz: 8a91349dc69667bdb7e701fa64a60fe71b0f8e311c017d5bda53b4fd0d1fa17c0276db50982076abdbf2d56408ea88cd6d9de8fac151ba609f41c75282c7125b
6
+ metadata.gz: b2226c90e2928e78e44ab3faf2ff837da356934ea89768637b7943c32d615b3c04ff605a00063dff6ad24126ac5e9a43f2e86f16c6385132bbc9c4ba7a27b82e
7
+ data.tar.gz: 5651ff30427d4f96b72440c28458251e00c4c595df98a602130fa95aec6f18c1f5d378d2a737ece3c918367cd38933f7aa615733ffc0c5ee77e6240f4fd82db8
@@ -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-nightly',
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.dig('version').nil? || (inventory_hash['version'] < 2)
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
- targets = if targets.nil?
54
- inventory_hash.to_s.scan(%r{uri"=>"(\S*)"}).flatten
55
- else
56
- [targets]
57
- end
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 = facts_from_node(inventory_hash, node_name)
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['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,19 @@ 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
+ command_to_run = "try { #{command_to_run}; exit $LASTEXITCODE } catch { write-error $_ ; exit 1 }" if os[:family] == 'windows'
87
+
86
88
  span.add_field('litmus.command_to_run', command_to_run)
87
89
  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
+ # IAC-1365 - Workaround for BOLT-1535 and bolt issue #1650
91
+ # bolt_result = run_command(command_to_run, target_node_name, config: nil, inventory: inventory_hash)
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
90
97
 
98
+ span.add_field('litmus.bolt_result', bolt_result)
91
99
  result = OpenStruct.new(exit_code: bolt_result.first['value']['exit_code'],
92
100
  stdout: bolt_result.first['value']['stdout'],
93
101
  stderr: bolt_result.first['value']['stderr'])
@@ -117,7 +125,7 @@ module PuppetLitmus::PuppetHelpers
117
125
  # @return [String] The path to the location of the manifest.
118
126
  def create_manifest_file(manifest)
119
127
  Honeycomb.start_span(name: 'litmus.create_manifest_file') do |span|
120
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
128
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
121
129
  span.add_field('litmus.manifest', manifest)
122
130
 
123
131
  require 'tmpdir'
@@ -147,6 +155,42 @@ module PuppetLitmus::PuppetHelpers
147
155
  end
148
156
  end
149
157
 
158
+ # Writes a string variable to a file on a target node at a specified path.
159
+ #
160
+ # @param content [String] String data to write to the file.
161
+ # @param destination [String] The path on the target node to write the file.
162
+ # @return [Bool] Success. The file was succesfully writtne on the target.
163
+ def write_file(content, destination)
164
+ Honeycomb.start_span(name: 'litmus.write_file') do |span|
165
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
166
+ span.add_field('litmus.destination', destination)
167
+
168
+ require 'tmpdir'
169
+ target_node_name = ENV['TARGET_HOST']
170
+
171
+ Tempfile.create('litmus') do |tmp_file|
172
+ tmp_file.write(content)
173
+ tmp_file.flush
174
+ if target_node_name.nil? || target_node_name == 'localhost'
175
+ require 'fileutils'
176
+ # no need to transfer
177
+ FileUtils.cp(tmp_file.path, destination)
178
+ else
179
+ # transfer to TARGET_HOST
180
+ inventory_hash = inventory_hash_from_inventory_file
181
+ span.add_field('litmus.node_name', target_node_name)
182
+ add_platform_field(inventory_hash, target_node_name)
183
+
184
+ bolt_result = upload_file(tmp_file.path, destination, target_node_name, options: {}, config: nil, inventory: inventory_hash)
185
+ span.add_field('litmus.bolt_result.file_upload', bolt_result)
186
+ raise bolt_result.first['value'].to_s unless bolt_result.first['status'] == 'success'
187
+ end
188
+ end
189
+
190
+ true
191
+ end
192
+ end
193
+
150
194
  # Runs a command against the target system
151
195
  #
152
196
  # @param command_to_run [String] The command to execute.
@@ -155,7 +199,7 @@ module PuppetLitmus::PuppetHelpers
155
199
  # @return [Object] A result object from the command.
156
200
  def run_shell(command_to_run, opts = {})
157
201
  Honeycomb.start_span(name: 'litmus.run_shell') do |span|
158
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
202
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
159
203
  span.add_field('litmus.command_to_run', command_to_run)
160
204
  span.add_field('litmus.opts', opts)
161
205
 
@@ -192,7 +236,7 @@ module PuppetLitmus::PuppetHelpers
192
236
  # @return [Object] A result object from the command.
193
237
  def bolt_upload_file(source, destination, opts = {}, options = {})
194
238
  Honeycomb.start_span(name: 'litmus.bolt_upload_file') do |span|
195
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
239
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
196
240
  span.add_field('litmus.source', source)
197
241
  span.add_field('litmus.destination', destination)
198
242
  span.add_field('litmus.opts', opts)
@@ -244,7 +288,7 @@ module PuppetLitmus::PuppetHelpers
244
288
  # @return [Object] A result object from the task.The values available are stdout, stderr and result.
245
289
  def run_bolt_task(task_name, params = {}, opts = {})
246
290
  Honeycomb.start_span(name: 'litmus.run_task') do |span|
247
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
291
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
248
292
  span.add_field('litmus.task_name', task_name)
249
293
  span.add_field('litmus.params', params)
250
294
  span.add_field('litmus.opts', opts)
@@ -312,7 +356,7 @@ module PuppetLitmus::PuppetHelpers
312
356
  # @return [Object] A result object from the script run.
313
357
  def bolt_run_script(script, opts = {}, arguments: [])
314
358
  Honeycomb.start_span(name: 'litmus.bolt_run_script') do |span|
315
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
359
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
316
360
  span.add_field('litmus.script', script)
317
361
  span.add_field('litmus.opts', opts)
318
362
  span.add_field('litmus.arguments', arguments)
@@ -382,7 +426,7 @@ module PuppetLitmus::PuppetHelpers
382
426
 
383
427
  # Return the stdout of the puppet run
384
428
  def puppet_output(bolt_result)
385
- bolt_result.dig(0, 'value', 'stderr').to_s << \
429
+ bolt_result.dig(0, 'value', 'stderr').to_s + \
386
430
  bolt_result.dig(0, 'value', 'stdout').to_s
387
431
  end
388
432
 
@@ -9,10 +9,14 @@ 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
- Honeycomb.add_field_to_trace('litmus.version', PuppetLitmus::VERSION)
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
16
20
  if ENV['CI'] == 'true' && ENV['TRAVIS'] == 'true'
17
21
  Honeycomb.add_field_to_trace('module_name', ENV['TRAVIS_REPO_SLUG'])
18
22
  Honeycomb.add_field_to_trace('ci.provider', 'travis')
@@ -37,6 +41,13 @@ elsif ENV['GITHUB_ACTIONS'] == 'true'
37
41
  Honeycomb.add_field_to_trace('ci.sha', ENV['GITHUB_SHA'])
38
42
  end
39
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
40
51
  process_span.send
41
52
  end
42
53
 
@@ -44,7 +55,7 @@ end
44
55
  module PuppetLitmus::RakeHelper
45
56
  # DEFAULT_CONFIG_DATA should be frozen for our safety, but it needs to work around https://github.com/puppetlabs/bolt/pull/1696
46
57
  DEFAULT_CONFIG_DATA ||= { 'modulepath' => File.join(Dir.pwd, 'spec', 'fixtures', 'modules') } # .freeze # rubocop:disable Style/MutableConstant
47
- SUPPORTED_PROVISIONERS ||= %w[abs docker docker_exp vagrant vmpooler].freeze
58
+ SUPPORTED_PROVISIONERS ||= %w[abs docker docker_exp provision_service vagrant vmpooler].freeze
48
59
 
49
60
  # Gets a string representing the operating system and version.
50
61
  #
@@ -92,7 +103,7 @@ module PuppetLitmus::RakeHelper
92
103
  # @return [Object] the standard out stream.
93
104
  def run_local_command(command)
94
105
  Honeycomb.start_span(name: 'litmus.run_local_command') do |span|
95
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
106
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
96
107
  span.add_field('litmus.command', command)
97
108
 
98
109
  require 'open3'
@@ -115,13 +126,18 @@ module PuppetLitmus::RakeHelper
115
126
 
116
127
  Honeycomb.add_field_to_trace('litmus.provisioner', provisioner)
117
128
  Honeycomb.start_span(name: 'litmus.provision') do |span|
118
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
129
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
119
130
  span.add_field('litmus.platform', platform)
120
- 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)
121
135
  span.add_field('litmus.config', DEFAULT_CONFIG_DATA)
122
136
 
123
- 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)
124
139
  span.add_field('litmus.node_name', bolt_result&.first&.dig('value', 'node_name'))
140
+
125
141
  raise_bolt_errors(bolt_result, "provisioning of #{platform} failed.")
126
142
 
127
143
  bolt_result
@@ -144,7 +160,7 @@ module PuppetLitmus::RakeHelper
144
160
 
145
161
  def tear_down_nodes(targets, inventory_hash)
146
162
  Honeycomb.start_span(name: 'litmus.tear_down_nodes') do |span|
147
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
163
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
148
164
  span.add_field('litmus.targets', targets)
149
165
 
150
166
  include ::BoltSpec::Run
@@ -156,6 +172,18 @@ module PuppetLitmus::RakeHelper
156
172
  next if node_name == 'litmus_localhost'
157
173
 
158
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
+
159
187
  results[node_name] = result unless result == []
160
188
  end
161
189
  results
@@ -164,7 +192,7 @@ module PuppetLitmus::RakeHelper
164
192
 
165
193
  def tear_down(node_name, inventory_hash)
166
194
  Honeycomb.start_span(name: 'litmus.tear_down') do |span|
167
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
195
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
168
196
  # how do we know what provisioner to use
169
197
 
170
198
  span.add_field('litmus.node_name', node_name)
@@ -180,7 +208,7 @@ module PuppetLitmus::RakeHelper
180
208
 
181
209
  def install_agent(collection, targets, inventory_hash)
182
210
  Honeycomb.start_span(name: 'litmus.install_agent') do |span|
183
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
211
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
184
212
  span.add_field('litmus.collection', collection)
185
213
  span.add_field('litmus.targets', targets)
186
214
 
@@ -188,7 +216,6 @@ module PuppetLitmus::RakeHelper
188
216
  params = if collection.nil?
189
217
  {}
190
218
  else
191
- Honeycomb.current_span.add_field('litmus.collection', collection)
192
219
  { 'collection' => collection }
193
220
  end
194
221
  raise "puppet_agent was not found in #{DEFAULT_CONFIG_DATA['modulepath']}, please amend the .fixtures.yml file" \
@@ -204,9 +231,13 @@ module PuppetLitmus::RakeHelper
204
231
  def configure_path(inventory_hash)
205
232
  results = []
206
233
  # fix the path on ssh_nodes
207
- unless inventory_hash['groups'].select { |group| group['name'] == 'ssh_nodes' }.size.zero?
208
- results = run_command('echo PATH="$PATH:/opt/puppetlabs/puppet/bin" > /etc/environment',
209
- '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)
210
241
  end
211
242
  results
212
243
  end
@@ -223,7 +254,7 @@ module PuppetLitmus::RakeHelper
223
254
  module_dir ||= Dir.pwd
224
255
  target_dir ||= File.join(source_dir, 'pkg')
225
256
 
226
- puts "Building '#{module_dir}' into '#{target_dir}''"
257
+ puts "Building '#{module_dir}' into '#{target_dir}'"
227
258
  builder = Puppet::Modulebuilder::Builder.new(module_dir, target_dir, nil)
228
259
 
229
260
  # Force the metadata to be read. Raises if metadata could not be found
@@ -265,10 +296,11 @@ module PuppetLitmus::RakeHelper
265
296
  # @param target_node_name [String] the name of the target where the module should be installed
266
297
  # @param module_tar [String] the filename of the module tarball to upload
267
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.
268
300
  # @return a bolt result
269
- 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
270
302
  Honeycomb.start_span(name: 'install_module') do |span|
271
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
303
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
272
304
  span.add_field('litmus.target_node_name', target_node_name)
273
305
  span.add_field('litmus.module_tar', module_tar)
274
306
 
@@ -286,6 +318,7 @@ module PuppetLitmus::RakeHelper
286
318
 
287
319
  module_repository_opts = "--module_repository '#{module_repository}'" unless module_repository.nil?
288
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'
289
322
  span.add_field('litmus.install_module_command', install_module_command)
290
323
 
291
324
  bolt_result = run_command(install_module_command, target_nodes, config: nil, inventory: inventory_hash.clone)
@@ -323,24 +356,30 @@ module PuppetLitmus::RakeHelper
323
356
 
324
357
  def check_connectivity?(inventory_hash, target_node_name)
325
358
  Honeycomb.start_span(name: 'litmus.check_connectivity') do |span|
326
- ENV['HTTP_X_HONEYCOMB_TRACE'] = span.to_trace_header
359
+ ENV['HONEYCOMB_TRACE'] = span.to_trace_header
327
360
  # if we're only checking connectivity for a single node
328
361
  if target_node_name
329
- span.add_field('litmus.node_name', target_node_name)
362
+ span.add_field('litmus.target_node_name', target_node_name)
330
363
  add_platform_field(inventory_hash, target_node_name)
331
364
  end
332
365
 
333
366
  include ::BoltSpec::Run
334
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
+
335
371
  results = run_command('cd .', target_nodes, config: nil, inventory: inventory_hash)
336
372
  span.add_field('litmus.bolt_result', results)
337
373
  failed = []
338
- results.each do |result|
339
- 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'])
340
377
  end
341
- 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' })
342
380
  raise "Connectivity has failed on: #{failed}" unless failed.length.zero?
343
381
 
382
+ puts 'Connectivity check PASSED.'
344
383
  true
345
384
  end
346
385
  end
@@ -387,4 +426,50 @@ module PuppetLitmus::RakeHelper
387
426
 
388
427
  nil
389
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
390
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?
@@ -66,7 +67,9 @@ module PuppetLitmus
66
67
  end
67
68
  set :host, options[:host_name] || host
68
69
  set :ssh_options, options
69
- set :request_pty, true
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.3'
5
+ VERSION ||= '0.22.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.3
4
+ version: 0.22.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-06-10 00:00:00.000000000 Z
11
+ date: 2021-01-21 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,23 +89,23 @@ 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
93
  - !ruby/object:Gem::Dependency
94
- name: parallel
94
+ name: retryable
95
95
  requirement: !ruby/object:Gem::Requirement
96
96
  requirements:
97
- - - ">="
97
+ - - "~>"
98
98
  - !ruby/object:Gem::Version
99
- version: '0'
99
+ version: '3.0'
100
100
  type: :runtime
101
101
  prerelease: false
102
102
  version_requirements: !ruby/object:Gem::Requirement
103
103
  requirements:
104
- - - ">="
104
+ - - "~>"
105
105
  - !ruby/object:Gem::Version
106
- version: '0'
106
+ version: '3.0'
107
107
  - !ruby/object:Gem::Dependency
108
- name: rspec
108
+ name: parallel
109
109
  requirement: !ruby/object:Gem::Requirement
110
110
  requirements:
111
111
  - - ">="
@@ -119,7 +119,7 @@ dependencies:
119
119
  - !ruby/object:Gem::Version
120
120
  version: '0'
121
121
  - !ruby/object:Gem::Dependency
122
- name: honeycomb-beeline
122
+ name: rspec
123
123
  requirement: !ruby/object:Gem::Requirement
124
124
  requirements:
125
125
  - - ">="
@@ -133,7 +133,7 @@ dependencies:
133
133
  - !ruby/object:Gem::Version
134
134
  version: '0'
135
135
  - !ruby/object:Gem::Dependency
136
- name: rspec_honeycomb_formatter
136
+ name: honeycomb-beeline
137
137
  requirement: !ruby/object:Gem::Requirement
138
138
  requirements:
139
139
  - - ">="
@@ -147,55 +147,31 @@ dependencies:
147
147
  - !ruby/object:Gem::Version
148
148
  version: '0'
149
149
  - !ruby/object:Gem::Dependency
150
- name: ed25519
151
- requirement: !ruby/object:Gem::Requirement
152
- requirements:
153
- - - ">="
154
- - !ruby/object:Gem::Version
155
- version: '1.2'
156
- - - "<"
157
- - !ruby/object:Gem::Version
158
- version: '2.0'
159
- type: :runtime
160
- prerelease: false
161
- version_requirements: !ruby/object:Gem::Requirement
162
- requirements:
163
- - - ">="
164
- - !ruby/object:Gem::Version
165
- version: '1.2'
166
- - - "<"
167
- - !ruby/object:Gem::Version
168
- version: '2.0'
169
- - !ruby/object:Gem::Dependency
170
- name: bcrypt_pbkdf
150
+ name: rspec_honeycomb_formatter
171
151
  requirement: !ruby/object:Gem::Requirement
172
152
  requirements:
173
153
  - - ">="
174
154
  - !ruby/object:Gem::Version
175
- version: '1.0'
176
- - - "<"
177
- - !ruby/object:Gem::Version
178
- version: '2.0'
155
+ version: '0'
179
156
  type: :runtime
180
157
  prerelease: false
181
158
  version_requirements: !ruby/object:Gem::Requirement
182
159
  requirements:
183
160
  - - ">="
184
161
  - !ruby/object:Gem::Version
185
- version: '1.0'
186
- - - "<"
187
- - !ruby/object:Gem::Version
188
- version: '2.0'
162
+ version: '0'
189
163
  description: " Providing a simple command line tool for puppet content creators,
190
164
  to enable simple and complex test deployments.\n"
191
165
  email:
192
166
  - info@puppet.com
193
- executables: []
167
+ executables:
168
+ - matrix_from_metadata
194
169
  extensions: []
195
170
  extra_rdoc_files: []
196
171
  files:
197
172
  - LICENSE
198
173
  - README.md
174
+ - exe/matrix_from_metadata
199
175
  - lib/puppet_litmus.rb
200
176
  - lib/puppet_litmus/inventory_manipulation.rb
201
177
  - lib/puppet_litmus/puppet_helpers.rb
@@ -209,10 +185,10 @@ files:
209
185
  - spec/data/jim.yaml
210
186
  - spec/lib/puppet_litmus/inventory_manipulation_spec.rb
211
187
  - spec/lib/puppet_litmus/puppet_helpers_spec.rb
188
+ - spec/lib/puppet_litmus/puppet_litmus_version_spec.rb
212
189
  - spec/lib/puppet_litmus/rake_helper_spec.rb
213
190
  - spec/lib/puppet_litmus/rake_tasks_spec.rb
214
191
  - spec/lib/puppet_litmus/util_spec.rb
215
- - spec/lib/puppet_litmus/version_spec.rb
216
192
  - spec/spec_helper.rb
217
193
  homepage: https://github.com/puppetlabs/puppet_litmus
218
194
  licenses:
@@ -226,26 +202,26 @@ required_ruby_version: !ruby/object:Gem::Requirement
226
202
  requirements:
227
203
  - - ">="
228
204
  - !ruby/object:Gem::Version
229
- version: '0'
205
+ version: 2.5.0
230
206
  required_rubygems_version: !ruby/object:Gem::Requirement
231
207
  requirements:
232
208
  - - ">="
233
209
  - !ruby/object:Gem::Version
234
210
  version: '0'
235
211
  requirements: []
236
- rubygems_version: 3.1.2
212
+ rubygems_version: 3.0.3
237
213
  signing_key:
238
214
  specification_version: 4
239
215
  summary: Providing a simple command line tool for puppet content creators, to enable
240
216
  simple and complex test deployments.
241
217
  test_files:
242
- - spec/data/doot.tar.gz
243
- - spec/data/inventory.yaml
244
- - spec/data/jim.yaml
218
+ - spec/spec_helper.rb
219
+ - spec/lib/puppet_litmus/rake_tasks_spec.rb
220
+ - spec/lib/puppet_litmus/puppet_litmus_version_spec.rb
245
221
  - spec/lib/puppet_litmus/util_spec.rb
246
- - spec/lib/puppet_litmus/version_spec.rb
247
222
  - spec/lib/puppet_litmus/inventory_manipulation_spec.rb
248
- - spec/lib/puppet_litmus/puppet_helpers_spec.rb
249
223
  - spec/lib/puppet_litmus/rake_helper_spec.rb
250
- - spec/lib/puppet_litmus/rake_tasks_spec.rb
251
- - spec/spec_helper.rb
224
+ - spec/lib/puppet_litmus/puppet_helpers_spec.rb
225
+ - spec/data/doot.tar.gz
226
+ - spec/data/jim.yaml
227
+ - spec/data/inventory.yaml