puppet_litmus 0.18.3 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
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