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