simp-beaker-helpers 1.18.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.fixtures.yml +8 -0
  3. data/.gitignore +8 -0
  4. data/.gitlab-ci.yml +163 -0
  5. data/.rspec +4 -0
  6. data/.rubocop.yml +546 -0
  7. data/.travis.yml +36 -0
  8. data/CHANGELOG.md +231 -0
  9. data/Gemfile +51 -0
  10. data/LICENSE +27 -0
  11. data/README.md +543 -0
  12. data/Rakefile +151 -0
  13. data/files/pki/clean.sh +1 -0
  14. data/files/pki/make.sh +101 -0
  15. data/files/pki/template_ca.cnf +259 -0
  16. data/files/pki/template_host.cnf +263 -0
  17. data/files/puppet-agent-versions.yaml +46 -0
  18. data/lib/simp/beaker_helpers.rb +1231 -0
  19. data/lib/simp/beaker_helpers/constants.rb +25 -0
  20. data/lib/simp/beaker_helpers/inspec.rb +328 -0
  21. data/lib/simp/beaker_helpers/snapshot.rb +156 -0
  22. data/lib/simp/beaker_helpers/ssg.rb +383 -0
  23. data/lib/simp/beaker_helpers/version.rb +5 -0
  24. data/lib/simp/beaker_helpers/windows.rb +16 -0
  25. data/lib/simp/rake/beaker.rb +269 -0
  26. data/simp-beaker-helpers.gemspec +38 -0
  27. data/spec/acceptance/nodesets/default.yml +32 -0
  28. data/spec/acceptance/suites/default/check_puppet_version_spec.rb +23 -0
  29. data/spec/acceptance/suites/default/enable_fips_spec.rb +23 -0
  30. data/spec/acceptance/suites/default/fixture_modules_spec.rb +22 -0
  31. data/spec/acceptance/suites/default/install_simp_deps_repo_spec.rb +43 -0
  32. data/spec/acceptance/suites/default/nodesets +1 -0
  33. data/spec/acceptance/suites/default/pki_tests_spec.rb +55 -0
  34. data/spec/acceptance/suites/default/set_hieradata_on_spec.rb +33 -0
  35. data/spec/acceptance/suites/default/write_hieradata_to_spec.rb +33 -0
  36. data/spec/acceptance/suites/fips_from_fixtures/00_default_spec.rb +63 -0
  37. data/spec/acceptance/suites/fips_from_fixtures/metadata.yml +2 -0
  38. data/spec/acceptance/suites/fips_from_fixtures/nodesets +1 -0
  39. data/spec/acceptance/suites/offline/00_default_spec.rb +165 -0
  40. data/spec/acceptance/suites/offline/README +2 -0
  41. data/spec/acceptance/suites/offline/nodesets/default.yml +26 -0
  42. data/spec/acceptance/suites/puppet_collections/00_default_spec.rb +25 -0
  43. data/spec/acceptance/suites/puppet_collections/metadata.yml +2 -0
  44. data/spec/acceptance/suites/puppet_collections/nodesets/default.yml +30 -0
  45. data/spec/acceptance/suites/snapshot/00_snapshot_test_spec.rb +82 -0
  46. data/spec/acceptance/suites/snapshot/10_general_usage_spec.rb +56 -0
  47. data/spec/acceptance/suites/snapshot/nodesets +1 -0
  48. data/spec/acceptance/suites/windows/00_default_spec.rb +119 -0
  49. data/spec/acceptance/suites/windows/metadata.yml +2 -0
  50. data/spec/acceptance/suites/windows/nodesets/default.yml +33 -0
  51. data/spec/acceptance/suites/windows/nodesets/win2016.yml +35 -0
  52. data/spec/acceptance/suites/windows/nodesets/win2019.yml +34 -0
  53. data/spec/lib/simp/beaker_helpers_spec.rb +216 -0
  54. data/spec/spec_helper.rb +100 -0
  55. data/spec/spec_helper_acceptance.rb +25 -0
  56. metadata +243 -0
@@ -0,0 +1,383 @@
1
+ module Simp::BeakerHelpers
2
+ require 'simp/beaker_helpers/constants'
3
+
4
+ # Helpers for working with the SCAP Security Guide
5
+ class SSG
6
+
7
+ if ENV['BEAKER_ssg_repo']
8
+ GIT_REPO = ENV['BEAKER_ssg_repo']
9
+ else
10
+ fail('You are offline: Set BEAKER_ssg_repo to point to the git repo that hosts the SSG content') unless ONLINE
11
+
12
+ GIT_REPO = 'https://github.com/ComplianceAsCode/content.git'
13
+ end
14
+
15
+ # If this is not set, the closest tag to the default branch will be used
16
+ GIT_BRANCH = nil
17
+
18
+ if ENV['BEAKER_ssg_branch']
19
+ GIT_BRANCH = ENV['BEAKER_ssg_branch']
20
+ end
21
+
22
+ EL_PACKAGES = [
23
+ 'PyYAML',
24
+ 'cmake',
25
+ 'git',
26
+ 'openscap-python',
27
+ 'openscap-utils',
28
+ 'python-lxml',
29
+ 'python-jinja2'
30
+ ]
31
+
32
+ EL8_PACKAGES = [
33
+ 'python3',
34
+ 'python3-pyyaml',
35
+ 'cmake',
36
+ 'git',
37
+ 'openscap-python3',
38
+ 'openscap-utils',
39
+ 'python3-lxml',
40
+ 'python3-jinja2'
41
+ ]
42
+
43
+ OS_INFO = {
44
+ 'RedHat' => {
45
+ '6' => {
46
+ 'required_packages' => EL_PACKAGES,
47
+ 'ssg' => {
48
+ 'profile_target' => 'rhel6',
49
+ 'build_target' => 'rhel6',
50
+ 'datastream' => 'ssg-rhel6-ds.xml'
51
+ }
52
+ },
53
+ '7' => {
54
+ 'required_packages' => EL_PACKAGES,
55
+ 'ssg' => {
56
+ 'profile_target' => 'rhel7',
57
+ 'build_target' => 'rhel7',
58
+ 'datastream' => 'ssg-rhel7-ds.xml'
59
+ }
60
+ },
61
+ '8' => {
62
+ 'required_packages' => EL8_PACKAGES,
63
+ 'ssg' => {
64
+ 'profile_target' => 'rhel8',
65
+ 'build_target' => 'rhel8',
66
+ 'datastream' => 'ssg-rhel8-ds.xml'
67
+ }
68
+ }
69
+ },
70
+ 'CentOS' => {
71
+ '6' => {
72
+ 'required_packages' => EL_PACKAGES,
73
+ 'ssg' => {
74
+ 'profile_target' => 'rhel6',
75
+ 'build_target' => 'centos6',
76
+ 'datastream' => 'ssg-centos6-ds.xml'
77
+ }
78
+ },
79
+ '7' => {
80
+ 'required_packages' => EL_PACKAGES,
81
+ 'ssg' => {
82
+ 'profile_target' => 'rhel7',
83
+ 'build_target' => 'centos7',
84
+ 'datastream' => 'ssg-centos7-ds.xml'
85
+ }
86
+ },
87
+ '8' => {
88
+ 'required_packages' => EL8_PACKAGES,
89
+ 'ssg' => {
90
+ 'profile_target' => 'rhel8',
91
+ 'build_target' => 'centos8',
92
+ 'datastream' => 'ssg-centos8-ds.xml'
93
+ }
94
+ }
95
+ },
96
+ 'OracleLinux' => {
97
+ '7' => {
98
+ 'required_packages' => EL_PACKAGES,
99
+ 'ssg' => {
100
+ 'profile_target' => 'ol7',
101
+ 'build_target' => 'ol7',
102
+ 'datastream' => 'ssg-ol7-ds.xml'
103
+ },
104
+ '8' => {
105
+ 'required_packages' => EL8_PACKAGES,
106
+ 'ssg' => {
107
+ 'profile_target' => 'ol8',
108
+ 'build_target' => 'ol8',
109
+ 'datastream' => 'ssg-ol8-ds.xml'
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ attr_accessor :scap_working_dir
117
+
118
+ # Create a new SSG helper for the specified host
119
+ #
120
+ # @param sut
121
+ # The SUT against which to run
122
+ #
123
+ def initialize(sut)
124
+ @sut = sut
125
+
126
+ @os = fact_on(@sut, 'operatingsystem')
127
+ @os_rel = fact_on(@sut, 'operatingsystemmajrelease')
128
+
129
+ sut.mkdir_p('scap_working_dir')
130
+
131
+ @scap_working_dir = on(sut, 'cd scap_working_dir && pwd').stdout.strip
132
+
133
+ unless OS_INFO[@os]
134
+ fail("Error: The '#{@os}' Operating System is not supported")
135
+ end
136
+
137
+ OS_INFO[@os][@os_rel]['required_packages'].each do |pkg|
138
+ @sut.install_package(pkg)
139
+ end
140
+
141
+ @output_dir = File.absolute_path('sec_results/ssg')
142
+
143
+ unless File.directory?(@output_dir)
144
+ FileUtils.mkdir_p(@output_dir)
145
+ end
146
+
147
+ @result_file = "#{@sut.hostname}-ssg-#{Time.now.to_i}"
148
+
149
+
150
+ get_ssg_datastream
151
+ end
152
+
153
+ def profile_target
154
+ OS_INFO[@os][@os_rel]['ssg']['profile_target']
155
+ end
156
+
157
+ def remediate(profile)
158
+ evaluate(profile, true)
159
+ end
160
+
161
+ def evaluate(profile, remediate=false)
162
+ cmd = "cd #{@scap_working_dir}; oscap xccdf eval"
163
+
164
+ if remediate
165
+ cmd += ' --remediate'
166
+ end
167
+
168
+ cmd += %( --fetch-remote-resources --profile #{profile} --results #{@result_file}.xml --report #{@result_file}.html #{OS_INFO[@os][@os_rel]['ssg']['datastream']})
169
+
170
+ # We accept all exit codes here because there have occasionally been
171
+ # failures in the SSG content and we're not testing that.
172
+
173
+ on(@sut, cmd, :accept_all_exit_codes => true)
174
+
175
+ ['xml', 'html'].each do |ext|
176
+ path = "#{@scap_working_dir}/#{@result_file}.#{ext}"
177
+ scp_from(@sut, path, @output_dir)
178
+
179
+ fail("Could not retrieve #{path} from #{@sut}") unless File.exist?(File.join(@output_dir, "#{@result_file}.#{ext}"))
180
+ end
181
+ end
182
+
183
+ # Output the report
184
+ #
185
+ # @param report
186
+ # The results Hash
187
+ #
188
+ def write_report(report)
189
+ File.open(File.join(@output_dir, @result_file) + '.report', 'w') do |fh|
190
+ fh.puts(report[:report].uncolor)
191
+ end
192
+ end
193
+
194
+ # Retrieve a subset of test results based on a match to filter
195
+ #
196
+ # @param filter [String, Array[String]]
197
+ # A 'short name' filter that will be matched against the rule ID name
198
+ #
199
+ # @param exclusions [String, Array[String]]
200
+ # A 'short name' filter of items that will be removed from the `filter`
201
+ # matches
202
+ #
203
+ # @return [Hash] A Hash of statistics and a formatted report
204
+ #
205
+ # FIXME:
206
+ # - This is a hack! Should be searching for rules based on a set
207
+ # set of STIG ids, but don't see those ids in the oscap results xml.
208
+ # Further mapping is required...
209
+ # - Create the same report structure as inspec
210
+ def process_ssg_results(filter=nil, exclusions=nil)
211
+ self.class.process_ssg_results(
212
+ File.join(@output_dir, @result_file) + '.xml',
213
+ filter,
214
+ exclusions
215
+ )
216
+ end
217
+
218
+ # Process the results of an SSG run
219
+ #
220
+ # @param result_file [String]
221
+ # The oscap result XML file to process
222
+ #
223
+ # @param filter [String, Array[String]]
224
+ # A 'short name' filter that will be matched against the rule ID name
225
+ #
226
+ # @param exclusions [String, Array[String]]
227
+ # A 'short name' filter of items that will be removed from the `filter`
228
+ # matches
229
+ #
230
+ # @return [Hash] A Hash of statistics and a formatted report
231
+ #
232
+ def self.process_ssg_results(result_file, filter=nil, exclusions=nil)
233
+ require 'highline'
234
+ require 'nokogiri'
235
+
236
+ HighLine.colorize_strings
237
+
238
+ fail("Could not find results XML file '#{result_file}'") unless File.exist?(result_file)
239
+
240
+ puts "Processing #{result_file}"
241
+ doc = Nokogiri::XML(File.open(result_file))
242
+
243
+ # because I'm lazy
244
+ doc.remove_namespaces!
245
+
246
+ if filter
247
+ filter = Array(filter)
248
+
249
+ xpath_query = [
250
+ '//rule-result[(',
251
+ ]
252
+
253
+ xpath_query << filter.map do |flt|
254
+ "contains(@idref,'#{flt}')"
255
+ end.join(' or ')
256
+
257
+ xpath_query << ')' if filter.size > 1
258
+
259
+ if exclusions
260
+ exclusions = Array(exclusions)
261
+
262
+ xpath_query << 'and not('
263
+
264
+ xpath_query << exclusions.map do |exl|
265
+ "contains(@idref,'#{exl}')"
266
+ end.join(' or ')
267
+
268
+ xpath_query << ')' if exclusions.size > 1
269
+ end
270
+
271
+ xpath_query << ')]'
272
+
273
+ xpath_query = xpath_query.join(' ')
274
+
275
+ # XPATH to get the pertinent test results:
276
+ # Any node named 'rule-result' for which the attribute 'idref'
277
+ # contains any of the `filter` Strings and does not contain any of the
278
+ # `exclusions` Strings
279
+ result_nodes = doc.xpath(xpath_query)
280
+ else
281
+ result_nodes = doc.xpath('//rule-result')
282
+ end
283
+
284
+ stats = {
285
+ :failed => [],
286
+ :passed => [],
287
+ :skipped => [],
288
+ :filter => filter.nil? ? 'No Filter' : filter,
289
+ :report => nil,
290
+ :score => 0
291
+ }
292
+
293
+ result_nodes.each do |rule_result|
294
+ # Results are recorded in a child node named 'result'.
295
+ # Within the 'result' node, the actual result string is
296
+ # the content of that node's (only) child node.
297
+
298
+ result = rule_result.element_children.at('result')
299
+ result_id = rule_result.attributes['idref'].value.to_s
300
+ result_value = [
301
+ 'Title: ' + doc.xpath("//Rule[@id='#{result_id}']/title/text()").first.to_s,
302
+ ' ID: ' + result_id
303
+ ].join("\n")
304
+
305
+ if result.child.content == 'fail'
306
+ stats[:failed] << result_value.red
307
+ elsif result.child.content == 'pass'
308
+ stats[:passed] << result_value.green
309
+ else
310
+ stats[:skipped] << result_value.yellow
311
+ end
312
+ end
313
+
314
+ report = []
315
+
316
+ report << '== Skipped =='
317
+ report << stats[:skipped].join("\n")
318
+
319
+ report << '== Passed =='
320
+ report << stats[:passed].join("\n")
321
+
322
+ report << '== Failed =='
323
+ report << stats[:failed].join("\n")
324
+
325
+
326
+ report << 'OSCAP Statistics:'
327
+
328
+ if filter
329
+ report << " * Used Filter: 'idref' ~= '#{stats[:filter]}'"
330
+ end
331
+
332
+ report << " * Passed: #{stats[:passed].count.to_s.green}"
333
+ report << " * Failed: #{stats[:failed].count.to_s.red}"
334
+ report << " * Skipped: #{stats[:skipped].count.to_s.yellow}"
335
+
336
+ score = 0
337
+
338
+ if (stats[:passed].count + stats[:failed].count) > 0
339
+ score = ((stats[:passed].count.to_f/(stats[:passed].count + stats[:failed].count)) * 100.0).round(0)
340
+ end
341
+
342
+ report << "\n Score: #{score}%"
343
+
344
+ stats[:score] = score
345
+ stats[:report] = report.join("\n")
346
+
347
+ return stats
348
+ end
349
+
350
+ private
351
+
352
+ def get_ssg_datastream
353
+ # Allow users to point at a specific SSG release 'tar.bz2' file
354
+ ssg_release = ENV['BEAKER_ssg_release']
355
+
356
+ # Grab the latest SSG release in fixtures if it exists
357
+ ssg_release ||= Dir.glob('spec/fixtures/ssg_releases/*.bz2').last
358
+
359
+ if ssg_release
360
+ copy_to(@sut, ssg_release, @scap_working_dir)
361
+
362
+ on(@sut, %(mkdir -p scap-content && tar -xj -C scap-content --strip-components 1 -f #{ssg_release} && cp scap-content/*ds.xml #{@scap_working_dir}))
363
+ else
364
+ on(@sut, %(git clone #{GIT_REPO} scap-content))
365
+ if GIT_BRANCH
366
+ on(@sut, %(cd scap-content; git checkout #{GIT_BRANCH}))
367
+ else
368
+ on(@sut, %(cd scap-content; git checkout $(git describe --abbrev=0 --tags)))
369
+ end
370
+
371
+ # Work around the issue where the profiles now strip out derivative
372
+ # content that isn't explicitlly approved for that OS. This means that
373
+ # we are unable to test CentOS builds against the STIG, etc...
374
+ #
375
+ # This isn't 100% correct but it's "good enough" for an automated CI
376
+ # environment to tell us if something is critically out of alignment.
377
+ on(@sut, %(cd scap-content/build-scripts; sed -i 's/ssg.build_derivatives.profile_handling/#ssg.build_derivatives.profile_handling/g' enable_derivatives.py))
378
+
379
+ on(@sut, %(cd scap-content/build; cmake ../; make -j4 #{OS_INFO[@os][@os_rel]['ssg']['build_target']}-content && cp *ds.xml #{@scap_working_dir}))
380
+ end
381
+ end
382
+ end
383
+ end
@@ -0,0 +1,5 @@
1
+ module Simp; end
2
+
3
+ module Simp::BeakerHelpers
4
+ VERSION = '1.18.8'
5
+ end
@@ -0,0 +1,16 @@
1
+ module Simp; end
2
+ module Simp::BeakerHelpers; end
3
+
4
+ module Simp::BeakerHelpers::Windows
5
+ begin
6
+ require 'beaker-windows'
7
+ rescue LoadError
8
+ logger.error(%{You must include 'beaker-windows' in your Gemfile for windows support})
9
+ exit 1
10
+ end
11
+
12
+ include BeakerWindows::Path
13
+ include BeakerWindows::Powershell
14
+ include BeakerWindows::Registry
15
+ include BeakerWindows::WindowsFeature
16
+ end
@@ -0,0 +1,269 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/tasklib'
4
+ require 'fileutils'
5
+ require 'puppetlabs_spec_helper/tasks/beaker'
6
+ require 'puppetlabs_spec_helper/tasks/fixtures'
7
+
8
+ module Simp; end
9
+ module Simp::Rake
10
+ class Beaker < ::Rake::TaskLib
11
+ def initialize(base_dir)
12
+
13
+ @base_dir = base_dir
14
+ @clean_list = []
15
+
16
+ ::CLEAN.include( %{#{@base_dir}/log} )
17
+ ::CLEAN.include( %{#{@base_dir}/junit} )
18
+ ::CLEAN.include( %{#{@base_dir}/sec_results} )
19
+ ::CLEAN.include( %{#{@base_dir}/spec/fixtures/inspec_deps} )
20
+
21
+ yield self if block_given?
22
+
23
+ ::CLEAN.include( @clean_list )
24
+
25
+ namespace :beaker do
26
+ desc <<-EOM
27
+ Run a Beaker test against a specific Nodeset
28
+ * :nodeset - The nodeset against which you wish to run
29
+ EOM
30
+ task :run, [:nodeset] do |t,args|
31
+ fail "You must pass :nodeset to #{t}" unless args[:nodeset]
32
+ nodeset = args[:nodeset].strip
33
+
34
+ old_stdout = $stdout
35
+ nodesets = StringIO.new
36
+ $stdout = nodesets
37
+
38
+ Rake::Task['beaker_nodes'].invoke
39
+
40
+ $stdout = old_stdout
41
+
42
+ nodesets = nodesets.string.split("\n")
43
+
44
+ fail "Nodeset '#{nodeset}' not found. Valid Nodesets:\n#{nodesets.map{|x| x = %( * #{x})}.join(%(\n))}" unless nodesets.include?(nodeset)
45
+
46
+ ENV['BEAKER_set'] = nodeset
47
+
48
+ Rake::Task['beaker'].invoke
49
+ end
50
+
51
+ desc <<-EOM
52
+ Run Beaker test suites.
53
+ * :suite - A specific suite to run
54
+ * If you set this to `ALL`, all suites will be run
55
+
56
+ * :nodeset - A specific nodeset to run on within a specific suite
57
+ * If you set this to `ALL`, all nodesets will be looped through, starting with 'default'
58
+ * You may also set this to a colon delimited list of short
59
+ nodeset names that will be run in that order
60
+
61
+ ## Suite Execution
62
+
63
+ By default the only suite that will be executed is `default`.
64
+ Since each suite is executed in a new environment, spin up can
65
+ take a lot of time. Therefore, the default is to only run the
66
+ default suite.
67
+
68
+ If there is a suite where the metadata contains `default_run` set
69
+ to the Boolean `true`, then that suite will be part of the
70
+ default suite execution.
71
+
72
+ You can run all suites by setting the passed suite name to `ALL`
73
+ (case sensitive).
74
+
75
+ ## Environment Variables
76
+
77
+ * BEAKER_suite_runall
78
+ * Run all Suites
79
+
80
+ * BEAKER_suite_basedir
81
+ * The base directory where suites will be defined
82
+ * Default: spec/acceptance
83
+
84
+ ## Global Suite Configuration
85
+ A file `config.yml` can be placed in the `suites` directory to
86
+ control certain aspects of the suite run.
87
+
88
+ ### Supported Config:
89
+
90
+ ```yaml
91
+ ---
92
+ # Fail the entire suite at the first failure
93
+ 'fail_fast' : <true|false> => Default: true
94
+ ```
95
+ ## Individual Suite Configuration
96
+
97
+ Each suite may contain a YAML file, metadata.yml, which will be
98
+ used to provide information to the suite of tests.
99
+
100
+ ### Supported Config:
101
+
102
+ ```yaml
103
+ ---
104
+ 'name' : '<User friendly name for the suite>'
105
+
106
+ # Run this suite by default
107
+ 'default_run' : <true|false> => Default: false
108
+ ```
109
+ EOM
110
+ task :suites, [:suite, :nodeset] => ['spec_prep'] do |t,args|
111
+ suite = args[:suite]
112
+ nodeset = args[:nodeset]
113
+
114
+ # Record Tasks That Fail
115
+ # Need to figure out how to capture the errors
116
+ failures = Hash.new
117
+
118
+ suite_basedir = File.join(@base_dir, 'spec/acceptance/suites')
119
+
120
+ if ENV['BEAKER_suite_basedir']
121
+ suite_basedir = ENV['BEAKER_suite_basedir']
122
+ end
123
+
124
+ raise("Error: Suites Directory at '#{suite_basedir}'!") unless File.directory?(suite_basedir)
125
+
126
+ if suite && (suite != 'ALL')
127
+ unless File.directory?(File.join(suite_basedir, suite))
128
+ STDERR.puts("Error: Could not find suite '#{suite}'")
129
+ STDERR.puts("Available Suites:")
130
+ STDERR.puts(' * ' + Dir.glob(
131
+ File.join(suite_basedir, '*')).sort.map{ |x|
132
+ File.basename(x)
133
+ }.join("\n * ")
134
+ )
135
+ exit(1)
136
+ end
137
+ end
138
+
139
+ suite_config = {
140
+ 'fail_fast' => true
141
+ }
142
+ suite_config_metadata_path = File.join(suite_basedir, 'config.yml')
143
+ if File.file?(suite_config_metadata_path)
144
+ suite_config.merge!(YAML.load_file(suite_config_metadata_path))
145
+ end
146
+
147
+ suites = Hash.new
148
+
149
+ if suite && (suite != 'ALL')
150
+ suites[suite] = Hash.new
151
+ # If a suite was set, make sure it runs.
152
+ suites[suite]['default_run'] = true
153
+ else
154
+ Dir.glob(File.join(suite_basedir,'*')) do |file|
155
+ if File.directory?(file)
156
+ suites[File.basename(file)] = Hash.new
157
+
158
+ if suite == 'ALL'
159
+ suites[File.basename(file)]['default_run'] = true
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ suites.keys.each do |ste|
166
+ suites[ste]['name'] = ste
167
+ suites[ste]['path'] = File.join(suite_basedir, ste)
168
+
169
+ metadata_path = File.join(suites[ste]['path'], 'metadata.yml')
170
+ if File.file?(metadata_path)
171
+ suites[ste]['metadata'] = YAML.load_file(metadata_path)
172
+ end
173
+
174
+ unless File.directory?(File.join(suites[ste]['path'],'nodesets'))
175
+ Dir.chdir(suites[ste]['path']) do
176
+ if File.directory?('../../nodesets')
177
+ FileUtils.ln_s('../../nodesets', 'nodesets')
178
+ end
179
+ end
180
+ end
181
+
182
+ suites[ste].merge!(suites[ste]['metadata']) if suites[ste]['metadata']
183
+
184
+ # Ensure that the 'default' suite runs unless explicitly disabled.
185
+ if suites['default']
186
+ if ( suites['default']['default_run'].nil? ) || ( suites['default']['default_run'] == true )
187
+ suites['default']['default_run'] = true
188
+ end
189
+ end
190
+ end
191
+
192
+ raise("Error: No Suites Found in '#{suite_basedir}'!") if suites.empty?
193
+
194
+ # Need to ensure that 'default' is first
195
+ ordered_suites = suites.keys.sort
196
+ default_suite = ordered_suites.delete('default')
197
+ ordered_suites.unshift(default_suite) if default_suite
198
+
199
+ ordered_suites.each do |ste|
200
+
201
+ next unless (suites[ste]['default_run'] == true)
202
+
203
+ name = suites[ste]['name']
204
+
205
+ $stdout.puts("\n\n=== Suite '#{name}' Starting ===\n\n")
206
+
207
+ nodesets = Array.new
208
+ nodeset_path = File.join(suites[ste]['path'],'nodesets')
209
+
210
+ if nodeset
211
+ if nodeset == 'ALL'
212
+ nodesets = Dir.glob(File.join(nodeset_path, '*.yml'))
213
+
214
+ # Make sure we run the default set first
215
+ default_set = nodesets.delete(File.join(nodeset_path, 'default.yml'))
216
+ nodesets.unshift(default_set) if default_set
217
+ else
218
+ nodeset.split(':').each do |tgt_nodeset|
219
+ nodesets << File.join(nodeset_path, "#{tgt_nodeset.strip}.yml")
220
+ end
221
+ end
222
+ else
223
+ nodesets << File.join(nodeset_path, 'default.yml')
224
+ end
225
+
226
+ nodesets.each do |nodeset_yml|
227
+ unless File.file?(nodeset_yml)
228
+ $stdout.puts("=== Suite #{name} Nodeset '#{File.basename(nodeset_yml, '.yml')}' Not Found, Skipping ===")
229
+ next
230
+ end
231
+
232
+ ENV['BEAKER_setfile'] = nodeset_yml
233
+
234
+ Rake::Task[:beaker].clear
235
+ RSpec::Core::RakeTask.new(:beaker) do |tsk|
236
+ tsk.rspec_opts = ['--color']
237
+ tsk.pattern = File.join(suites[ste]['path'])
238
+ end
239
+
240
+ current_suite_task = Rake::Task[:beaker]
241
+
242
+ if suite_config['fail_fast'] == true
243
+ current_suite_task.execute
244
+ else
245
+ begin
246
+ current_suite_task.execute
247
+ rescue SystemExit
248
+ failures[suites[ste]['name']] = {
249
+ 'path' => suites[ste]['path'],
250
+ 'nodeset' => File.basename(nodeset_yml, '.yml')
251
+ }
252
+ end
253
+ end
254
+
255
+ $stdout.puts("\n\n=== Suite '#{name}' Complete ===\n\n")
256
+ end
257
+ end
258
+
259
+ unless failures.keys.empty?
260
+ $stdout.puts("The following tests had failures:")
261
+ failures.keys.sort.each do |ste|
262
+ $stdout.puts(" * #{ste} => #{failures[ste]['path']} on #{failures[ste]['nodeset']}")
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end