simp-beaker-helpers 2.0.5 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '01691b723cf1d20c20093a46cc90eaba1f0d3bd947e517388d963c86c086a406'
4
- data.tar.gz: 5cdc26846cbefaf342a15200542dd801c00947677b9e553ec741bc0bd62ae71b
3
+ metadata.gz: 249a3f841428c2b28cbe7961183d25344edc726652e3ae5bb66c084e9a30b2d9
4
+ data.tar.gz: 29fb61f0eb9dd16251d1389dd5b68457b93e3bbd81946e52a153dc82ace40fbd
5
5
  SHA512:
6
- metadata.gz: fcfbe6df23fe2fcb941349958cdd4d1645ffc61592c417ad52ce28975de51b44f795e6be786e297b9a8e6ca8a23aaf01898a0ef181f075dea6219ff5a595aa8d
7
- data.tar.gz: 1efc45ea2fb279ffa9795c4f31510be99ae565dcb36bdc5366bf8056d11a9d0bf8d6fd5dcbca1d18fc1d38c3a4912c965290ff88489f12bb1a55476fea08e4f0
6
+ metadata.gz: d42a18f587b5c613a0ce1d4af8946be6f070e258e1012e8b1811dd63d93caa0fe967205b545ddc3462b87d37d3df58902bb5aca8705528750e08eb80166b80ab
7
+ data.tar.gz: 5ddf2f564b29b9eb93e8d02c5bb5670d3019cf2ed2950c8083cbbc5d0a29ddca349b5009e1ec81242292af0d80b84149c0afcda54c97dc443cbe1b50584bbc9f
@@ -17,14 +17,6 @@ jobs:
17
17
  strategy:
18
18
  matrix:
19
19
  puppet:
20
- - label: 'Puppet 7.x [SIMP 6.6/PE 2021.7]'
21
- puppet_version: '~> 7.0'
22
- ruby_version: '2.7'
23
- experimental: false
24
- - label: 'Puppet 8.x'
25
- puppet_version: '~> 8.0'
26
- ruby_version: '3.2'
27
- experimental: false
28
20
  - label: 'OpenVox 8.x'
29
21
  puppet_version: '~> 8.0'
30
22
  ruby_version: '3.2'
@@ -37,9 +29,6 @@ jobs:
37
29
  - label: beaker puppet_collections
38
30
  suite: puppet_collections
39
31
  allowed_to_fail: false
40
- - label: beaker ssg
41
- suite: ssg
42
- allowed_to_fail: false
43
32
  # The inspec suite fails for unclear reasons during an scp.
44
33
  # Sicura tests compliance at the framework level, so troubleshooting
45
34
  # inspec acceptance tests is not a priority at present.
@@ -51,7 +40,7 @@ jobs:
51
40
  PUPPET_VERSION: ${{matrix.puppet.puppet_version}}
52
41
  steps:
53
42
  - name: checkout repo
54
- uses: actions/checkout@v6
43
+ uses: actions/checkout@v7
55
44
  - name: setup ruby
56
45
  uses: ruby/setup-ruby@v1
57
46
  with:
@@ -32,7 +32,7 @@ jobs:
32
32
  runs-on: ubuntu-latest
33
33
  continue-on-error: true
34
34
  steps:
35
- - uses: actions/checkout@v6
35
+ - uses: actions/checkout@v7
36
36
  - name: "Install Ruby 3.2"
37
37
  uses: ruby/setup-ruby@v1
38
38
  with:
@@ -48,18 +48,20 @@ jobs:
48
48
  strategy:
49
49
  matrix:
50
50
  puppet:
51
- - label: 'Puppet 7.x [SIMP 6.6/PE 2021.7]'
52
- puppet_version: '~> 7.0'
53
- ruby_version: '2.7'
54
- experimental: false
55
- - label: 'Puppet 8.x'
51
+ - label: 'OpenVox 8.x'
56
52
  puppet_version: '~> 8.0'
57
53
  ruby_version: '3.2'
58
54
  experimental: false
55
+ # OpenVox 9 is unreleased; preview the future Ruby 4.0 / OpenVox 9
56
+ # combo by running the OpenVox 8 gem on Ruby 4.0.
57
+ - label: 'OpenVox 9.x preview (Ruby 4.0, OpenVox 8 gem)'
58
+ puppet_version: '~> 8.0'
59
+ ruby_version: '4.0'
60
+ experimental: false
59
61
  env:
60
62
  PUPPET_VERSION: ${{matrix.puppet.puppet_version}}
61
63
  steps:
62
- - uses: actions/checkout@v6
64
+ - uses: actions/checkout@v7
63
65
  - name: 'Install Ruby ${{matrix.puppet.ruby_version}}'
64
66
  uses: ruby/setup-ruby@v1
65
67
  with:
@@ -48,7 +48,7 @@ on:
48
48
  - '[0-9]+\.[0-9]+\.[0-9]+\-[a-z]+[0-9]+'
49
49
 
50
50
  env:
51
- PUPPET_VERSION: '~> 7'
51
+ PUPPET_VERSION: '~> 8'
52
52
  LOCAL_WORKFLOW_CONFIG_FILE: .github/workflows.local.json
53
53
 
54
54
  jobs:
@@ -63,7 +63,7 @@ jobs:
63
63
  steps:
64
64
  - name: "Assert '${{ github.ref }}' is a tag"
65
65
  run: '[[ "$GITHUB_REF" =~ ^refs/tags/ ]] || { echo "::error ::GITHUB_REF is not a tag: ${GITHUB_REF}"; exit 1 ; }'
66
- - uses: actions/checkout@v6
66
+ - uses: actions/checkout@v7
67
67
  with:
68
68
  ref: ${{ github.ref }}
69
69
  clean: true
@@ -116,7 +116,7 @@ jobs:
116
116
  tag: ${{ steps.tag-check.outputs.tag }}
117
117
  steps:
118
118
  - name: Checkout code
119
- uses: actions/checkout@v6
119
+ uses: actions/checkout@v7
120
120
  with:
121
121
  ref: ${{ github.ref }}
122
122
  clean: true
@@ -178,7 +178,7 @@ jobs:
178
178
  PKG_DIR: ${{ needs.releng-checks.outputs.pkg_dir }}
179
179
  steps:
180
180
  - name: Checkout code
181
- uses: actions/checkout@v6
181
+ uses: actions/checkout@v7
182
182
  with:
183
183
  ref: ${{ github.ref }}
184
184
  clean: true
data/CHANGELOG.md CHANGED
@@ -1,7 +1,39 @@
1
+ ### 3.1.0 / 2026-07-02
2
+ * Added:
3
+ * `ensure_beaker_ip_on`: detect SUTs whose recorded `host[:ip]` was never
4
+ actually applied (e.g. EL10 under Vagrant, where the legacy ifcfg files
5
+ Vagrant writes for static private-network IPs are no longer supported and
6
+ the interface silently falls back to DHCP) and (re-)apply the expected
7
+ address via NetworkManager (reboot-safe), falling back to `ip addr add`.
8
+ Runs automatically alongside `activate_interfaces` in the global
9
+ `before(:all)` hook; disable with `BEAKER_no_fix_interfaces`.
10
+
11
+ ### 3.0.0 / 2026-06-24
12
+ * Changed (**Breaking**):
13
+ * Dropped support for Puppet 7 / Ruby 2.7. CI now tests Puppet/OpenVox 8 only,
14
+ and the default OpenVox gem floor is raised to `>= 8.0.0`.
15
+ * Replaced the `puppetlabs_spec_helper/tasks/fixtures` dependency with
16
+ `puppet_fixtures`, enabling Ruby 4.0 support. `puppetlabs_spec_helper` pulls
17
+ in `puppet-syntax` < 5, which depends on the `puppet` gem and its `facter`
18
+ dependency that does not support Ruby >= 4.0; `puppet_fixtures` is the
19
+ OpenVox-ecosystem replacement and supports Ruby 4.0.
20
+ * The `beaker:suites` task now depends on `fixtures:prep` (from
21
+ `puppet_fixtures`) instead of `spec_prep`, and `ensure_fixture_modules` now
22
+ runs `rake fixtures:prep`.
23
+ * Removed (**Breaking**):
24
+ * Removed the unsupported `Simp::BeakerHelpers::SSG` helpers, the `ssg`
25
+ acceptance suite, and the `SSG_REPO_URL` constant (#268)
26
+ * Added:
27
+ * `puppet_fixtures` runtime dependency
28
+ * OpenVox-named environment variables for `get_puppet_install_info`, taking
29
+ precedence over the Puppet-named equivalents (which remain as fallbacks):
30
+ `OPENVOX_VERSION`, `OPENVOX_INSTALL_VERSION`, `OPENVOX_INSTALL_TYPE`,
31
+ `BEAKER_OPENVOX_AGENT_VERSION`, `BEAKER_OPENVOX_COLLECTION`,
32
+ `BEAKER_OPENVOX_PACKAGE_NAME`
33
+
1
34
  ### 2.0.5 / 2026-06-06
2
35
  * Fixed:
3
36
  * Additional cleanup for rubocop
4
-
5
37
  ### 2.0.4 / 2025-10-16
6
38
  * Fixed:
7
39
  * `install_puppet` failure on Windows (#266)
data/Gemfile CHANGED
@@ -41,15 +41,14 @@ group :system_tests do
41
41
  gem 'ed25519'
42
42
  gem 'net-ssh'
43
43
  # renovate: datasource=rubygems versioning=ruby
44
- gem 'openvox', ENV.fetch('OPENVOX_VERSION', ENV.fetch('PUPPET_VERSION', ['>= 7.0.0', '< 9.0.0']))
45
- gem 'pry-byebug', '~> 3.10.0'
46
- gem 'puppetlabs_spec_helper', '>= 4.0.0', '< 9.0.0'
44
+ gem 'openvox', ENV.fetch('OPENVOX_VERSION', ENV.fetch('PUPPET_VERSION', ['>= 8.0.0', '< 9.0.0']))
45
+ gem 'pry-byebug', '~> 3.12.0'
47
46
  gem 'syslog' # Required for Ruby >= 3.4
48
47
  end
49
48
 
50
49
  group :tests do
51
- gem 'rubocop', '~> 1.87.0'
50
+ gem 'rubocop', '~> 1.88.0'
52
51
  gem 'rubocop-performance', '~> 1.26.0'
53
52
  gem 'rubocop-rake', '~> 0.7.0'
54
- gem 'rubocop-rspec', '~> 3.8.0'
53
+ gem 'rubocop-rspec', '~> 3.10.0'
55
54
  end
@@ -9,8 +9,6 @@ module Simp::BeakerHelpers # rubocop:disable Style/OneClassPerFile
9
9
  # oldest system that we support
10
10
  DEFAULT_PUPPET_AGENT_VERSION = '~> 8.0'.freeze
11
11
 
12
- SSG_REPO_URL = ENV['BEAKER_ssg_repo'] || 'https://github.com/ComplianceAsCode/content.git'
13
-
14
12
  if ['true', 'yes'].include?(ENV['BEAKER_online'])
15
13
  ONLINE = true
16
14
  elsif ['false', 'no'].include?(ENV['BEAKER_online'])
@@ -4,5 +4,5 @@
4
4
  module Simp; end
5
5
 
6
6
  module Simp::BeakerHelpers # rubocop:disable Style/OneClassPerFile
7
- VERSION = '2.0.5'
7
+ VERSION = '3.1.0'
8
8
  end
@@ -14,7 +14,6 @@ module Simp::BeakerHelpers # rubocop:disable Style/OneClassPerFile
14
14
  require 'simp/beaker_helpers/constants'
15
15
  require 'simp/beaker_helpers/inspec'
16
16
  require 'simp/beaker_helpers/snapshot'
17
- require 'simp/beaker_helpers/ssg'
18
17
  require 'simp/beaker_helpers/version'
19
18
  require 'find'
20
19
 
@@ -367,8 +366,8 @@ module Simp::BeakerHelpers # rubocop:disable Style/OneClassPerFile
367
366
  if missing_modules.empty?
368
367
  puts ' == all fixture modules present'
369
368
  else
370
- cmd = 'bundle exec rake spec_prep'
371
- puts " -- running spec_prep: '#{cmd}'"
369
+ cmd = 'bundle exec rake fixtures:prep'
370
+ puts " -- running fixtures:prep: '#{cmd}'"
372
371
  `#{cmd}`
373
372
  end
374
373
  end
@@ -1139,6 +1138,64 @@ module Simp::BeakerHelpers # rubocop:disable Style/OneClassPerFile
1139
1138
  end
1140
1139
  end
1141
1140
 
1141
+ # Ensure that the IP address that Beaker has recorded for each SUT is
1142
+ # actually present on the system and (re-)apply it if it is not.
1143
+ #
1144
+ # Some OS/hypervisor combinations silently fail to configure the static IP
1145
+ # for secondary (private) networks. In particular, Vagrant configures
1146
+ # static IPs on RedHat-family guests by writing legacy ifcfg files, which
1147
+ # EL10 no longer supports at all. The interface falls back to NetworkManager
1148
+ # DHCP and receives an arbitrary address, so everything that targets the
1149
+ # recorded `host[:ip]` (cross-SUT traffic, direct Net::SSH logins, etc.)
1150
+ # fails with 'No route to host'.
1151
+ #
1152
+ # This detects the mismatch and applies the expected address to the
1153
+ # interface that is attached to the same network, using NetworkManager so
1154
+ # that the address survives reboots, and falling back to a runtime-only
1155
+ # `ip addr add` when NetworkManager is not in use.
1156
+ def ensure_beaker_ip_on(hosts)
1157
+ return if ENV['BEAKER_no_fix_interfaces']
1158
+
1159
+ block_on(hosts, run_in_parallel: @run_in_parallel) do |host|
1160
+ expected_ip = host[:ip]
1161
+ next unless expected_ip
1162
+ next if host[:platform].include?('windows')
1163
+
1164
+ # All currently assigned IPv4 addresses ('<iface> <address>/<prefix>')
1165
+ addrs = on(host, %(ip -o -4 addr show), accept_all_exit_codes: true).stdout.lines.map { |line|
1166
+ next unless line =~ %r{^\d+:\s+(\S+)\s+inet\s+(\S+)/(\d+)}
1167
+ { iface: Regexp.last_match(1), ip: Regexp.last_match(2), prefix: Regexp.last_match(3) }
1168
+ }.compact
1169
+
1170
+ next if addrs.any? { |a| a[:ip] == expected_ip }
1171
+
1172
+ require 'ipaddr'
1173
+ target = addrs.find do |a|
1174
+ IPAddr.new("#{a[:ip]}/#{a[:prefix]}").include?(expected_ip)
1175
+ rescue IPAddr::Error
1176
+ false
1177
+ end
1178
+
1179
+ unless target
1180
+ puts " -- WARNING: #{host} does not have expected IP #{expected_ip} and no interface on that network could be found"
1181
+ next
1182
+ end
1183
+
1184
+ puts " -- FIXING: #{host} expected IP #{expected_ip} but '#{target[:iface]}' has #{target[:ip]}"
1185
+
1186
+ nm_connection = on(host, %(nmcli -g GENERAL.CONNECTION device show #{target[:iface]}), accept_all_exit_codes: true).stdout.strip
1187
+ if nm_connection.empty?
1188
+ # Not NetworkManager-managed => runtime-only fallback
1189
+ on(host, %(ip addr add #{expected_ip}/#{target[:prefix]} dev #{target[:iface]}), accept_all_exit_codes: true)
1190
+ else
1191
+ # Match Vagrant's static network behavior: fixed address, no gateway,
1192
+ # and never a default route on the private interface
1193
+ on(host, %(nmcli connection modify "#{nm_connection}" ipv4.method manual ipv4.addresses "#{expected_ip}/#{target[:prefix]}" ipv4.gateway "" ipv4.never-default yes))
1194
+ on(host, %(nmcli connection up "#{nm_connection}"))
1195
+ end
1196
+ end
1197
+ end
1198
+
1142
1199
  ## Inline Hiera Helpers ##
1143
1200
  ## These will be integrated into core Beaker at some point ##
1144
1201
 
@@ -1153,6 +1210,10 @@ module Simp::BeakerHelpers # rubocop:disable Style/OneClassPerFile
1153
1210
 
1154
1211
  # We can't guarantee that the upstream vendor isn't disabling interfaces
1155
1212
  activate_interfaces(hosts)
1213
+
1214
+ # Some OS/hypervisor combinations (e.g. EL10 under Vagrant) fail to
1215
+ # apply the static IP that Beaker expects for private networks
1216
+ ensure_beaker_ip_on(hosts)
1156
1217
  end
1157
1218
 
1158
1219
  c.after(:all) do
@@ -1426,16 +1487,25 @@ module Simp::BeakerHelpers # rubocop:disable Style/OneClassPerFile
1426
1487
  # install version and a puppet collection are specified. This is
1427
1488
  # because the puppet install version can specify more precise
1428
1489
  # version information than is available from a puppet collection.
1490
+ #
1491
+ # Each Puppet-named environment variable has an OpenVox-named
1492
+ # equivalent (e.g. OPENVOX_VERSION, BEAKER_OPENVOX_COLLECTION) that
1493
+ # takes precedence; the Puppet-named variables are kept as fallbacks
1494
+ # for backwards compatibility.
1429
1495
  def get_puppet_install_info
1430
- # The first match is internal Beaker and the second is legacy SIMP
1431
- puppet_install_version = ENV['BEAKER_PUPPET_AGENT_VERSION'] || ENV['PUPPET_INSTALL_VERSION'] || ENV['PUPPET_VERSION']
1496
+ # OpenVox-named variables win; the Puppet-named ones remain as fallbacks.
1497
+ # Within each pair the first match is internal Beaker, the second legacy SIMP.
1498
+ puppet_install_version =
1499
+ ENV['BEAKER_OPENVOX_AGENT_VERSION'] || ENV['BEAKER_PUPPET_AGENT_VERSION'] ||
1500
+ ENV['OPENVOX_INSTALL_VERSION'] || ENV['PUPPET_INSTALL_VERSION'] ||
1501
+ ENV['OPENVOX_VERSION'] || ENV['PUPPET_VERSION']
1432
1502
 
1433
1503
  if puppet_install_version && !puppet_install_version.strip.empty?
1434
1504
  puppet_agent_version = latest_puppet_agent_version_for(puppet_install_version.strip)
1435
1505
  end
1436
1506
 
1437
1507
  if puppet_agent_version.nil?
1438
- if (puppet_collection = ENV['BEAKER_PUPPET_COLLECTION'] || host.options['puppet_collection'])
1508
+ if (puppet_collection = ENV['BEAKER_OPENVOX_COLLECTION'] || ENV['BEAKER_PUPPET_COLLECTION'] || host.options['puppet_collection'])
1439
1509
  raise("Error: Puppet Collection '#{puppet_collection}' must match /(puppet|openvox)(\\d+)/") unless puppet_collection =~ %r{(puppet|openvox)(\d+)}
1440
1510
  puppet_collection_name = ::Regexp.last_match(1)
1441
1511
  puppet_install_version = "~> #{::Regexp.last_match(2)}"
@@ -1453,18 +1523,18 @@ module Simp::BeakerHelpers # rubocop:disable Style/OneClassPerFile
1453
1523
  {
1454
1524
  puppet_install_version: puppet_agent_version,
1455
1525
  puppet_collection: puppet_collection,
1456
- puppet_install_type: ENV.fetch('PUPPET_INSTALL_TYPE', 'agent')
1526
+ puppet_install_type: ENV['OPENVOX_INSTALL_TYPE'] || ENV.fetch('PUPPET_INSTALL_TYPE', 'agent')
1457
1527
  }
1458
1528
  end
1459
1529
 
1460
1530
  def run_puppet_install_helper_on(hosts)
1461
1531
  block_on hosts, run_in_parallel: true do |host|
1462
- puppet_collection = ENV.fetch('BEAKER_PUPPET_COLLECTION', nil) || host.options['puppet_collection']
1532
+ puppet_collection = ENV['BEAKER_OPENVOX_COLLECTION'] || ENV.fetch('BEAKER_PUPPET_COLLECTION', nil) || host.options['puppet_collection']
1463
1533
  if is_windows?(host)
1464
1534
  install_msi_on(host, puppet_collection)
1465
1535
  else
1466
1536
  BeakerPuppetHelpers::InstallUtils.install_puppet_release_repo_on(host, puppet_collection)
1467
- package_name = ENV.fetch('BEAKER_PUPPET_PACKAGE_NAME', BeakerPuppetHelpers::InstallUtils.collection2packagename(host, puppet_collection))
1537
+ package_name = ENV['BEAKER_OPENVOX_PACKAGE_NAME'] || ENV.fetch('BEAKER_PUPPET_PACKAGE_NAME', BeakerPuppetHelpers::InstallUtils.collection2packagename(host, puppet_collection))
1468
1538
  host.install_package(package_name)
1469
1539
  end
1470
1540
  end
@@ -4,7 +4,7 @@ require 'rake/tasklib'
4
4
  require 'fileutils'
5
5
  require 'beaker/tasks/rake_task'
6
6
  require 'beaker-rspec/rake_task'
7
- require 'puppetlabs_spec_helper/tasks/fixtures'
7
+ require 'puppet_fixtures/tasks'
8
8
 
9
9
  # Simp namespace
10
10
  module Simp; end
@@ -111,7 +111,7 @@ class Simp::Rake::Beaker < ::Rake::TaskLib # rubocop:disable Style/OneClassPerFi
111
111
  'default_run' : <true|false> => Default: false
112
112
  ```
113
113
  EOM
114
- task :suites, [:suite, :nodeset] => ['spec_prep'] do |_t, args|
114
+ task :suites, [:suite, :nodeset] => ['fixtures:prep'] do |_t, args|
115
115
  suite = args[:suite]
116
116
  nodeset = args[:nodeset]
117
117
 
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
30
30
  s.add_runtime_dependency 'docker-api', ['>= 2.1.0', '< 3.0.0']
31
31
  s.add_runtime_dependency 'highline', ['>= 2.0', '< 4.0.0']
32
32
  s.add_runtime_dependency 'nokogiri', '~> 1.8'
33
+ s.add_runtime_dependency 'puppet_fixtures', ['>= 0.1', '< 3.0.0']
33
34
 
34
35
  ### s.files = Dir['Rakefile', '{bin,lib,spec}/**/*', 'README*', 'LICENSE*'] & `git ls-files -z .`.split("\0")
35
36
  s.files = %x(git ls-files).split("\n")
@@ -38,7 +38,7 @@ describe 'Inspec STIG Profile' do
38
38
 
39
39
  it 'has a report' do
40
40
  expect(inspec_report_data[:report]).not_to be_nil
41
- puts inspec_report_data[:report]
41
+ puts inspec_report_data[:report] # rubocop:disable RSpec/Output
42
42
  end
43
43
  else
44
44
  # rubocop:disable RSpec/RepeatedDescription
@@ -4,6 +4,7 @@ require 'simp/beaker_helpers'
4
4
  # redefine methods used in RSpec.configure withing Simp::BeakerHelpers
5
5
  def hosts; end
6
6
  def activate_interfaces(hosts); end
7
+ def ensure_beaker_ip_on(hosts); end
7
8
  def clear_temp_hieradata; end
8
9
 
9
10
  class MyTestClass
@@ -131,10 +132,15 @@ describe 'Simp::BeakerHelpers' do
131
132
 
132
133
  context '#get_puppet_install_info' do
133
134
  after(:each) do
135
+ ENV['BEAKER_OPENVOX_AGENT_VERSION'] = nil
134
136
  ENV['BEAKER_PUPPET_AGENT_VERSION'] = nil
137
+ ENV['OPENVOX_INSTALL_VERSION'] = nil
135
138
  ENV['PUPPET_INSTALL_VERSION'] = nil
139
+ ENV['OPENVOX_VERSION'] = nil
136
140
  ENV['PUPPET_VERSION'] = nil
141
+ ENV['BEAKER_OPENVOX_COLLECTION'] = nil
137
142
  ENV['BEAKER_PUPPET_COLLECTION'] = nil
143
+ ENV['OPENVOX_INSTALL_TYPE'] = nil
138
144
  ENV['PUPPET_INSTALL_TYPE'] = nil
139
145
  end
140
146
 
@@ -222,6 +228,46 @@ describe 'Simp::BeakerHelpers' do
222
228
  expect(helper.get_puppet_install_info).to eq expected
223
229
  end
224
230
 
231
+ it 'extracts info from OPENVOX_VERSION' do
232
+ allow(helper).to receive(:`).with('gem search -ra -e puppet').and_return(gem_search_results)
233
+ ENV['OPENVOX_VERSION'] = '5.5.0'
234
+ expected = {
235
+ puppet_install_version: '5.5.0',
236
+ puppet_collection: 'puppet5',
237
+ puppet_install_type: 'agent'
238
+ }
239
+ expect(helper.get_puppet_install_info).to eq expected
240
+ end
241
+
242
+ it 'extracts openvox info from BEAKER_OPENVOX_COLLECTION' do
243
+ allow(helper).to receive(:`).with('gem search -ra -e openvox').and_return(openvox_gem_search_results)
244
+ ENV['BEAKER_OPENVOX_COLLECTION'] = 'openvox8'
245
+ expected = {
246
+ puppet_install_version: '8.19.2',
247
+ puppet_collection: 'openvox8',
248
+ puppet_install_type: 'agent'
249
+ }
250
+ expect(helper.get_puppet_install_info).to eq expected
251
+ end
252
+
253
+ it 'prefers BEAKER_OPENVOX_COLLECTION over BEAKER_PUPPET_COLLECTION' do
254
+ allow(helper).to receive(:`).with('gem search -ra -e openvox').and_return(openvox_gem_search_results)
255
+ ENV['BEAKER_OPENVOX_COLLECTION'] = 'openvox8'
256
+ ENV['BEAKER_PUPPET_COLLECTION'] = 'puppet5'
257
+ expected = {
258
+ puppet_install_version: '8.19.2',
259
+ puppet_collection: 'openvox8',
260
+ puppet_install_type: 'agent'
261
+ }
262
+ expect(helper.get_puppet_install_info).to eq expected
263
+ end
264
+
265
+ it 'extracts info from OPENVOX_INSTALL_TYPE' do
266
+ ENV['OPENVOX_INSTALL_TYPE'] = 'pe'
267
+
268
+ expect(helper.get_puppet_install_info[:puppet_install_type]).to eq('pe')
269
+ end
270
+
225
271
  it 'extracts info from PUPPET_INSTALL_TYPE' do
226
272
  ENV['PUPPET_INSTALL_TYPE'] = 'pe'
227
273
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simp-beaker-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Tessmer
8
8
  - Trevor Vaughan
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-07 00:00:00.000000000 Z
11
+ date: 2026-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: beaker
@@ -164,6 +164,26 @@ dependencies:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
166
  version: '1.8'
167
+ - !ruby/object:Gem::Dependency
168
+ name: puppet_fixtures
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0.1'
174
+ - - "<"
175
+ - !ruby/object:Gem::Version
176
+ version: 3.0.0
177
+ type: :runtime
178
+ prerelease: false
179
+ version_requirements: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0.1'
184
+ - - "<"
185
+ - !ruby/object:Gem::Version
186
+ version: 3.0.0
167
187
  description: 'Beaker helper methods to help scaffold SIMP acceptance tests
168
188
 
169
189
  '
@@ -196,7 +216,6 @@ files:
196
216
  - lib/simp/beaker_helpers/constants.rb
197
217
  - lib/simp/beaker_helpers/inspec.rb
198
218
  - lib/simp/beaker_helpers/snapshot.rb
199
- - lib/simp/beaker_helpers/ssg.rb
200
219
  - lib/simp/beaker_helpers/version.rb
201
220
  - lib/simp/beaker_helpers/windows.rb
202
221
  - lib/simp/rake/beaker.rb
@@ -229,9 +248,6 @@ files:
229
248
  - spec/acceptance/suites/snapshot/00_snapshot_test_spec.rb
230
249
  - spec/acceptance/suites/snapshot/10_general_usage_spec.rb
231
250
  - spec/acceptance/suites/snapshot/nodesets
232
- - spec/acceptance/suites/ssg/00_default_spec.rb
233
- - spec/acceptance/suites/ssg/metadata.yml
234
- - spec/acceptance/suites/ssg/nodesets
235
251
  - spec/acceptance/suites/windows/00_default_spec.rb
236
252
  - spec/acceptance/suites/windows/metadata.yml
237
253
  - spec/acceptance/suites/windows/nodesets/default.yml
@@ -1,479 +0,0 @@
1
- # SIMP Beaker helper methods for testing
2
- module Simp::BeakerHelpers
3
- require 'simp/beaker_helpers/constants'
4
-
5
- # Helpers for working with the SCAP Security Guide
6
- class SSG
7
- if ENV['BEAKER_ssg_repo']
8
- GIT_REPO = ENV['BEAKER_ssg_repo']
9
- else
10
- raise('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'.freeze
13
- end
14
-
15
- # If this is not set, the highest numeric tag 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
- EL7_PACKAGES = [
23
- 'PyYAML',
24
- 'cmake',
25
- 'git',
26
- 'openscap-python',
27
- 'openscap-scanner',
28
- 'openscap-utils',
29
- 'python-jinja2',
30
- 'python-lxml',
31
- 'python-setuptools',
32
- ].freeze
33
-
34
- EL8_PACKAGES = [
35
- 'cmake',
36
- 'git',
37
- 'make',
38
- 'openscap-python3',
39
- 'openscap-utils',
40
- 'openscap-scanner',
41
- 'python3',
42
- 'python3-jinja2',
43
- 'python3-lxml',
44
- 'python3-pyyaml',
45
- 'python3-setuptools',
46
- 'libarchive',
47
- ].freeze
48
-
49
- EL9_PACKAGES = [
50
- 'cmake',
51
- 'git',
52
- 'make',
53
- 'openscap-python3',
54
- 'openscap-utils',
55
- 'openscap-scanner',
56
- 'python3',
57
- 'python3-jinja2',
58
- 'python3-lxml',
59
- 'python3-pyyaml',
60
- 'python3-setuptools',
61
- 'libarchive',
62
- ].freeze
63
-
64
- OS_INFO = {
65
- 'RedHat' => {
66
- '6' => {
67
- 'required_packages' => EL7_PACKAGES,
68
- 'ssg' => {
69
- 'profile_target' => 'rhel6',
70
- 'build_target' => 'rhel6',
71
- 'datastream' => 'ssg-rhel6-ds.xml'
72
- }
73
- },
74
- '7' => {
75
- 'required_packages' => EL7_PACKAGES,
76
- 'ssg' => {
77
- 'profile_target' => 'rhel7',
78
- 'build_target' => 'rhel7',
79
- 'datastream' => 'ssg-rhel7-ds.xml'
80
- }
81
- },
82
- '8' => {
83
- 'required_packages' => EL8_PACKAGES,
84
- 'ssg' => {
85
- 'profile_target' => 'rhel8',
86
- 'build_target' => 'rhel8',
87
- 'datastream' => 'ssg-rhel8-ds.xml'
88
- }
89
- },
90
- '9' => {
91
- 'required_packages' => EL9_PACKAGES,
92
- 'ssg' => {
93
- 'profile_target' => 'rhel9',
94
- 'build_target' => 'rhel9',
95
- 'datastream' => 'ssg-rhel9-ds.xml'
96
- }
97
- }
98
- },
99
- 'CentOS' => {
100
- '6' => {
101
- 'required_packages' => EL7_PACKAGES,
102
- 'ssg' => {
103
- 'profile_target' => 'rhel6',
104
- 'build_target' => 'centos6',
105
- 'datastream' => 'ssg-centos6-ds.xml'
106
- }
107
- },
108
- '7' => {
109
- 'required_packages' => EL7_PACKAGES,
110
- 'ssg' => {
111
- 'profile_target' => 'centos7',
112
- 'build_target' => 'centos7',
113
- 'datastream' => 'ssg-centos7-ds.xml'
114
- }
115
- },
116
- '8' => {
117
- 'required_packages' => EL8_PACKAGES,
118
- 'ssg' => {
119
- 'profile_target' => 'centos8',
120
- 'build_target' => 'centos8',
121
- 'datastream' => 'ssg-centos8-ds.xml'
122
- }
123
- },
124
- '9' => {
125
- 'required_packages' => EL9_PACKAGES,
126
- 'ssg' => {
127
- 'profile_target' => 'cs9',
128
- 'build_target' => 'cs9',
129
- 'datastream' => 'ssg-cs9-ds.xml'
130
- }
131
- }
132
- },
133
- 'Rocky' => {
134
- '8' => {
135
- 'required_packages' => EL8_PACKAGES,
136
- 'ssg' => {
137
- 'profile_target' => 'centos8',
138
- 'build_target' => 'centos8',
139
- 'datastream' => 'ssg-centos8-ds.xml'
140
- }
141
- },
142
- '9' => {
143
- 'required_packages' => EL9_PACKAGES,
144
- 'ssg' => {
145
- 'profile_target' => 'cs9',
146
- 'build_target' => 'cs9',
147
- 'datastream' => 'ssg-cs9-ds.xml'
148
- }
149
- }
150
- },
151
- 'OracleLinux' => {
152
- '7' => {
153
- 'required_packages' => EL7_PACKAGES,
154
- 'ssg' => {
155
- 'profile_target' => 'ol7',
156
- 'build_target' => 'ol7',
157
- 'datastream' => 'ssg-ol7-ds.xml'
158
- },
159
- },
160
- '8' => {
161
- 'required_packages' => EL8_PACKAGES,
162
- 'ssg' => {
163
- 'profile_target' => 'ol8',
164
- 'build_target' => 'ol8',
165
- 'datastream' => 'ssg-ol8-ds.xml'
166
- }
167
- },
168
- '9' => {
169
- 'required_packages' => EL9_PACKAGES,
170
- 'ssg' => {
171
- 'profile_target' => 'ol9',
172
- 'build_target' => 'ol9',
173
- 'datastream' => 'ssg-ol9-ds.xml'
174
- }
175
- }
176
- }
177
- }.freeze
178
-
179
- attr_accessor :scap_working_dir
180
-
181
- # Create a new SSG helper for the specified host
182
- #
183
- # @param sut
184
- # The SUT against which to run
185
- #
186
- def initialize(sut)
187
- @sut = sut
188
-
189
- @os = pfact_on(@sut, 'os.name')
190
- @os_rel = pfact_on(@sut, 'os.release.major')
191
-
192
- sut.mkdir_p('scap_working_dir')
193
-
194
- @scap_working_dir = on(sut, 'cd scap_working_dir && pwd').stdout.strip
195
-
196
- unless OS_INFO[@os]
197
- raise("Error: The '#{@os}' Operating System is not supported")
198
- end
199
-
200
- OS_INFO[@os][@os_rel]['required_packages'].each do |pkg|
201
- install_latest_package_on(@sut, pkg)
202
- end
203
-
204
- @output_dir = File.absolute_path('sec_results/ssg')
205
-
206
- unless File.directory?(@output_dir)
207
- FileUtils.mkdir_p(@output_dir)
208
- end
209
-
210
- @result_file = "#{@sut.hostname}-ssg-#{Time.now.to_i}"
211
-
212
- get_ssg_datastream
213
- end
214
-
215
- def profile_target
216
- OS_INFO[@os][@os_rel]['ssg']['profile_target']
217
- end
218
-
219
- def get_profiles
220
- cmd = "cd #{@scap_working_dir}; oscap info --profiles"
221
- on(@sut, "#{cmd} #{OS_INFO[@os][@os_rel]['ssg']['datastream']}")
222
- .stdout
223
- .strip
224
- .lines
225
- .map { |x| x.split(':').first }
226
- end
227
-
228
- def remediate(profile)
229
- evaluate(profile, true)
230
- end
231
-
232
- def evaluate(profile, remediate = false)
233
- cmd = "cd #{@scap_working_dir}; oscap xccdf eval"
234
-
235
- if remediate
236
- cmd += ' --remediate'
237
- end
238
-
239
- cmd += %( --profile #{profile} --results #{@result_file}.xml --report #{@result_file}.html #{OS_INFO[@os][@os_rel]['ssg']['datastream']})
240
-
241
- # We accept all exit codes here because there have occasionally been
242
- # failures in the SSG content and we're not testing that.
243
-
244
- on(@sut, cmd, accept_all_exit_codes: true)
245
-
246
- ['xml', 'html'].each do |ext|
247
- path = "#{@scap_working_dir}/#{@result_file}.#{ext}"
248
- scp_from(@sut, path, @output_dir)
249
-
250
- raise("Could not retrieve #{path} from #{@sut}") unless File.exist?(File.join(@output_dir, "#{@result_file}.#{ext}"))
251
- end
252
- end
253
-
254
- # Output the report
255
- #
256
- # @param report
257
- # The results Hash
258
- #
259
- def write_report(report)
260
- File.open(File.join(@output_dir, @result_file) + '.report', 'w') do |fh|
261
- fh.puts(report[:report].uncolor)
262
- end
263
- end
264
-
265
- # Retrieve a subset of test results based on a match to filter
266
- #
267
- # @param filter [String, Array[String]]
268
- # A 'short name' filter that will be matched against the rule ID name
269
- #
270
- # @param exclusions [String, Array[String]]
271
- # A 'short name' filter of items that will be removed from the `filter`
272
- # matches
273
- #
274
- # @return [Hash] A Hash of statistics and a formatted report
275
- #
276
- # FIXME:
277
- # - This is a hack! Should be searching for rules based on a set
278
- # set of STIG ids, but don't see those ids in the oscap results xml.
279
- # Further mapping is required...
280
- # - Create the same report structure as inspec
281
- def process_ssg_results(filter = nil, exclusions = nil)
282
- self.class.process_ssg_results(
283
- File.join(@output_dir, @result_file) + '.xml',
284
- filter,
285
- exclusions,
286
- )
287
- end
288
-
289
- # Process the results of an SSG run
290
- #
291
- # @param result_file [String]
292
- # The oscap result XML file to process
293
- #
294
- # @param filter [String, Array[String]]
295
- # A 'short name' filter that will be matched against the rule ID name
296
- #
297
- # @param exclusions [String, Array[String]]
298
- # A 'short name' filter of items that will be removed from the `filter`
299
- # matches
300
- #
301
- # @return [Hash] A Hash of statistics and a formatted report
302
- #
303
- def self.process_ssg_results(result_file, filter = nil, exclusions = nil)
304
- require 'highline'
305
- require 'nokogiri'
306
-
307
- HighLine.colorize_strings
308
-
309
- raise("Could not find results XML file '#{result_file}'") unless File.exist?(result_file)
310
-
311
- puts "Processing #{result_file}"
312
- doc = Nokogiri::XML(File.open(result_file))
313
-
314
- # because I'm lazy
315
- doc.remove_namespaces!
316
-
317
- if filter
318
- filter = Array(filter)
319
-
320
- xpath_query = [
321
- '//rule-result[(',
322
- ]
323
-
324
- xpath_query << filter.map { |flt|
325
- "contains(@idref,'#{flt}')"
326
- }.join(' or ')
327
-
328
- xpath_query << ')' if filter.size > 1
329
-
330
- exclusions = Array(exclusions)
331
- unless exclusions.empty?
332
- xpath_query << 'and not('
333
-
334
- xpath_query << exclusions.map { |exl|
335
- "contains(@idref,'#{exl}')"
336
- }.join(' or ')
337
-
338
- xpath_query << ')' unless exclusions.empty?
339
- end
340
-
341
- xpath_query << ')]'
342
-
343
- xpath_query = xpath_query.join(' ')
344
-
345
- # XPATH to get the pertinent test results:
346
- # Any node named 'rule-result' for which the attribute 'idref'
347
- # contains any of the `filter` Strings and does not contain any of the
348
- # `exclusions` Strings
349
- result_nodes = doc.xpath(xpath_query)
350
- else
351
- result_nodes = doc.xpath('//rule-result')
352
- end
353
-
354
- stats = {
355
- failed: [],
356
- passed: [],
357
- skipped: [],
358
- filter: filter.nil? ? 'No Filter' : filter,
359
- report: nil,
360
- score: 0
361
- }
362
-
363
- result_nodes.each do |rule_result|
364
- # Results are recorded in a child node named 'result'.
365
- # Within the 'result' node, the actual result string is
366
- # the content of that node's (only) child node.
367
-
368
- result = rule_result.element_children.at('result')
369
- result_id = rule_result.attributes['idref'].value.to_s
370
- result_value = [
371
- 'Title: ' + doc.xpath("//Rule[@id='#{result_id}']/title/text()").first.to_s,
372
- ' ID: ' + result_id,
373
- ]
374
-
375
- if result.child.content == 'fail'
376
- references = {}
377
-
378
- doc.xpath("//Rule[@id='#{result_id}']/reference").each do |ref|
379
- references[ref['href']] ||= []
380
- references[ref['href']] << ref.text
381
- end
382
-
383
- result_value << ' References:'
384
- references.each_pair do |src, items|
385
- result_value << " * #{src}"
386
- result_value << " * #{items.join(', ')}"
387
- end
388
- result_value << ' Description: ' + doc.xpath("//Rule[@id='#{result_id}']/description").text.gsub("\n", "\n ")
389
- end
390
-
391
- result_value = result_value.join("\n")
392
-
393
- if result.child.content == 'fail'
394
- stats[:failed] << result_value.red
395
- elsif result.child.content == 'pass'
396
- stats[:passed] << result_value.green
397
- else
398
- stats[:skipped] << result_value.yellow
399
- end
400
- end
401
-
402
- report = []
403
-
404
- report << '== Skipped =='
405
- report << stats[:skipped].join("\n")
406
-
407
- report << '== Passed =='
408
- report << stats[:passed].join("\n")
409
-
410
- report << '== Failed =='
411
- report << stats[:failed].join("\n")
412
-
413
- report << 'OSCAP Statistics:'
414
-
415
- if filter
416
- report << " * Used Filter: 'idref' ~= '#{stats[:filter]}'"
417
- end
418
-
419
- report << " * Passed: #{stats[:passed].count.to_s.green}"
420
- report << " * Failed: #{stats[:failed].count.to_s.red}"
421
- report << " * Skipped: #{stats[:skipped].count.to_s.yellow}"
422
-
423
- score = 0
424
-
425
- if (stats[:passed].count + stats[:failed].count) > 0
426
- score = ((stats[:passed].count.to_f / (stats[:passed].count + stats[:failed].count)) * 100.0).round(0)
427
- end
428
-
429
- report << "\n Score: #{score}%"
430
-
431
- stats[:score] = score
432
- stats[:report] = report.join("\n")
433
-
434
- stats
435
- end
436
-
437
- private
438
-
439
- def get_ssg_datastream
440
- # Allow users to point at a specific SSG release 'tar.bz2' file
441
- ssg_release = ENV['BEAKER_ssg_release']
442
-
443
- # Grab the latest SSG release in fixtures if it exists
444
- ssg_release ||= Dir.glob('spec/fixtures/ssg_releases/*.bz2').last
445
-
446
- if ssg_release
447
- copy_to(@sut, ssg_release, @scap_working_dir)
448
-
449
- 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}))
450
- else
451
- on(@sut, %(git clone #{GIT_REPO} scap-content))
452
- if GIT_BRANCH
453
- on(@sut, %(cd scap-content; git checkout #{GIT_BRANCH}))
454
- else
455
- tags = on(@sut, %(cd scap-content; git tag -l)).output
456
- target_tag = tags.lines.map(&:strip)
457
- .select { |x| x.match?(%r{^v(\d+\.)+\d+$}) }
458
- .sort.last
459
-
460
- on(@sut, %(cd scap-content; git checkout #{target_tag}))
461
- end
462
-
463
- # Work around the issue where the profiles now strip out derivative
464
- # content that isn't explicitlly approved for that OS. This means that
465
- # we are unable to test CentOS builds against the STIG, etc...
466
- #
467
- # This isn't 100% correct but it's "good enough" for an automated CI
468
- # environment to tell us if something is critically out of alignment.
469
- safe_sed(
470
- @sut,
471
- 's/ssg.build_derivatives.profile_handling/__simp_dontcare__ = None #ssg.build_derivatives.profile_handling/g',
472
- 'scap-content/build-scripts/enable_derivatives.py',
473
- )
474
-
475
- on(@sut, %(cd scap-content/build; cmake ../; make -j4 #{OS_INFO[@os][@os_rel]['ssg']['build_target']}-content && cp *ds.xml #{@scap_working_dir}))
476
- end
477
- end
478
- end
479
- end
@@ -1,49 +0,0 @@
1
- require 'spec_helper_acceptance'
2
-
3
- test_name 'SSG Functionality Validation'
4
-
5
- describe 'run the SSG against an SCAP profile' do
6
- hosts.each do |host|
7
- context "on #{host}" do
8
- ssg = nil
9
- ssg_report = nil
10
-
11
- before(:all) do
12
- ssg = Simp::BeakerHelpers::SSG.new(host)
13
-
14
- # If we don't do this, the variable gets reset
15
- ssg_report = { data: nil }
16
- end
17
-
18
- it 'runs the SSG' do
19
- profiles = ssg.get_profiles
20
-
21
- profile = profiles.find { |x| x.include?('_stig') } ||
22
- profiles.find { |x| x.include?('_cui') } ||
23
- profiles.find { |x| x.include?('_ospp') } ||
24
- profiles.find { |x| x.include?('_standard') } ||
25
- profiles.last
26
-
27
- expect(profile).not_to be_nil
28
- ssg.evaluate(profile)
29
- end
30
-
31
- it 'has an SSG report' do
32
- # Validate that the filter works
33
- filter = '_rule_audit'
34
- host_exclusions = ['ssh_']
35
-
36
- ssg_report[:data] = ssg.process_ssg_results(filter, host_exclusions)
37
-
38
- expect(ssg_report[:data]).not_to be_nil
39
-
40
- ssg.write_report(ssg_report[:data])
41
- end
42
-
43
- it 'has a report' do
44
- expect(ssg_report[:data][:report]).not_to be_nil
45
- puts ssg_report[:data][:report]
46
- end
47
- end
48
- end
49
- end
@@ -1,2 +0,0 @@
1
- ---
2
- 'default_run': true
@@ -1 +0,0 @@
1
- ../../nodesets