Module: Simp::BeakerHelpers

Includes:
BeakerPuppet
Defined in:
lib/simp/beaker_helpers.rb,
lib/simp/beaker_helpers/ssg.rb,
lib/simp/beaker_helpers/inspec.rb,
lib/simp/beaker_helpers/version.rb,
lib/simp/beaker_helpers/windows.rb,
lib/simp/beaker_helpers/snapshot.rb,
lib/simp/beaker_helpers/constants.rb

Defined Under Namespace

Modules: Windows Classes: Inspec, SSG, Snapshot

Constant Summary collapse

VERSION =
'1.34.0'
DEFAULT_PUPPET_AGENT_VERSION =

This is the oldest puppet-agent version that the latest release of SIMP supports

This is done so that we know if some new thing that we’re using breaks the oldest system that we support

'~> 8.0'
SSG_REPO_URL =
ENV['BEAKER_ssg_repo'] || 'https://github.com/ComplianceAsCode/content.git'
ONLINE =
false

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.tmpnameObject

Stealing this from the Ruby 2.5 Dir::Tmpname workaround from Rails



19
20
21
22
# File 'lib/simp/beaker_helpers.rb', line 19

def self.tmpname
  t = Time.new.strftime("%Y%m%d")
  "simp-beaker-helpers-#{t}-#{$$}-#{rand(0x100000000).to_s(36)}.tmp"
end

Instance Method Details

#activate_interfaces(hosts) ⇒ Object

Activate all network interfaces on the target system

This is generally needed if the upstream vendor does not activate all interfaces by default (EL7 for example)

Can be passed any number of hosts either singly or as an Array



1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
# File 'lib/simp/beaker_helpers.rb', line 1150

def activate_interfaces(hosts)
  return if ENV['BEAKER_no_fix_interfaces']

  block_on(hosts, :run_in_parallel => @run_in_parallel) do |host|
    if host[:platform] =~ /windows/
      puts "  -- SKIPPING #{host} because it is windows"
      next
    end

    networking_fact = pfact_on(host, 'networking')
    if networking_fact && networking_fact['interfaces']
      networking_fact['interfaces'].each do |iface, data|
        next if ( ( data['ip'] && !data['ip'].empty? ) || ( data['ip6'] && !data['ip6'].empty? ) )
        on(host, "ifup #{iface}", :accept_all_exit_codes => true)
      end
    else
      interfaces_fact = pfact_on(host, 'interfaces')

      interfaces = interfaces_fact.strip.split(',')
      interfaces.delete_if { |x| x =~ /^lo/ }

      interfaces.each do |iface|
        if pfact_on(host, "ipaddress_#{iface}")
          on(host, "ifup #{iface}", :accept_all_exit_codes => true)
        end
      end
    end
  end
end

#clear_temp_hieradataObject

Clean up all temporary hiera data files.

Meant to be called from after(:all)



1372
1373
1374
1375
1376
1377
1378
1379
1380
# File 'lib/simp/beaker_helpers.rb', line 1372

def clear_temp_hieradata
  if @temp_hieradata_dirs && !@temp_hieradata_dirs.empty?
    @temp_hieradata_dirs.each do |data_dir|
      if File.exist?(data_dir)
        FileUtils.rm_r(data_dir)
      end
    end
  end
end

#copy_fixture_modules_to(suts = hosts, opts = {}) ⇒ Object

Copy the local fixture modules (under ‘spec/fixtures/modules`) onto each SUT



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/simp/beaker_helpers.rb', line 377

def copy_fixture_modules_to( suts = hosts, opts = {})
  ensure_fixture_modules

  opts[:pluginsync] = opts.fetch(:pluginsync, true)

  unless ENV['BEAKER_copy_fixtures'] == 'no'
    block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
      STDERR.puts "  ** copy_fixture_modules_to: '#{sut}'" if ENV['BEAKER_helpers_verbose']

      # Use spec_prep to provide modules (this supports isolated networks)
      unless ENV['BEAKER_use_fixtures_dir_for_modules'] == 'no'

        # NOTE: As a result of BKR-723, which does not look easy to fix, we
        # cannot rely on `copy_module_to()` to choose a sane default for
        # `target_module_path`.  This workaround queries each SUT's
        # `modulepath` and targets the first one.
        target_module_path = puppet_modulepath_on(sut).first

        mod_root = File.expand_path( "spec/fixtures/modules", File.dirname( fixtures_yml_path ))

        Dir.chdir(mod_root) do
          # Have to do things the slow way on Windows
          if is_windows?(sut)
            begin
              zipfile = "#{Simp::BeakerHelpers.tmpname}.zip"
              files = []

              # 'zip -x' does not reliably exclude paths, so we need to remove them from
              #   the list of files to zip
              Dir.glob('*') do |module_root|
                next unless Dir.exist?(module_root)
                Find.find("#{module_root}/") do |path|
                  if PUPPET_MODULE_INSTALL_IGNORE.any? { |ignore| path.include?(ignore) }
                    Find.prune
                    next
                  end

                  files << path
                end
              end

              command = ['zip', zipfile] + files
              Kernel.system(*command)

              raise("Error: module zip file '#{zipfile}' could not be created at #{mod_root}") unless File.exist?(zipfile)
              copy_to(sut, zipfile, target_module_path, opts)

              # Windows 2012 and R2 does not natively include PowerShell 5, in which
              #  the Expand-Archive cmdlet was introduced
              if fact_on(sut, 'os.release.major').include?('2012')
                unzip_cmd = [
                  "\"[System.Reflection.Assembly]::LoadWithPartialName(\'System.IO.Compression.FileSystem\')",
                  "[System.IO.Compression.ZipFile]::OpenRead(\'#{target_module_path}\\#{File.basename(zipfile)}\').Entries.FullName \| %{Remove-Item -Path (\"\"\"#{target_module_path}\\$_\"\"\") -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue}", # rubocop:disable Layout/LineLength
                  "[System.IO.Compression.ZipFile]::ExtractToDirectory(\'#{target_module_path}\\#{File.basename(zipfile)}\', \'#{target_module_path}\')\"",
                ].join(';')
              else
                unzip_cmd = "$ProgressPreference='SilentlyContinue';Expand-Archive -Path #{target_module_path}\\#{File.basename(zipfile)} -DestinationPath #{target_module_path} -Force"
              end
              on(sut, powershell(unzip_cmd))
            ensure
              FileUtils.remove_entry(zipfile, true)
            end
          else
            begin
              tarfile = "#{Simp::BeakerHelpers.tmpname}.tar"

              excludes = (PUPPET_MODULE_INSTALL_IGNORE + ['spec']).map do |x|
                x = "--exclude '*/#{x}'"
              end.join(' ')

              %x(tar -ch #{excludes} -f #{tarfile} *)

              if File.exist?(tarfile)
                copy_to(sut, tarfile, target_module_path, opts)
              else
                fail("Error: module tar file '#{tarfile}' could not be created at #{mod_root}")
              end

              on(sut, "cd #{target_module_path} && tar -xf #{File.basename(tarfile)}")
            ensure
              FileUtils.remove_entry(tarfile, true)
            end
          end
        end
      end
    end
  end
  STDERR.puts '  ** copy_fixture_modules_to: finished' if ENV['BEAKER_helpers_verbose']

  # sync custom facts from the new modules to each SUT's factpath
  pluginsync_on(suts) if opts[:pluginsync]
end

#copy_hiera_data_to(sut, path) ⇒ Object

A shim to stand in for the now deprecated copy_hiera_data_to function

Parameters:

  • sut (Host)

    One host to act upon

  • File (Path)

    containing hiera data



1292
1293
1294
# File 'lib/simp/beaker_helpers.rb', line 1292

def copy_hiera_data_to(sut, path)
  copy_to(sut, path, hiera_datadir(sut))
end

#copy_keydist_to(ca_sut = master, host_keydist_dir = nil) ⇒ Object

Copy a CA keydist/ directory of CA+host certs into an SUT

This simulates the output of FakeCA’s gencerts_nopass.sh to keydist/



1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
# File 'lib/simp/beaker_helpers.rb', line 1132

def copy_keydist_to( ca_sut = master, host_keydist_dir = nil  )
  if !host_keydist_dir
    modulepath = puppet_modulepath_on(ca_sut)

    host_keydist_dir = "#{modulepath.first}/pki/files/keydist"
  end
  on ca_sut, "rm -rf #{host_keydist_dir}/*"
  ca_sut.mkdir_p(host_keydist_dir)
  on ca_sut, "cp -pR /root/pki/keydist/. #{host_keydist_dir}/"
  on ca_sut, "chgrp -R puppet #{host_keydist_dir}"
end

#copy_pki_to(sut, local_pki_dir, sut_base_dir = '/etc/pki/simp-testing') ⇒ Object

Copy a single SUT’s PKI certs (with cacerts) onto an SUT.

This simulates the result of pki::copy

The directory structure is:

SUT_BASE_DIR/

pki/
    cacerts/cacerts.pem
    # This is a copy of cacerts.pem since cacerts.pem is a
    # collection of the CA certificates in pupmod-simp-pki
    cacerts/simp_auto_ca.pem
    public/fdqn.pub
    private/fdqn.pem


1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
# File 'lib/simp/beaker_helpers.rb', line 1092

def copy_pki_to(sut, local_pki_dir, sut_base_dir = '/etc/pki/simp-testing')
    fqdn                = fact_on(sut, 'networking.fqdn')
    sut_pki_dir         = File.join( sut_base_dir, 'pki' )
    local_host_pki_tree = File.join(local_pki_dir,'pki','keydist',fqdn)
    local_cacert = File.join(local_pki_dir,'pki','demoCA','cacert.pem')

    sut.mkdir_p("#{sut_pki_dir}/public")
    sut.mkdir_p("#{sut_pki_dir}/private")
    sut.mkdir_p("#{sut_pki_dir}/cacerts")
    copy_to(sut, "#{local_host_pki_tree}/#{fqdn}.pem", "#{sut_pki_dir}/private/")
    copy_to(sut, "#{local_host_pki_tree}/#{fqdn}.pub", "#{sut_pki_dir}/public/")

    copy_to(sut, local_cacert, "#{sut_pki_dir}/cacerts/simp_auto_ca.pem")

    # NOTE: to match pki::copy, 'cacert.pem' is copied to 'cacerts.pem'
    copy_to(sut, local_cacert, "#{sut_pki_dir}/cacerts/cacerts.pem")

    # Need to hash all of the CA certificates so that apps can use them
    # properly! This must happen on the host itself since it needs to match
    # the native hashing algorithms.
    hash_cmd = <<~EOM.strip
      PATH=/opt/puppetlabs/puppet/bin:$PATH; \
      cd #{sut_pki_dir}/cacerts; \
      for x in *; do \
        if [ ! -h "$x" ]; then \
          `openssl x509 -in $x >/dev/null 2>&1`; \
          if [ $? -eq 0 ]; then \
            hash=`openssl x509 -in $x -hash | head -1`; \
            ln -sf $x $hash.0; \
          fi; \
         fi; \
      done
      EOM

    on(sut, hash_cmd)
end

#copy_to(sut, src, dest, opts = {}) ⇒ Object

Figure out the best method to copy files to a host and use it

Will create the directories leading up to the target if they don’t exist



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/simp/beaker_helpers.rb', line 152

def copy_to(sut, src, dest, opts={})
  sut.mkdir_p(File.dirname(dest))

  if sut[:hypervisor] == 'docker'
    exclude_list = []
    opts[:silent] ||= true

    if opts.has_key?(:ignore) && !opts[:ignore].empty?
      opts[:ignore].each do |value|
        exclude_list << "--exclude '#{value}'"
      end
    end

    # Work around for breaking changes in beaker-docker
    if sut.host_hash[:docker_container]
      container_id = sut.host_hash[:docker_container].id
    else
      container_id = sut.host_hash[:docker_container_id]
    end

    if ENV['BEAKER_docker_cmd']
      docker_cmd = ENV['BEAKER_docker_cmd']
    else
      docker_cmd = 'docker'

      if ::Docker.version['Components'].any?{|x| x['Name'] =~ /podman/i}
        docker_cmd = 'podman'

        if ENV['CONTAINER_HOST']
          docker_cmd = 'podman --remote'
        elsif ENV['DOCKER_HOST']
          docker_cmd = "podman --remote --url=#{ENV['DOCKER_HOST']}"
        end
      end
    end

    sut.mkdir_p(File.dirname(dest)) unless directory_exists_on(sut, dest)

    if File.file?(src)
      cmd = %{#{docker_cmd} cp "#{src}" "#{container_id}:#{dest}"}
    else
      cmd = [
        %{tar #{exclude_list.join(' ')} -hcf - -C "#{File.dirname(src)}" "#{File.basename(src)}"},
        %{#{docker_cmd} exec -i "#{container_id}" tar -C "#{dest}" -xf -}
      ].join(' | ')
    end

    %x(#{cmd})
  elsif rsync_functional_on?(sut)
    # This makes rsync_to work like beaker and scp usually do
    exclude_hack = %(__-__' -L --exclude '__-__)

    # There appears to be a single copy of 'opts' that gets passed around
    # through all of the different hosts so we're going to make a local deep
    # copy so that we don't destroy the world accidentally.
    _opts = Marshal.load(Marshal.dump(opts))
    _opts[:ignore] ||= []
    _opts[:ignore] << exclude_hack

    if File.directory?(src)
      dest = File.join(dest, File.basename(src)) if File.directory?(src)
      sut.mkdir_p(dest)
    end

    # End rsync hackery

    begin
      rsync_to(sut, src, dest, _opts)
    rescue
      # Depending on what is getting tested, a new SSH session might not
      # work. In this case, we fall back to SSH.
      #
      # The rsync failure is quite fast so this doesn't affect performance as
      # much as shoving a bunch of data over the ssh session.
      scp_to(sut, src, dest, opts)
    end
  else
    scp_to(sut, src, dest, opts)
  end
end

#create_yum_resource(repo, metadata) ⇒ Object



594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
# File 'lib/simp/beaker_helpers.rb', line 594

def create_yum_resource( repo,  )
  repo_attrs = [
    :assumeyes,
    :bandwidth,
    :cost,
    :deltarpm_metadata_percentage,
    :deltarpm_percentage,
    :descr,
    :enabled,
    :enablegroups,
    :exclude,
    :failovermethod,
    :gpgcakey,
    :gpgcheck,
    :http_caching,
    :include,
    :includepkgs,
    :keepalive,
    :metadata_expire,
    :metalink,
    :mirrorlist,
    :mirrorlist_expire,
    :priority,
    :protect,
    :provider,
    :proxy,
    :proxy_password,
    :proxy_username,
    :repo_gpgcheck,
    :retries,
    :s3_enabled,
    :skip_if_unavailable,
    :sslcacert,
    :sslclientcert,
    :sslclientkey,
    :sslverify,
    :target,
    :throttle,
    :timeout
  ]

    repo_manifest = %(yumrepo { #{repo}:)

    repo_manifest_opts = []

    # Legacy Support
    urls = ![:url].nil? ? [:url] : [:baseurl]
    if urls
      repo_manifest_opts << 'baseurl => ' + '"' + Array(urls).flatten.join('\n        ').gsub('$','\$') + '"'
    end

    # Legacy Support
    gpgkeys = ![:gpgkeys].nil? ? [:gpgkeys] : [:gpgkey]
    if gpgkeys
      repo_manifest_opts << 'gpgkey => ' + '"' + Array(gpgkeys).flatten.join('\n       ').gsub('$','\$') + '"'
    end

    repo_attrs.each do |attr|
      if [attr]
        repo_manifest_opts << "#{attr} => '#{[attr]}'"
      end
    end

    repo_manifest = repo_manifest + %(\n#{repo_manifest_opts.join(",\n")}) + "\n}\n"
end

#enable_epel_on(suts) ⇒ Object

Enable EPEL if appropriate to do so and the system is online

Can be disabled by setting BEAKER_enable_epel=no



663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
# File 'lib/simp/beaker_helpers.rb', line 663

def enable_epel_on(suts)
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    if ONLINE
      os_info = fact_on(sut, 'os')
      os_maj_rel = os_info['release']['major']

      # This is based on the official EPEL docs https://fedoraproject.org/wiki/EPEL
      case os_info['name']
      when 'RedHat','CentOS','AlmaLinux','Rocky'
        install_latest_package_on(
          sut,
          'epel-release',
          "https://dl.fedoraproject.org/pub/epel/epel-release-latest-#{os_maj_rel}.noarch.rpm",
        )

        if os_info['name'] == 'RedHat' && ENV['BEAKER_RHSM_USER'] && ENV['BEAKER_RHSM_PASS']
          if os_maj_rel == '7'
            on sut, %{subscription-manager repos --enable "rhel-*-extras-rpms"}
            on sut, %{subscription-manager repos --enable "rhel-ha-for-rhel-*-server-rpms"}
          end

          if os_maj_rel == '8'
            on sut, %{subscription-manager repos --enable "codeready-builder-for-rhel-8-#{os_info['architecture']}-rpms"}
          end
        end

        if ['CentOS','AlmaLinux','Rocky'].include?(os_info['name'])
          if os_maj_rel == '8'
            # 8.0 fallback
            install_latest_package_on(sut, 'dnf-plugins-core')
            on sut, %{dnf config-manager --set-enabled powertools || dnf config-manager --set-enabled PowerTools}
          end
        end
      when 'OracleLinux'
        package_name = "oracle-epel-release-el#{os_maj_rel}"
        install_latest_package_on(sut,package_name)
      when 'Amazon'
        on sut, %{amazon-linux-extras install epel -y}
      end
    end
  end
end

#enable_fips_mode_on(suts = hosts) ⇒ Object

Configure and reboot SUTs into FIPS mode



506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
# File 'lib/simp/beaker_helpers.rb', line 506

def enable_fips_mode_on( suts = hosts )
  puts '== configuring FIPS mode on SUTs'
  puts '  -- (use BEAKER_fips=no to disable)'

  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    next if sut[:hypervisor] == 'docker'

    if is_windows?(sut)
      puts "  -- SKIPPING #{sut} because it is windows"
      next
    end

    puts "  -- enabling FIPS on '#{sut}'"

    # We need to use FIPS compliant algorithms and keylengths as per the FIPS
    # certification.
    on(sut, 'puppet config set digest_algorithm sha256')
    on(sut, 'puppet config set keylength 2048')

    # We need to be able to get back into our system!
    # Make these safe for all systems, even old ones.
    # TODO Use simp-ssh Puppet module appropriately (i.e., in a fashion
    #      that doesn't break vagrant access and is appropriate for
    #      typical module tests.)
    fips_ssh_ciphers = [ 'aes256-ctr','aes192-ctr','aes128-ctr']
    safe_sed(sut, '/Ciphers /d', '/etc/ssh/sshd_config')
    on(sut, %(echo 'Ciphers #{fips_ssh_ciphers.join(',')}' >> /etc/ssh/sshd_config))

    fips_enable_modulepath = ''

    if pupmods_in_fixtures_yml.include?('fips')
      copy_fixture_modules_to(sut)
    else
      # If we don't already have the simp-fips module installed
      #
      # Use the simp-fips Puppet module to set FIPS up properly:
      # Download the appropriate version of the module and its dependencies from PuppetForge.
      # TODO provide a R10k download option in which user provides a Puppetfile
      # with simp-fips and its dependencies
      on(sut, 'mkdir -p /root/.beaker_fips/modules')

      fips_enable_modulepath = '--modulepath=/root/.beaker_fips/modules'

      modules_to_install = {
        'simp-fips' => ENV['BEAKER_fips_module_version'],
        'simp-crypto_policy' => nil
      }

      modules_to_install.each_pair do |to_install, version|
        module_install_cmd = "puppet module install #{to_install} --target-dir=/root/.beaker_fips/modules"
        module_install_cmd += " --version #{version}" if version
        on(sut, module_install_cmd)
      end
    end

    # Work around Vagrant and cipher restrictions in EL8+
    #
    # Hopefully, Vagrant will update the used ciphers at some point but who
    # knows when that will be
    munge_ssh_crypto_policies(sut)

    # Enable FIPS and then reboot to finish.
    on(sut, %(puppet apply --verbose #{fips_enable_modulepath} -e "class { 'fips': enabled => true }"))

    sut.reboot
  end
end

#enable_yum_repos_on(suts = hosts) ⇒ Object

Collect all ‘yum_repos’ entries from the host nodeset. The acceptable format is as follows: yum_repos:

<repo_name>:
  url: <URL>
  gpgkeys:
    - <URL to GPGKEY1>
    - <URL to GPGKEY2>


582
583
584
585
586
587
588
589
590
591
592
# File 'lib/simp/beaker_helpers.rb', line 582

def enable_yum_repos_on( suts = hosts )
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    if sut['yum_repos']
      sut['yum_repos'].each_pair do |repo, |
        repo_manifest = create_yum_resource(repo, )

        apply_manifest_on(sut, repo_manifest, :catch_failures => true)
      end
    end
  end
end

#ensure_fixture_modulesObject

Ensures that the fixture modules (under ‘spec/fixtures/modules`) exists. if any fixture modules are missing, run ’rake spec_prep’ to populate the fixtures/modules



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/simp/beaker_helpers.rb', line 352

def ensure_fixture_modules
  STDERR.puts "  ** ensure_fixture_modules" if ENV['BEAKER_helpers_verbose']
  unless ENV['BEAKER_spec_prep'] == 'no'
    puts "== checking prepped modules from .fixtures.yml"
    puts "  -- (use BEAKER_spec_prep=no to disable)"
    missing_modules = []
    pupmods_in_fixtures_yml.each do |pupmod|
      STDERR.puts "  **  -- ensure_fixture_modules: '#{pupmod}'" if ENV['BEAKER_helpers_verbose']
      mod_root = File.expand_path( "spec/fixtures/modules/#{pupmod}", File.dirname( fixtures_yml_path ))
      missing_modules << pupmod unless File.directory? mod_root
    end
    puts "  -- #{missing_modules.size} modules need to be prepped"
    unless missing_modules.empty?
      cmd = 'bundle exec rake spec_prep'
      puts "  -- running spec_prep: '#{cmd}'"
      %x(#{cmd})
    else
      puts "  == all fixture modules present"
    end
  end
  STDERR.puts "  **  -- ensure_fixture_modules: finished" if ENV['BEAKER_helpers_verbose']
end

#file_content_on(sut, path, trim = true) ⇒ String?

Return the contents of a file on the remote host

Parameters:

  • sut (Host)

    the host upon which to operate

  • path (String)

    the path to the target file

  • trim (Boolean) (defaults to: true)

    remove leading and trailing whitespace

Returns:

  • (String, nil)

    the contents of the remote file



1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
# File 'lib/simp/beaker_helpers.rb', line 1208

def file_content_on(sut, path, trim=true)
  file_content = nil

  if file_exists_on(sut, path)
    Dir.mktmpdir do |dir|
      scp_from(sut, path, dir)

      file_content = File.read(File.join(dir,File.basename(path)))
    end
  end

  return file_content
end

#fips_enabled(sut) ⇒ Object

We can’t cache this because it may change during a run



114
115
116
117
118
119
# File 'lib/simp/beaker_helpers.rb', line 114

def fips_enabled(sut)
  return on( sut,
            'cat /proc/sys/crypto/fips_enabled 2>/dev/null',
            :accept_all_exit_codes => true
           ).output.strip == '1'
end

#fix_errata_on(suts = hosts) ⇒ Object

Apply known OS fixes we need to run Beaker on each SUT



942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
# File 'lib/simp/beaker_helpers.rb', line 942

def fix_errata_on( suts = hosts )
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    if is_windows?(sut)
      # Load the Windows requirements
      require 'simp/beaker_helpers/windows'

      # Install the necessary windows certificate for testing
      #
      # https://petersouter.xyz/testing-windows-with-beaker-without-cygwin/
      geotrust_global_ca = <<~EOM.freeze
      -----BEGIN CERTIFICATE-----
      MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
      MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
      YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
      EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
      R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
      9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
      fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
      iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
      1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
      bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
      MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
      ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
      uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
      Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
      tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
      PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
      hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
      5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
      -----END CERTIFICATE-----
      EOM

      install_cert_on_windows(sut, 'geotrustglobal', geotrust_global_ca)
    else
      linux_errata(sut)
    end
  end

  # Configure and reboot SUTs into FIPS mode
  if ENV['BEAKER_fips'] == 'yes'
    enable_fips_mode_on(suts)
  end
end

#fixtures_pathObject

Return the path to the ‘spec/fixtures’ directory



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/simp/beaker_helpers.rb', line 283

def fixtures_path
  return @fixtures_path if @fixtures_path

  STDERR.puts '  ** fixtures_path' if ENV['BEAKER_helpers_verbose']
  dir = RSpec.configuration.default_path
  dir = File.join('.', 'spec') unless dir

  dir = File.join(File.expand_path(dir), 'fixtures')

  if File.directory?(dir)
    @fixtures_path = dir
    return @fixtures_path
  else
    raise("Could not find fixtures directory at '#{dir}'")
  end
end

#fixtures_yml_pathObject

Locates .fixture.yml in or above this directory.



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/simp/beaker_helpers.rb', line 301

def fixtures_yml_path
  return @fixtures_yml_path if @fixtures_yml_path

  STDERR.puts '  ** fixtures_yml_path' if ENV['BEAKER_helpers_verbose']

  if ENV['FIXTURES_YML']
    fixtures_yml = ENV['FIXTURES_YML']
  else
    fixtures_yml = ''
    dir          = '.'
    while( fixtures_yml.empty? && File.expand_path(dir) != '/' ) do
      file = File.expand_path( '.fixtures.yml', dir )
      STDERR.puts "  ** fixtures_yml_path: #{file}" if ENV['BEAKER_helpers_verbose']
      if File.exist? file
        fixtures_yml = file
        break
      end
      dir = "#{dir}/.."
    end
  end

  raise 'ERROR: cannot locate .fixtures.yml!' if fixtures_yml.empty?

  STDERR.puts "  ** fixtures_yml_path:finished (file: '#{file}')" if ENV['BEAKER_helpers_verbose']

  @fixtures_yml_path = fixtures_yml

  return @fixtures_yml_path
end

#get_hiera_config_on(sut) ⇒ Object

Retrieve the default environment hiera.yaml

Parameters:

  • sut (Host)

    one host to act upon



1236
1237
1238
# File 'lib/simp/beaker_helpers.rb', line 1236

def get_hiera_config_on(sut)
  file_content_on(sut, hiera_config_path_on(sut))
end

#get_puppet_install_infoObject

returns hash with :puppet_install_version, :puppet_collection, and :puppet_install_type keys determined from environment variables, host settings, and/or defaults

NOTE: BEAKER_PUPPET_AGENT_VERSION or PUPPET_INSTALL_VERSION or

PUPPET_VERSION takes precedence over BEAKER_PUPPET_COLLECTION
or host.options['puppet_collection'], when both a puppet
install version and a puppet collection are specified. This is
because the puppet install version can specify more precise
version information than is available from a puppet collection.


1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
# File 'lib/simp/beaker_helpers.rb', line 1471

def get_puppet_install_info
  # The first match is internal Beaker and the second is legacy SIMP
  puppet_install_version = ENV['BEAKER_PUPPET_AGENT_VERSION'] || ENV['PUPPET_INSTALL_VERSION'] || ENV['PUPPET_VERSION']

  if puppet_install_version and !puppet_install_version.strip.empty?
    puppet_agent_version = latest_puppet_agent_version_for(puppet_install_version.strip)
  end

  if puppet_agent_version.nil?
    if puppet_collection = (ENV['BEAKER_PUPPET_COLLECTION'] || host.options['puppet_collection'])
      if puppet_collection =~ /puppet(\d+)/
        puppet_install_version = "~> #{$1}"
        puppet_agent_version = latest_puppet_agent_version_for(puppet_install_version)
      else
        raise("Error: Puppet Collection '#{puppet_collection}' must match /puppet(\\d+)/")
      end
    else
      puppet_agent_version = latest_puppet_agent_version_for(DEFAULT_PUPPET_AGENT_VERSION)
    end
  end

  if puppet_collection.nil?
    base_version = puppet_agent_version.to_i
    puppet_collection = "puppet#{base_version}" if base_version >= 5
  end

  {
    :puppet_install_version => puppet_agent_version,
    :puppet_collection      => puppet_collection,
    :puppet_install_type    => ENV.fetch('PUPPET_INSTALL_TYPE', 'agent')
  }
end

#has_crypto_policies(sut) ⇒ Object



470
471
472
# File 'lib/simp/beaker_helpers.rb', line 470

def has_crypto_policies(sut)
  file_exists_on(sut, '/etc/crypto-policies/config')
end

#hiera_config_path_on(sut) ⇒ Object

Retrieve the default hiera.yaml path

Parameters:

  • sut (Host)

    one host to act upon



1227
1228
1229
# File 'lib/simp/beaker_helpers.rb', line 1227

def hiera_config_path_on(sut)
  File.join(puppet_environment_path_on(sut), 'hiera.yaml')
end

#hiera_datadir(sut) ⇒ Object

A shim to stand in for the now deprecated hiera_datadir function

Note: This may not work if you’ve shoved data somewhere that is not the default and/or are manipulating the default hiera.yaml.

Parameters:

  • sut (Host)

    One host to act upon



1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
# File 'lib/simp/beaker_helpers.rb', line 1304

def hiera_datadir(sut)
  # A workaround for PUP-11042
  sut_environment = sut.puppet_configprint['environment']

  # This output lets us know where Hiera is configured to look on the system
  puppet_lookup_info = on(sut, "puppet lookup --explain --environment #{sut_environment} test__simp__test", :silent => true).output.strip.lines

  if sut.puppet_configprint['manifest'].nil? || sut.puppet_configprint['manifest'].empty?
    fail("No output returned from `puppet config print manifest` on #{sut}")
  end

  puppet_env_path = puppet_environment_path_on(sut)

  # We'll just take the first match since Hiera will find things there
  puppet_lookup_info = puppet_lookup_info.grep(/Path "/).grep(Regexp.new(puppet_env_path))

  # Grep always returns an Array
  if puppet_lookup_info.empty?
    fail("Could not determine hiera data directory under #{puppet_env_path} on #{sut}")
  end

  # Snag the actual path without the extra bits
  puppet_lookup_info = puppet_lookup_info.first.strip.split('"').last

  # Make the parent directories exist
  sut.mkdir_p(File.dirname(puppet_lookup_info))

  # We just want the data directory name
  datadir_name = puppet_lookup_info.split(puppet_env_path).last

  # Grab the file separator to add back later
  file_sep = datadir_name[0]

  # Snag the first entry (this is the data directory)
  datadir_name = datadir_name.split(file_sep)[1]

  # Constitute the full path to the data directory
  datadir_path = puppet_env_path + file_sep + datadir_name

  # Return the path to the data directory
  return datadir_path
end

#install_latest_package_on(suts, package_name, package_source = nil, opts = {}) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/simp/beaker_helpers.rb', line 88

def install_latest_package_on(suts, package_name, package_source=nil, opts={})
  default_opts = {
    max_retries: 3,
    retry_interval: 10
  }

  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    package_source = package_name unless package_source

    if sut.check_for_package(package_name)
      sut.upgrade_package(
        package_source,
        '',
        default_opts.merge(opts)
      )
    else
      install_package_unless_present_on(sut, package_name, package_source, opts)
    end
  end
end

#install_package_unless_present_on(suts, package_name, package_source = nil, opts = {}) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/simp/beaker_helpers.rb', line 68

def install_package_unless_present_on(suts, package_name, package_source=nil, opts={})
  default_opts = {
    max_retries: 3,
    retry_interval: 10
  }

  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    package_source = package_name unless package_source

    unless sut.check_for_package(package_name)
      sut.install_package(
        package_source,
        '',
        nil,
        default_opts.merge(opts)
      )
    end
  end
end

#install_puppetObject

Replacement for ‘install_puppet` in spec_helper_acceptance.rb



1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
# File 'lib/simp/beaker_helpers.rb', line 1506

def install_puppet
  install_info = get_puppet_install_info

  # In case  Beaker needs this info internally
  ENV['PUPPET_INSTALL_VERSION'] = install_info[:puppet_install_version]
  if install_info[:puppet_collection]
    ENV['BEAKER_PUPPET_COLLECTION'] = install_info[:puppet_collection]
  end

  require 'beaker-puppet'
  install_puppet_on(hosts, version: install_info[:puppet_install_version])
end

#install_simp_repos(suts, disable = []) ⇒ Object

Configure all SIMP repos on a host and disable all repos in the disable Array

Examples:

install_simp_repos( myhost )           # install all the repos an enable them.
install_simp_repos( myhost, ['simp'])  # install the repos but disable the simp repo.

Valid repo names include any repository available on the system.

For backwards compatibility purposes, the following translations are automatically performed:

* 'simp'
  * 'simp-community-simp'

* 'simp_deps'
  * 'simp-community-epel'
  * 'simp-community-postgres'
  * 'simp-community-puppet'

Environment Variables:

* BEAKER_SIMP_install_repos
  * 'no' => disable the capability
* BEAKER_SIMP_disable_repos
  * Comma delimited list of active yum repo names to disable

Parameters:

  • sut (Beaker::Host)

    Host on which to configure SIMP repos

  • disable (Array[String]) (defaults to: [])

    List of repos to disable

Raises:

  • (StandardError)

    if disable contains an invalid repo name.



1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
# File 'lib/simp/beaker_helpers.rb', line 1548

def install_simp_repos(suts, disable = [])
  # NOTE: Do *NOT* use puppet in this method since it may not be available yet

  return if (ENV.fetch('SIMP_install_repos', 'yes') == 'no')

  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    install_package_unless_present_on(sut, 'yum-utils')

    os = fact_on(sut, 'os.name')
    release = fact_on(sut, 'os.release.major')

    # Work around Amazon 2 compatibility
    if (( os == 'Amazon' ) && ( "#{release}" == '2' ))
      release = '7'
    end

    install_package_unless_present_on(
      sut,
      'simp-release-community',
      "https://download.simp-project.com/simp-release-community.el#{release}.rpm"
    )

    # TODO: Remove this hack-around when there's a version for AL2
    if ( os == 'Amazon' )
      on(sut, %(sed -i 's/$releasever/#{release}/g' /etc/yum.repos.d/simp*))
    end

    to_disable = disable.dup
    to_disable += ENV.fetch('BEAKER_SIMP_disable_repos', '').split(',').map(&:strip)

    unless to_disable.empty?
      if to_disable.include?('simp') || to_disable.include?('simp-community-simp')
        to_disable.delete('simp')

        # legacy community RPM
        to_disable << 'simp-community-simp'

        # SIMP 6.6+ community RPM
        to_disable << 'SIMP--simp'
      end

      if to_disable.include?('simp_deps')
        to_disable.delete('simp_deps')
        # legacy community RPM
        to_disable << 'simp-community-epel'
        to_disable << 'simp-community-postgres'
        to_disable << 'simp-community-puppet'

        # SIMP 6.6+ community RPM
        to_disable << 'epel--simp'
        to_disable << 'postgresql--simp'
        to_disable << 'puppet--simp'
        to_disable << 'puppet7--simp'
        to_disable << 'puppet6--simp'
      end

      logger.info(%{INFO: repos to disable: '#{to_disable.join("', '")}'.})

      # NOTE: This --enablerepo enables the repos for listing and is inherited
      # from YUM. This does not actually "enable" the repos, that would require
      # the "--enable" option (from yum-config-manager) :-D.
      #
      # Note: Certain versions of EL8 do not dump by default and EL7 does not
      # have the '--dump' option.
      x = on(sut, %{yum repolist all || dnf repolist --all}).stdout.lines
      y = x.map{|z| z.gsub(%r{/.*\Z},'')}
      available_repos = y.grep(/\A([a-zA-Z][a-zA-Z0-9:_-]+)\s*/){|x| $1}
      logger.info(%{INFO: available repos: '#{available_repos.join("', '")}'.})

      invalid_repos = (to_disable - available_repos)

      # Verify that the repos passed to disable are in the list of valid repos
      unless invalid_repos.empty?
        logger.warn(%{WARN: install_simp_repo - requested repos to disable do not exist on the target system '#{invalid_repos.join("', '")}'.})
      end


      (to_disable - invalid_repos).each do |repo|
        on(sut, %{yum-config-manager --disable "#{repo}"})
      end
    end
  end

  set_yum_opts_on(suts, {'simp*.skip_if_unavailable' => '1' })
end

#is_windows?(sut) ⇒ Boolean

Returns:

  • (Boolean)


109
110
111
# File 'lib/simp/beaker_helpers.rb', line 109

def is_windows?(sut)
  sut[:platform] =~ /windows/i
end

#latest_puppet_agent_version_for(puppet_version) ⇒ String, Nil

Looks up latest ‘puppet-agent` version by the version of its `puppet` gem

Parameters:

  • puppet_version (String)

    target Puppet gem version. Works with Gemfile comparison syntax (e.g., ‘4.0’, ‘= 4.2’, ‘~> 4.3.1’, ‘> 5.1, < 5.5’)

Returns:

  • (String, Nil)

    the ‘puppet-agent` version or nil



1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
# File 'lib/simp/beaker_helpers.rb', line 1407

def latest_puppet_agent_version_for( puppet_version )
  return nil if puppet_version.nil?

  require 'rubygems/requirement'
  require 'rubygems/version'
  require 'yaml'

  _puppet_version = puppet_version.strip.split(',')


  @agent_version_table ||= YAML.load_file(
                             File.expand_path(
                               '../../files/puppet-agent-versions.yaml',
                               File.dirname(__FILE__)
                           )).fetch('version_mappings')
  _pair = @agent_version_table.find do |k,v|
    Gem::Requirement.new(_puppet_version).satisfied_by?(Gem::Version.new(k))
  end
  result = _pair ? _pair.last : nil

  # If we didn't get a match, go look for published rubygems
  unless result
    puppet_gems = nil

    Bundler.with_unbundled_env do
      puppet_gems = %x(gem search -ra -e puppet).match(/\((.+)\)/)
    end

    if puppet_gems
      puppet_gems = puppet_gems[1].split(/,?\s+/).select{|x| x =~ /^\d/}

      # If we don't have a full version string, we need to massage it for the
      # match.
      begin
        if _puppet_version.size == 1
          Gem::Version.new(_puppet_version[0])
          if _puppet_version[0].count('.') < 2
           _puppet_version = "~> #{_puppet_version[0]}"
          end
        end
      rescue ArgumentError
        # this means _puppet_version is not just a version, but a version
        # specifier such as "= 5.2.3", "<= 5.1", "> 4", "~> 4.10.7"
      end

      result = puppet_gems.find do |ver|
        Gem::Requirement.new(_puppet_version).satisfied_by?(Gem::Version.new(ver))
      end
    end
  end

  return result
end

#linux_errata(suts) ⇒ Object



714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
# File 'lib/simp/beaker_helpers.rb', line 714

def linux_errata( suts )
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    # Set the locale if not set
    sut.set_env_var('LANG', 'en_US.UTF-8') unless sut.get_env_var('LANG')

    # We need to be able to flip between server and client without issue
    on sut, 'puppet resource group puppet gid=52'
    on sut, 'puppet resource user puppet comment="Puppet" gid="52" uid="52" home="/var/lib/puppet" managehome=true'

    os_info = fact_on(sut, 'os')

    # Make sure we have a domain on our host
    current_domain = fact_on(sut, 'networking.domain').strip
    hostname = fact_on(sut, 'networking.hostname').strip

    if current_domain.empty?
      new_fqdn = hostname + '.beaker.test'

      safe_sed(sut, 's/#{hostname}.*/#{new_fqdn} #{hostname}/', '/etc/hosts')

      if !sut.which('hostnamectl').empty?
        on(sut, "hostnamectl set-hostname #{new_fqdn}")
      else
        on(sut, "echo '#{new_fqdn}' > /etc/hostname", :accept_all_exit_codes => true)
        on(sut, "hostname #{new_fqdn}", :accept_all_exit_codes => true)
      end

      if sut.file_exist?('/etc/sysconfig/network')
        on(sut, "sed -s '/HOSTNAME=/d' /etc/sysconfig/network")
        on(sut, "echo 'HOSTNAME=#{new_fqdn}' >> /etc/sysconfig/network")
      end
    end

    if fact_on(sut, 'networking.domain').strip.empty?
      fail("Error: hosts must have an FQDN, got domain='#{current_domain}'")
    end

    # This may not exist in docker so just skip the whole thing
    if sut.file_exist?('/etc/ssh')
      # SIMP uses a central ssh key location so we prep that spot in case we
      # flip to the SIMP SSH module.
      on(sut, 'mkdir -p /etc/ssh/local_keys')
      on(sut, 'chown -R root:root /etc/ssh/local_keys')
      on(sut, 'chmod 755 /etc/ssh/local_keys')

       = on(sut, 'getent passwd').stdout.lines

      # Hash of user => home_dir
      # Exclude silly directories
      #   * /
      #   * /dev/*
      #   * /s?bin
      #   * /proc
       = Hash[
        .map do |u|
          u.strip!
          u = u.split(':')
          u[5] =~ %r{^(/|/dev/.*|/s?bin/?.*|/proc/?.*)$} ? [nil] : [u[0], u[5]]
        end
      ]

      .keys.each do |user|
        src_file = "#{[user]}/.ssh/authorized_keys"
        tgt_file = "/etc/ssh/local_keys/#{user}"

        on(sut, %{if [ -f "#{src_file}" ]; then cp -a -f "#{src_file}" "#{tgt_file}" && chmod 644 "#{tgt_file}"; fi}, :silent => true)
      end
    end

    # SIMP uses structured facts, therefore stringify_facts must be disabled
    unless ENV['BEAKER_stringify_facts'] == 'yes'
      on sut, 'puppet config set stringify_facts false'
    end

    # Occasionally we run across something similar to BKR-561, so to ensure we
    # at least have the host defaults:
    #
    # :hieradatadir is used as a canary here; it isn't the only missing key
    unless sut.host_hash.key? :hieradatadir
      configure_type_defaults_on(sut)
    end

    if os_info['family'] == 'RedHat'
      # OS-specific items
      if os_info['name'] == 'RedHat'
        rhel_rhsm_subscribe(sut)

        RSpec.configure do |c|
          c.after(:all) do
            unless ENV['BEAKER_RHSM_UNSUBSCRIBE'] == 'false'
              rhel_rhsm_unsubscribe(sut)
            end
          end
        end
      end

      if [
          'AlmaLinux',
          'Amazon',
          'CentOS',
          'OracleLinux',
          'RedHat',
          'Rocky'
      ].include?(os_info['name'])
        enable_yum_repos_on(sut)
        enable_epel_on(sut)

        # net-tools required for netstat utility being used by be_listening
        if (os_info['release']['major'].to_i >= 7) ||((os_info['name'] == 'Amazon') && (os_info['release']['major'].to_i >= 2))
          pp = <<-EOS
            package { 'net-tools': ensure => installed }
          EOS
          apply_manifest_on(sut, pp, :catch_failures => false)
        end

        # Clean up YUM prior to starting our test runs.
        on(sut, 'yum clean all')
      end
    end
  end
end

#munge_ssh_crypto_policies(suts, key_types = ['ssh-rsa']) ⇒ Object



474
475
476
477
478
479
480
481
482
483
484
# File 'lib/simp/beaker_helpers.rb', line 474

def munge_ssh_crypto_policies(suts, key_types=['ssh-rsa'])
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    if has_crypto_policies(sut)
      install_latest_package_on(sut, 'crypto-policies', nil, :accept_all_exit_codes => true)

      # Since we may be doing this prior to having a box flip into FIPS mode, we
      # need to find and modify *all* of the affected policies
      on( sut, %{sed --follow-symlinks -i 's/\\(HostKeyAlgorithms\\|PubkeyAcceptedKeyTypes\\)\\(.\\)/\\1\\2#{key_types.join(',')},/g' $( grep -L ssh-rsa $( find /etc/crypto-policies /usr/share/crypto-policies -type f -a \\( -name '*.txt' -o -name '*.config' \\) -exec grep -l PubkeyAcceptedKeyTypes {} \\; ) ) })
    end
  end
end

#pfact_on(sut, fact_name) ⇒ Object

use the ‘puppet fact` face to look up facts on an SUT



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/simp/beaker_helpers.rb', line 234

def pfact_on(sut, fact_name)
  found_fact = nil
  # If puppet is not installed, there are no puppet facts to fetch
  if sut.which('puppet').empty?
    found_fact = fact_on(sut, fact_name)
  else
    facts_json = nil
    begin
      cmd_output = on(sut, 'facter -p --json', :silent => true)
      # Facter 4+
      raise('skip facter -p') if (cmd_output.stderr =~ /no longer supported/)

      facts = JSON.parse(cmd_output.stdout)
    rescue StandardError
      # If *anything* fails, we need to fall back to `puppet facts`

      facts_json = retry_on(sut, 'puppet facts find garbage_xxx', :silent => true, :max_retries => 4).stdout
      facts = JSON.parse(facts_json)['values']
    end

    found_fact = facts.dig(*(fact_name.split('.')))

    # If we did not find a fact, we should use the upstream function since
    # puppet may be installed via a gem or through some other means.
    found_fact = fact_on(sut, fact_name) if found_fact.nil?
  end

  # Ensure that Hashes return as Hash objects
  # OpenStruct objects have a marshal_dump method
  found_fact.respond_to?(:marshal_dump) ? found_fact.marshal_dump : found_fact
end

#pluginsync_on(suts = hosts) ⇒ Object

pluginsync custom facts for all modules



1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
# File 'lib/simp/beaker_helpers.rb', line 1384

def pluginsync_on( suts = hosts )
  puts "== pluginsync_on'" if ENV['BEAKER_helpers_verbose']
  pluginsync_manifest =<<-PLUGINSYNC_MANIFEST
  file { $::settings::libdir:
        ensure  => directory,
        source  => 'puppet:///plugins',
        recurse => true,
        purge   => true,
        backup  => false,
        noop    => false
      }
  PLUGINSYNC_MANIFEST
  apply_manifest_on(hosts, pluginsync_manifest, :run_in_parallel => @run_in_parallel)
end

#pupmods_in_fixtures_ymlObject

returns an Array of puppet modules declared in .fixtures.yml



333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/simp/beaker_helpers.rb', line 333

def pupmods_in_fixtures_yml
  return @pupmods_in_fixtures_yml if @pupmods_in_fixtures_yml

  STDERR.puts '  ** pupmods_in_fixtures_yml' if ENV['BEAKER_helpers_verbose']
  fixtures_yml = fixtures_yml_path
  data         = YAML.load_file( fixtures_yml )
  repos        = data.fetch('fixtures').fetch('repositories', {}).keys || []
  symlinks     = data.fetch('fixtures').fetch('symlinks', {}).keys     || []
  STDERR.puts '  ** pupmods_in_fixtures_yml: finished' if ENV['BEAKER_helpers_verbose']

  @pupmods_in_fixtures_yml = (repos + symlinks)

  return @pupmods_in_fixtures_yml
end

#puppet_environment_path_on(sut, environment = 'production') ⇒ Object

Return the default environment path



278
279
280
# File 'lib/simp/beaker_helpers.rb', line 278

def puppet_environment_path_on(sut, environment='production')
  File.dirname(sut.puppet_configprint['manifest'])
end

#puppet_modulepath_on(sut, environment = 'production') ⇒ Object

Returns the modulepath on the SUT, as an Array



267
268
269
270
271
272
273
274
275
# File 'lib/simp/beaker_helpers.rb', line 267

def puppet_modulepath_on(sut, environment='production')
  splitchar = ':'
  splitchar = ';' if is_windows?(sut)

  (
    sut.puppet_configprint['modulepath'].split(splitchar) +
    sut.puppet_configprint['basemodulepath'].split(splitchar)
  ).uniq
end

#rhel_repo_disable(suts, repos) ⇒ Object



923
924
925
926
927
928
929
930
931
# File 'lib/simp/beaker_helpers.rb', line 923

def rhel_repo_disable(suts, repos)
  if ENV['BEAKER_RHSM_USER'] && ENV['BEAKER_RHSM_PASS']
    block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
      Array(repos).each do |repo|
        on(sut, %{subscription-manager repos --disable #{repo}}, :accept_all_exit_codes => true)
      end
    end
  end
end

#rhel_repo_enable(suts, repos) ⇒ Object



913
914
915
916
917
918
919
920
921
# File 'lib/simp/beaker_helpers.rb', line 913

def rhel_repo_enable(suts, repos)
  if ENV['BEAKER_RHSM_USER'] && ENV['BEAKER_RHSM_PASS']
    block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
      Array(repos).each do |repo|
        on(sut, %{subscription-manager repos --enable #{repo}})
      end
    end
  end
end

#rhel_rhsm_subscribe(suts, *opts) ⇒ Object

Register a RHEL system with a development license

Must set BEAKER_RHSM_USER and BEAKER_RHSM_PASS environment variables or pass them in as parameters



840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
# File 'lib/simp/beaker_helpers.rb', line 840

def rhel_rhsm_subscribe(suts, *opts)
  require 'securerandom'

  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    rhsm_opts = {
      :username => ENV['BEAKER_RHSM_USER'],
      :password => ENV['BEAKER_RHSM_PASS'],
      :system_name => "#{sut}_beaker_#{Time.now.to_i}_#{SecureRandom.uuid}",
      :repo_list => {
        '7' => [
          'rhel-7-server-extras-rpms',
          'rhel-7-server-rh-common-rpms',
          'rhel-7-server-rpms',
          'rhel-7-server-supplementary-rpms'
        ],
        '8' => [
          'rhel-8-for-x86_64-baseos-rpms',
          'rhel-8-for-x86_64-supplementary-rpms'
        ],
        '9' => [
          'rhel-9-for-x86_64-appstream-rpms',
          'rhel-9-for-x86_64-baseos-rpms'
        ]
      }
    }

    if opts && opts.is_a?(Hash)
      rhsm_opts.merge!(opts)
    end

    os = fact_on(sut, 'os.name').strip
    os_release = fact_on(sut, 'os.release.major').strip

    if os == 'RedHat'
      unless rhsm_opts[:username] && rhsm_opts[:password]
        warn("BEAKER_RHSM_USER and/or BEAKER_RHSM_PASS not set on RHEL system.", "Assuming that subscription-manager is not needed. This may prevent packages from installing")
        return
      end

      sub_status = on(sut, 'subscription-manager status', :accept_all_exit_codes => true)
      unless sub_status.exit_code == 0
        logger.info("Registering #{sut} via subscription-manager")
        on(sut, %{subscription-manager register --auto-attach --name='#{rhsm_opts[:system_name]}' --username='#{rhsm_opts[:username]}' --password='#{rhsm_opts[:password]}'}, :silent => true)
      end

      if rhsm_opts[:repo_list][os_release]
        rhel_repo_enable(sut, rhsm_opts[:repo_list][os_release])
      else
        logger.warn("simp-beaker-helpers:#{__method__} => Default repos for RHEL '#{os_release}' not found")
      end

      # Ensure that all users can access the entitlements since we don't know
      # who we'll be running jobs as (often not root)
      on(sut, 'chmod -R ugo+rX /etc/pki/entitlement', :accept_all_exit_codes => true)
    end
  end
end

#rhel_rhsm_unsubscribe(suts) ⇒ Object



933
934
935
936
937
938
939
# File 'lib/simp/beaker_helpers.rb', line 933

def rhel_rhsm_unsubscribe(suts)
  if ENV['BEAKER_RHSM_USER'] && ENV['BEAKER_RHSM_PASS']
    block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
      on(sut, %{subscription-manager unregister}, :accept_all_exit_codes => true)
    end
  end
end

#rsync_functional_on?(sut) ⇒ Boolean

Returns:

  • (Boolean)


121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/simp/beaker_helpers.rb', line 121

def rsync_functional_on?(sut)
  # We have to check if rsync *still* works otherwise
  return false if (@rsync_functional == false)

  require 'facter'
  unless Facter::Util::Resolution.which('rsync')
    @rsync_functional = false
    return @rsync_functional
  end

  require 'tempfile'

  testfile = Tempfile.new('rsync_check')
  testfile.puts('test')
  testfile.close

  begin
    rsync_to(sut, testfile.path, sut.system_temp_path)
  rescue Beaker::Host::CommandFailure
    @rsync_functional = false
    return false
  ensure
    testfile.unlink
  end

  return true
end

#run_fake_pki_ca_on(ca_sut = master, suts = hosts, local_dir = '') ⇒ Object

Generate a fake openssl CA + certs for each host on a given SUT

The directory structure is the same as what FakeCA drops into keydist/

NOTE: This generates everything within an SUT and copies it back out.

This is because it is assumed the SUT will have the appropriate
openssl in its environment, which may not be true of the host.


993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
# File 'lib/simp/beaker_helpers.rb', line 993

def run_fake_pki_ca_on( ca_sut = master, suts = hosts, local_dir = '' )
  puts "== Fake PKI CA"
  pki_dir  = File.expand_path( "../../files/pki", File.dirname(__FILE__))
  host_dir = '/root/pki'

  ca_sut.mkdir_p(host_dir)
  Dir[ File.join(pki_dir, '*') ].each{|f| copy_to( ca_sut, f, host_dir)}

  # Collect network information from all SUTs
  #
  # We need this so that we don't insert any common IP addresses into certs
  suts_network_info = {}

  hosts.each do |host|
    fqdn = fact_on(host, 'networking.fqdn').strip

    host_entry = { fqdn => [] }

    # Add the short name because containers can't change the hostname
    host_entry[fqdn] << host.name if (host[:hypervisor] == 'docker')

    # Ensure that all interfaces are active prior to collecting data
    activate_interfaces(host)

    networking_fact = pfact_on(host, 'networking')
    if networking_fact && networking_fact['interfaces']
      networking_fact['interfaces'].each do |iface, data|
        next unless data['ip']
        next if data['ip'].start_with?('127.')

        host_entry[fqdn] << data['ip'].strip
      end
    else
      # Gather the IP Addresses for the host to embed in the cert
      interfaces = fact_on(host, 'interfaces').strip.split(',')
      interfaces.each do |interface|
        ipaddress = fact_on(host, "ipaddress_#{interface}")

        next if ipaddress.nil? || ipaddress.empty? || ipaddress.start_with?('127.')

        host_entry[fqdn] << ipaddress.strip
      end
    end

    unless host_entry[fqdn].empty?
      suts_network_info[fqdn] = host_entry[fqdn].sort.uniq
    end
  end

  # Get all of the repeated SUT IP addresses:
  #   1. Create a hash of elements that have a key that is the value and
  #      elements that are the same value
  #   2. Grab all elements that have more than one value (therefore, were
  #      repeated)
  #   3. Pull out an Array of all of the common element keys for future
  #      comparison
  common_ip_addresses = suts_network_info
    .values.flatten
    .group_by{ |x| x }
    .select{|k,v| v.size > 1}
    .keys

  # generate PKI certs for each SUT
  Dir.mktmpdir do |dir|
    pki_hosts_file = File.join(dir, 'pki.hosts')

    File.open(pki_hosts_file, 'w') do |fh|
      suts_network_info.each do |fqdn, ipaddresses|
        fh.puts ([fqdn] + (ipaddresses - common_ip_addresses)) .join(',')
      end
    end

    copy_to(ca_sut, pki_hosts_file, host_dir)

    # generate certs
    on(ca_sut, "cd #{host_dir}; cat #{host_dir}/pki.hosts | xargs bash make.sh")
  end

  # if a local_dir was provided, copy everything down to it
  unless local_dir.empty?
    FileUtils.mkdir_p local_dir
    scp_from( ca_sut, host_dir, local_dir )
  end
end

#safe_sed(suts = hosts, pattern, target_file) ⇒ Object

Perform the equivalend of an in-place sed without changing the target inode

Required for many container targets



489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/simp/beaker_helpers.rb', line 489

def safe_sed(suts = hosts, pattern, target_file)
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    tmpfile = sut.tmpfile('safe_sed')

    command = [
      "cp #{target_file} #{tmpfile}",
      "sed -i '#{pattern}' #{tmpfile}",
      "cat #{tmpfile} > #{target_file}"
    ].join(' && ')

    on(sut, command)

    sut.rm_rf(tmpfile)
  end
end

#set_hiera_config_on(sut, hiera_yaml) ⇒ Object

Updates the default environment hiera.yaml

Parameters:

  • sut (Host)

    One host to act upon

  • hiera_yaml (Hash, String)

    The data to place into hiera.yaml



1246
1247
1248
1249
1250
# File 'lib/simp/beaker_helpers.rb', line 1246

def set_hiera_config_on(sut, hiera_yaml)
  hiera_yaml = hiera_yaml.to_yaml if hiera_yaml.is_a?(Hash)

  create_remote_file(sut, hiera_config_path_on(sut), hiera_yaml)
end

#set_hieradata_on(sut, hieradata, terminus = 'deprecated') ⇒ Nil

Note:

This is authoritative. It manages both Hiera data and configuration, so it may not be used with other Hiera data sources.

Write the provided data structure to Hiera’s :datadir and configure Hiera to use that data exclusively.

Parameters:

  • sut (Array<Host>, String, Symbol)

    One or more hosts to act upon.

  • heradata (Hash, String)

    The full hiera data structure to write to the system.

  • terminus (String) (defaults to: 'deprecated')

    DEPRECATED - Will be removed in a future release. All hieradata is written to the first discovered path via ‘puppet lookup’

Returns:

  • (Nil)


1364
1365
1366
# File 'lib/simp/beaker_helpers.rb', line 1364

def set_hieradata_on(sut, hieradata, terminus = 'deprecated')
  write_hieradata_to sut, hieradata
end

#set_simp_repo_release(sut, simp_release_type = 'stable', simp_release = '6') ⇒ Object

Set the release and release type of the SIMP yum repos

Environment variables may be used to set either one

* BEAKER_SIMP_repo_release => The actual release (version number)
* BEAKER_SIMP_repo_release_type => The type of release (stable, unstable, rolling, etc...)


1639
1640
1641
1642
1643
1644
1645
1646
1647
# File 'lib/simp/beaker_helpers.rb', line 1639

def set_simp_repo_release(sut, simp_release_type='stable', simp_release='6')
  simp_release = ENV.fetch('BEAKER_SIMP_repo_release', simp_release)
  simp_release_type = ENV.fetch('BEAKER_SIMP_repo_release_type', simp_release_type)

  simp_release_type = 'releases' if (simp_release_type == 'stable')

  create_remote_file(sut, '/etc/yum/vars/simprelease', simp_release)
  create_remote_file(sut, '/etc/yum/vars/simpreleasetype', simp_release_type)
end

#set_yum_opt_on(suts, key, value) ⇒ Object

Sets a single YUM option in the form that yum-config-manager/dnf config-manager would expect.

If not prefaced with a repository, the option will be applied globally.

Has no effect if yum or dnf is not present.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/simp/beaker_helpers.rb', line 30

def set_yum_opt_on(suts, key, value)
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    repo,target = key.split('.')

    unless target
      key = "\\*.#{repo}"
    end

    command = nil
    if !sut.which('dnf').empty?
      install_package_unless_present_on(sut, 'dnf-plugins-core', :accept_all_exit_codes => true)
      command = 'dnf config-manager'
    elsif !sut.which('yum').empty?
      command = 'yum-config-manager'
    end

    on(sut, %{#{command} --save --setopt=#{key}=#{value}}, :silent => true) if command
  end
end

#set_yum_opts_on(suts, yum_opts = {}) ⇒ Object

Takes a hash of YUM options to set in the form that yum-config-manager/dnf config-manager would expect.

If not prefaced with a repository, the option will be applied globally.

Example:

{
  'skip_if_unavailable' => '1', # Applies globally
  'foo.installonly_limit' => '5' # Applies only to the 'foo' repo
}


60
61
62
63
64
65
66
# File 'lib/simp/beaker_helpers.rb', line 60

def set_yum_opts_on(suts, yum_opts={})
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    yum_opts.each_pair do |k,v|
      set_yum_opt_on(sut, k, v)
    end
  end
end

#sosreport(suts, dest = 'sosreports') ⇒ Object



898
899
900
901
902
903
904
905
906
907
908
909
910
911
# File 'lib/simp/beaker_helpers.rb', line 898

def sosreport(suts, dest='sosreports')
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    install_latest_package_on(sut, 'sos')
    on(sut, 'sosreport --batch')

    files = on(sut, 'ls /var/tmp/sosreport* /tmp/sosreport* 2>/dev/null', :accept_all_exit_codes => true).output.lines.map(&:strip)

    FileUtils.mkdir_p(dest)

    files.each do |file|
      scp_from(sut, file, File.absolute_path(dest))
    end
  end
end

#update_package_from_centos_stream(suts, package_name) ⇒ Object



706
707
708
709
710
711
712
# File 'lib/simp/beaker_helpers.rb', line 706

def update_package_from_centos_stream(suts, package_name)
  block_on(suts, :run_in_parallel => @run_in_parallel) do |sut|
    sut.install_package('centos-release-stream') unless sut.check_for_package('centos-release-stream')
    install_latest_package_on(sut, package_name)
    sut.uninstall_package('centos-release-stream')
  end
end

#write_hieradata_to(sut, hieradata, terminus = 'deprecated') ⇒ Nil

Note:

This is useless unless Hiera is configured to use the data file. @see ‘#set_hiera_config_on`

Note:

This creates a tempdir on the host machine which should be removed using ‘#clear_temp_hieradata` in the `after(:all)` hook. It may also be retained for debugging purposes.

Writes a YAML file in the Hiera :datadir of a Beaker::Host.

Parameters:

  • sut (Array<Host>, String, Symbol)

    One or more hosts to act upon.

  • hieradata (Hash, String)

    The full hiera data structure to write to the system.

  • terminus (String) (defaults to: 'deprecated')

    DEPRECATED - This will be removed in a future release and currently has no effect.

Returns:

  • (Nil)


1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
# File 'lib/simp/beaker_helpers.rb', line 1271

def write_hieradata_to(sut, hieradata, terminus = 'deprecated')
  @temp_hieradata_dirs ||= []
  data_dir = Dir.mktmpdir('hieradata')
  @temp_hieradata_dirs << data_dir

  fh = File.open(File.join(data_dir, 'common.yaml'), 'w')
  if hieradata.is_a?(String)
    fh.puts(hieradata)
  else
    fh.puts(hieradata.to_yaml)
  end
  fh.close

  copy_hiera_data_to sut, File.join(data_dir, 'common.yaml')
end