simp-beaker-helpers 1.18.8
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 +7 -0
- data/.fixtures.yml +8 -0
- data/.gitignore +8 -0
- data/.gitlab-ci.yml +163 -0
- data/.rspec +4 -0
- data/.rubocop.yml +546 -0
- data/.travis.yml +36 -0
- data/CHANGELOG.md +231 -0
- data/Gemfile +51 -0
- data/LICENSE +27 -0
- data/README.md +543 -0
- data/Rakefile +151 -0
- data/files/pki/clean.sh +1 -0
- data/files/pki/make.sh +101 -0
- data/files/pki/template_ca.cnf +259 -0
- data/files/pki/template_host.cnf +263 -0
- data/files/puppet-agent-versions.yaml +46 -0
- data/lib/simp/beaker_helpers.rb +1231 -0
- data/lib/simp/beaker_helpers/constants.rb +25 -0
- data/lib/simp/beaker_helpers/inspec.rb +328 -0
- data/lib/simp/beaker_helpers/snapshot.rb +156 -0
- data/lib/simp/beaker_helpers/ssg.rb +383 -0
- data/lib/simp/beaker_helpers/version.rb +5 -0
- data/lib/simp/beaker_helpers/windows.rb +16 -0
- data/lib/simp/rake/beaker.rb +269 -0
- data/simp-beaker-helpers.gemspec +38 -0
- data/spec/acceptance/nodesets/default.yml +32 -0
- data/spec/acceptance/suites/default/check_puppet_version_spec.rb +23 -0
- data/spec/acceptance/suites/default/enable_fips_spec.rb +23 -0
- data/spec/acceptance/suites/default/fixture_modules_spec.rb +22 -0
- data/spec/acceptance/suites/default/install_simp_deps_repo_spec.rb +43 -0
- data/spec/acceptance/suites/default/nodesets +1 -0
- data/spec/acceptance/suites/default/pki_tests_spec.rb +55 -0
- data/spec/acceptance/suites/default/set_hieradata_on_spec.rb +33 -0
- data/spec/acceptance/suites/default/write_hieradata_to_spec.rb +33 -0
- data/spec/acceptance/suites/fips_from_fixtures/00_default_spec.rb +63 -0
- data/spec/acceptance/suites/fips_from_fixtures/metadata.yml +2 -0
- data/spec/acceptance/suites/fips_from_fixtures/nodesets +1 -0
- data/spec/acceptance/suites/offline/00_default_spec.rb +165 -0
- data/spec/acceptance/suites/offline/README +2 -0
- data/spec/acceptance/suites/offline/nodesets/default.yml +26 -0
- data/spec/acceptance/suites/puppet_collections/00_default_spec.rb +25 -0
- data/spec/acceptance/suites/puppet_collections/metadata.yml +2 -0
- data/spec/acceptance/suites/puppet_collections/nodesets/default.yml +30 -0
- data/spec/acceptance/suites/snapshot/00_snapshot_test_spec.rb +82 -0
- data/spec/acceptance/suites/snapshot/10_general_usage_spec.rb +56 -0
- data/spec/acceptance/suites/snapshot/nodesets +1 -0
- data/spec/acceptance/suites/windows/00_default_spec.rb +119 -0
- data/spec/acceptance/suites/windows/metadata.yml +2 -0
- data/spec/acceptance/suites/windows/nodesets/default.yml +33 -0
- data/spec/acceptance/suites/windows/nodesets/win2016.yml +35 -0
- data/spec/acceptance/suites/windows/nodesets/win2019.yml +34 -0
- data/spec/lib/simp/beaker_helpers_spec.rb +216 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/spec_helper_acceptance.rb +25 -0
- 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,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
|