diff --git a/.github/actions/presuite.rb b/.github/actions/presuite.rb deleted file mode 100644 index 189d77ccdc..0000000000 --- a/.github/actions/presuite.rb +++ /dev/null @@ -1,157 +0,0 @@ -# frozen_string_literal: true - -require 'open3' -require 'fileutils' - -def if_no_env_vars_set_defaults - ENV['FACTER_ROOT'] = __dir__.gsub('/.github/actions', '') unless ENV['FACTER_ROOT'] - ENV['SHA'] = 'latest' unless ENV['SHA'] - ENV['RELEASE_STREAM'] = 'puppet7' unless ENV['RELEASE_STREAM'] -end - -def install_bundler - message('INSTALL BUNDLER') - run('gem install bundler') -end - -def install_facter_acceptance_dependencies - message('INSTALL FACTER ACCEPTANCE DEPENDENCIES') - run('bundle install') -end - -def initialize_beaker - beaker_platform_with_options = platform_with_options(beaker_platform) - - message('BEAKER INITIALIZE') - run("beaker init -h #{beaker_platform_with_options} -o #{File.join('config', 'aio', 'options.rb')}") - - message('BEAKER PROVISION') - run('beaker provision') -end - -def beaker_platform - { - 'ubuntu-20.04' => 'ubuntu2004-64a', - 'macos-12' => 'osx12-64a', - 'windows-2016' => 'windows2016-64a', - 'windows-2019' => 'windows2019-64a' - }[HOST_PLATFORM] -end - -def platform_with_options(platform) - return "\"#{platform}{hypervisor=none,hostname=localhost,is_cygwin=false}\"" if platform.include? 'windows' - - "#{platform}{hypervisor=none\\,hostname=localhost}" -end - -def install_puppet_agent - message('INSTALL PUPPET AGENT') - - beaker_puppet_root = run('bundle info beaker-puppet --path') - - # Bundler/Rubygems can sometimes give output other than the filepath (deprecation warnings, etc.) - begin - if File.exist?(beaker_puppet_root.chomp) - presuite_file_path = File.join(beaker_puppet_root.chomp, 'setup', 'aio', '010_Install_Puppet_Agent.rb') - - run("beaker exec pre-suite --pre-suite #{presuite_file_path} --preserve-state", './', env_path_var) - else - exit - end - rescue SystemExit - puts "`bundle info beaker-puppet --path` produced unexpected output, please address this." - end -end - -def puppet_puppet_bin_dir - return '/opt/puppetlabs/puppet/bin' unless HOST_PLATFORM.include? 'windows' - - 'C:\\Program Files\\Puppet Labs\\Puppet\\puppet\\bin' -end - -def puppet_bin_dir - return '/opt/puppetlabs/puppet/bin' unless HOST_PLATFORM.include? 'windows' - - 'C:\\Program Files\\Puppet Labs\\Puppet\\bin' -end - -def puppet_ruby - return '/opt/puppetlabs/puppet/bin/ruby' unless HOST_PLATFORM.include? 'windows' - - 'C:\\Program Files\\Puppet Labs\\Puppet\\puppet\\bin\\ruby.exe' -end - -def facter_lib_path - return '/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/facter' unless HOST_PLATFORM.include? 'windows' - - 'C:\\Program Files\\Puppet Labs\\Puppet\\puppet\\lib\\ruby\\vendor_ruby\\facter' -end - -def env_path_var - HOST_PLATFORM.include?('windows') ? { 'PATH' => "#{puppet_bin_dir};#{ENV['PATH']}" } : {} -end - -def install_facter - message('OVERWRITE FACTER FROM PUPPET AGENT') - - # clean facter directory - FileUtils.rm_r(facter_lib_path) - FileUtils.mkdir(facter_lib_path) - - Dir.chdir('../') do - run("\'#{puppet_ruby}\' install.rb --bindir=\'#{puppet_puppet_bin_dir}\' " \ - "--sitelibdir=\'#{facter_lib_path.gsub('facter', '')}\'") - end -end - -def run_acceptance_tests - message('RUN ACCEPTANCE TESTS') - - run('beaker exec tests --test-tag-exclude=server,facter_3 --test-tag-or=risk:high,audit:high', './', env_path_var) -end - -def message(message) - message_length = message.length - total_length = 130 - lines_length = (total_length - message_length) / 2 - result = ('-' * lines_length + ' ' + message + ' ' + '-' * lines_length)[0, total_length] - puts "\n\n#{result}\n\n" -end - -def run(command, dir = './', env = {}) - puts command - output = '' - Open3.popen2e(env, command, chdir: dir) do |_stdin, stdout_and_err, wait_thr| - stdout_and_err.each do |line| - puts line - output += line - end - exit_status = wait_thr.value.exitstatus - exit(exit_status) if exit_status != 0 - end - output -end - -def verify_facter_standalone_exits_0 - Dir.chdir(ENV['FACTER_ROOT']) do - run('bundle install --without development') - run('bundle exec facter') - end -end - -ENV['DEBIAN_DISABLE_RUBYGEMS_INTEGRATION'] = 'no_warnings' -if_no_env_vars_set_defaults -ACCEPTANCE_PATH = File.join(ENV['FACTER_ROOT'], 'acceptance') -HOST_PLATFORM = ARGV[0] - -install_bundler - -verify_facter_standalone_exits_0 - -Dir.chdir(ACCEPTANCE_PATH) do - install_facter_acceptance_dependencies - initialize_beaker - install_puppet_agent - install_facter - run_acceptance_tests -end diff --git a/.github/workflows/acceptance_tests.yml b/.github/workflows/acceptance_tests.yml deleted file mode 100644 index cc321f8f28..0000000000 --- a/.github/workflows/acceptance_tests.yml +++ /dev/null @@ -1,67 +0,0 @@ ---- -name: Acceptance tests - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - acceptance_tests: - name: Platform - strategy: - matrix: - os: [ windows-2019, ubuntu-20.04, macos-12 ] - runs-on: ${{ matrix.os }} - env: - BEAKER_debug: true - FACTER_ROOT: facter - SHA: latest - RELEASE_STREAM: puppet7 - - steps: - - name: Checkout current PR - uses: actions/checkout@v4 - with: - path: facter - - - name: Install Ruby 2.7 - uses: ruby/setup-ruby@v1 - with: - ruby-version: '2.7' - rubygems: '3.3.26' - - - name: Fix common Linux and macOS permissions - if: runner.os != 'Windows' - run: sudo chmod a-w /opt - - - name: Fix Linux permissions - if: runner.os == 'Linux' - run: | - sudo chmod a-w /home/runner /usr/share && - sudo chmod -R a-w /home/runner/.config /home/linuxbrew - - - name: Install dhclient for Linux - if: runner.os == 'Linux' - run: | - sudo apt install dhcpcd5 - sudo dhclient - - # IPv6 is missing on the GitHub macOS image and we need it for the networking facts tests - # https://github.com/actions/runner-images/issues/668 - - name: Add IPv6 on macOS - if: runner.os == 'macOS' - run: | - primary_interface=`route -n get default | awk '/interface: */{print $NF}'` - sudo ifconfig $primary_interface inet6 add ::1/64 - - - name: Run acceptance tests on Linux-like platform - if: runner.os != 'Windows' - run: sudo -E "PATH=$PATH" ruby $FACTER_ROOT/.github/actions/presuite.rb ${{ matrix.os }} - - - name: Run acceptance tests on Windows-like platform - if: runner.os == 'Windows' - run: ruby $Env:FACTER_ROOT/.github/actions/presuite.rb ${{ matrix.os }} diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 8cc88f0f4c..a4e14c9c7f 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -11,7 +11,7 @@ on: jobs: rubocop_checks: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest name: RuboCop steps: - name: Checkout current PR diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml index 29bcfecc7b..c0f9a8e80a 100644 --- a/.github/workflows/integration_tests.yaml +++ b/.github/workflows/integration_tests.yaml @@ -22,8 +22,8 @@ jobs: - {os: ubuntu-22.04, ruby: '3.2'} # with openssl 3 - {os: ubuntu-22.04, ruby: 'jruby-9.3.14.0'} - {os: ubuntu-latest, ruby: 'jruby-9.4.8.0'} - - {os: windows-2019, ruby: '2.7'} - - {os: windows-2019, ruby: '3.2'} # with openssl 3 + - {os: windows-2022, ruby: '2.7'} + - {os: windows-2022, ruby: '3.2'} # with openssl 3 runs-on: ${{ matrix.cfg.os }} env: BUNDLE_SET: 'with integration' diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml index cd0ee2bc5f..e05104ca0c 100644 --- a/.github/workflows/unit_tests.yaml +++ b/.github/workflows/unit_tests.yaml @@ -24,7 +24,9 @@ jobs: - '3.2' - 'jruby-9.3.14.0' - 'jruby-9.4.8.0' - runs-on: ubuntu-20.04 + # 24.04 doesn't support older jruby versions, so use 22.04 as middle ground + # When we drop testing against puppet7, then we can use newer ubuntu. + runs-on: ubuntu-22.04 steps: - name: Checkout current PR uses: actions/checkout@v4 @@ -43,7 +45,7 @@ jobs: ruby: - '2.7' - '3.2' - runs-on: windows-2019 + runs-on: windows-2022 steps: - name: Checkout current PR uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index bb0cf40eef..76aa0d30f6 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ build-iPhoneSimulator/ .rvmrc .rspec_status +.rspec_integration_status Gemfile.lock Gemfile.local acceptance/vendor diff --git a/.rubocop.yml b/.rubocop.yml index edf01dc4b1..50565e9b59 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -11,6 +11,10 @@ require: - rubocop-performance - rubocop-rspec +plugins: + - rubocop-factory_bot + - rubocop-capybara + Layout/LineLength: Enabled: false @@ -43,6 +47,7 @@ Naming/MethodName: Exclude: - 'spec/mocks/win32ole.rb' - 'spec/mocks/ffi.rb' + - 'spec/facter/util/windows/network_utils_spec.rb' Naming/PredicateName: Exclude: diff --git a/Rakefile b/Rakefile index 055f62d1b2..8f80bc3f16 100644 --- a/Rakefile +++ b/Rakefile @@ -5,8 +5,6 @@ require 'open3' require 'rspec/core/rake_task' require 'facter/version' -Dir.glob(File.join('tasks/**/*.rake')).each { |file| load file } - task default: :spec desc 'Generate changelog' diff --git a/acceptance/lib/facter/acceptance/base_fact_utils.rb b/acceptance/lib/facter/acceptance/base_fact_utils.rb index c789830923..281bcadfcd 100644 --- a/acceptance/lib/facter/acceptance/base_fact_utils.rb +++ b/acceptance/lib/facter/acceptance/base_fact_utils.rb @@ -118,7 +118,7 @@ def debian_expected_facts(agent) # el (RedHat, Centos) def el_expected_facts(agent) - version = agent['platform'].match(/(el|centos)-(\d)/) + version = agent['platform'].match(/(el|centos)-(\d+)/) os_version = if version.nil? /\d+/ else @@ -143,6 +143,11 @@ def el_expected_facts(agent) os_distro_description = /Rocky Linux release #{os_version}\.\d+ \(.+\)/ os_distro_id = 'Rocky' os_distro_release_full = /#{os_version}\.\d+/ + when /rhel/ + os_name = 'RedHat' + os_distro_description = /Red Hat Enterprise Linux( Server)? release #{os_version}\.\d+( Beta)? \(\w+\)/ + os_distro_id = /^RedHatEnterprise(Server)?$/ + os_distro_release_full = /#{os_version}\.\d+/ when /centos/ os_name = 'CentOS' os_distro_description = /CentOS( Linux)? release #{os_version}\.\d+(\.\d+)? \(\w+\)/ @@ -154,6 +159,7 @@ def el_expected_facts(agent) os_distro_id = /^RedHatEnterprise(Server)?$/ os_distro_release_full = /#{os_version}\.\d+/ end + if agent['platform'] =~ /x86_64/ os_arch = 'x86_64' os_hardware = 'x86_64' @@ -190,7 +196,7 @@ def el_expected_facts(agent) expected_facts['os.release.minor'] = /(\d+)?/ expected_facts['processors.count'] = /[1-9]/ expected_facts['processors.physicalcount'] = /[1-9]/ - expected_facts['processors.isa'] = os_hardware + expected_facts['processors.isa'] = /unknown|#{os_hardware}/ expected_facts['processors.models'] = processor_model_pattern expected_facts['kernel'] = 'Linux' expected_facts['kernelrelease'] = /\d+\.\d+\.\d+/ @@ -437,6 +443,8 @@ def windows_expected_facts(agent) os_version = '2019' elsif agent['platform'] =~ /2022/ os_version = '2022' + elsif agent['platform'] =~ /2025/ + os_version = '2025' else fail_test "Unknown Windows version #{agent['platform']}" end diff --git a/acceptance/tests/custom_facts/using_win32ole_should_not_hang.rb b/acceptance/tests/custom_facts/using_win32ole_should_not_hang.rb index 8e369cdd0b..08118272cb 100644 --- a/acceptance/tests/custom_facts/using_win32ole_should_not_hang.rb +++ b/acceptance/tests/custom_facts/using_win32ole_should_not_hang.rb @@ -9,7 +9,7 @@ setcode do require 'win32ole' locator = WIN32OLE.new('WbemScripting.SWbemLocator') - locator.ConnectServer('', "root/CIMV2", '', '', nil, nil, nil, nil).to_s + locator.ConnectServer('', "root/CIMV2", '', '', nil, nil, nil, nil).class.to_s end end EOM @@ -26,7 +26,7 @@ # Test is assumed to have hung if it takes longer than 5 seconds. Timeout::timeout(5) do on agent, facter('--custom-dir', custom_dir, 'custom_fact_ole') do |facter_result| - assert_match(/#/, facter_result.stdout.chomp, 'Custom fact output does not match expected output') + assert_match(/WIN32OLE/, facter_result.stdout.chomp, 'Custom fact output does not match expected output') end end end diff --git a/acceptance/tests/facts/networking_facts.rb b/acceptance/tests/facts/networking_facts.rb index 64f7077316..b846ef7171 100644 --- a/acceptance/tests/facts/networking_facts.rb +++ b/acceptance/tests/facts/networking_facts.rb @@ -11,7 +11,7 @@ agents.each do |agent| expected_networking = { - %w[networking dhcp] => agent['platform'] =~ /fedora-|el-8-|el-9-/ ? '' : @ip_regex, # https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/426 + %w[networking dhcp] => agent['platform'] =~ /fedora-|el-(8|9|10)/ ? '' : @ip_regex, # https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/426 %w[networking ip] => @ip_regex, %w[networking ip6] => /[a-f0-9]+:+/, %w[networking mac] => /[a-f0-9]{2}:/, diff --git a/facter.gemspec b/facter.gemspec index 74ed30af94..956b1dad37 100644 --- a/facter.gemspec +++ b/facter.gemspec @@ -14,15 +14,9 @@ Gem::Specification.new do |spec| spec.description = 'You can prove anything with facts!' spec.license = 'Apache-2.0' - dirs = - Dir[File.join(__dir__, 'bin/facter')] + - Dir[File.join(__dir__, 'LICENSE')] + - Dir[File.join(__dir__, 'lib/**/*.rb')] + - Dir[File.join(__dir__, 'lib/**/*.json')] + - Dir[File.join(__dir__, 'lib/**/*.conf')] + - Dir[File.join(__dir__, 'lib/**/*.erb')] - base = "#{__dir__}#{File::SEPARATOR}" - spec.files = dirs.map { |path| path.sub(base, '') } + spec.files = + %w[LICENSE README.md Gemfile facter.gemspec] + + Dir['{bin,lib}/**/*'].reject { |f| File.directory?(f) || f.end_with?('.dox') || f.end_with?('.yaml') } spec.required_ruby_version = '>= 2.5', '< 4.0' spec.bindir = 'bin' diff --git a/lib/facter/custom_facts/core/execution/base.rb b/lib/facter/custom_facts/core/execution/base.rb index a498208c68..7707743823 100644 --- a/lib/facter/custom_facts/core/execution/base.rb +++ b/lib/facter/custom_facts/core/execution/base.rb @@ -139,7 +139,7 @@ def log_stderr(msg, command, logger) def builtin_command?(command) output, _status = Open3.capture2("type #{command}") - /builtin/.match?(output.chomp) ? true : false + /builtin/.match?(output.chomp) end end end diff --git a/lib/facter/custom_facts/core/resolvable.rb b/lib/facter/custom_facts/core/resolvable.rb index a3be67aa9b..1b476853a4 100644 --- a/lib/facter/custom_facts/core/resolvable.rb +++ b/lib/facter/custom_facts/core/resolvable.rb @@ -79,7 +79,7 @@ def value end end - LegacyFacter::Util::Normalization.normalize(result) + LegacyFacter::Util::Normalization.normalize(@fact.name, result) rescue Timeout::Error => e Facter.log_exception(e, "Timed out after #{limit} seconds while resolving #{qualified_name}") diff --git a/lib/facter/custom_facts/core/suitable.rb b/lib/facter/custom_facts/core/suitable.rb index 9e9af4b3cd..1503cfa945 100644 --- a/lib/facter/custom_facts/core/suitable.rb +++ b/lib/facter/custom_facts/core/suitable.rb @@ -18,7 +18,7 @@ module Suitable # @return [void] # # @api public - def has_weight(weight) # rubocop:disable Naming/PredicateName + def has_weight(weight) # rubocop:disable Naming/PredicatePrefix @weight = weight self end diff --git a/lib/facter/custom_facts/util/fact.rb b/lib/facter/custom_facts/util/fact.rb index 863b79defc..02f307c4b3 100644 --- a/lib/facter/custom_facts/util/fact.rb +++ b/lib/facter/custom_facts/util/fact.rb @@ -35,7 +35,7 @@ class Fact # # @api private def initialize(name, options = {}) - @name = LegacyFacter::Util::Normalization.normalize(name.to_s.downcase).intern + @name = LegacyFacter::Util::Normalization.normalize(name.to_s.downcase, name.to_s.downcase).intern @options = options.dup extract_ldapname_option!(options) diff --git a/lib/facter/custom_facts/util/loader.rb b/lib/facter/custom_facts/util/loader.rb index 0f72a91404..435127137c 100644 --- a/lib/facter/custom_facts/util/loader.rb +++ b/lib/facter/custom_facts/util/loader.rb @@ -115,6 +115,18 @@ def kernel_load(file) Kernel.load(file) end + # Ruby < 3 returned env key/values based on the active code page + # Ruby 3+ use UTF-8, see ruby/ruby@ca76337a00244635faa331afd04f4b75161ce6fb + if RUBY_VERSION.to_f < 3.0 + def env_string(str) + str.force_encoding(Encoding.default_external) + end + else + def env_string(str) + str + end + end + # Load facts from the environment. If no name is provided, # all will be loaded. def load_env(fact = nil) @@ -123,7 +135,8 @@ def load_env(fact = nil) # Skip anything that doesn't match our regex. next unless name =~ /^facter_?(\w+)$/i - env_name = Regexp.last_match(1).downcase + env_name = env_string(Regexp.last_match(1).downcase) + env_value = env_string(value.dup) # If a fact name was specified, skip anything that doesn't # match it. @@ -131,7 +144,7 @@ def load_env(fact = nil) LegacyFacter.add(env_name, fact_type: :external, is_env: true) do has_weight 1_000_000 - setcode { value } + setcode { env_value } end # Short-cut, if we are only looking for one value. diff --git a/lib/facter/custom_facts/util/normalization.rb b/lib/facter/custom_facts/util/normalization.rb index b768f42987..ea8f95ef03 100644 --- a/lib/facter/custom_facts/util/normalization.rb +++ b/lib/facter/custom_facts/util/normalization.rb @@ -17,18 +17,18 @@ class NormalizationError < StandardError; end # @api public # @raise [NormalizationError] If the data structure contained an invalid element. # @return [void] - def normalize(value) + def normalize(fact_name, value) case value when Integer, Float, TrueClass, FalseClass, NilClass, Symbol value when Date, Time value.iso8601 when String - normalize_string(value) + normalize_string(fact_name, value) when Array - normalize_array(value) + normalize_array(fact_name, value) when Hash - normalize_hash(value) + normalize_hash(fact_name, value) else raise NormalizationError, "Expected #{value} to be one of #{VALID_TYPES.inspect}, but was #{value.class}" end @@ -47,8 +47,17 @@ def normalize(value) # @param value [String] # @return [void] - def normalize_string(value) - value = value.encode(Encoding::UTF_8) + def normalize_string(fact_name, value) + if value.encoding == Encoding::ASCII_8BIT + value_copy_encoding = value.dup.force_encoding(Encoding::UTF_8) + raise NormalizationError, "custom fact \"#{fact_name}\" with value: #{value_copy_encoding} contains binary data and could not be force encoded to UTF-8" unless value_copy_encoding.valid_encoding? + + log.debug("custom fact \"#{fact_name}\" with value: #{value} contained binary data and was force encoded to UTF-8 and now has the value: #{value_copy_encoding}") + value = value_copy_encoding + + else + value = value.encode(Encoding::UTF_8) + end unless value.valid_encoding? raise NormalizationError, "String #{value.inspect} doesn't match the reported encoding #{value.encoding}" @@ -69,9 +78,9 @@ def normalize_string(value) # @raise [NormalizationError] If one of the elements failed validation # @param value [Array] # @return [void] - def normalize_array(value) + def normalize_array(fact_name, value) value.collect do |elem| - normalize(elem) + normalize(fact_name, elem) end end @@ -81,8 +90,12 @@ def normalize_array(value) # @raise [NormalizationError] If one of the keys or values failed normalization # @param value [Hash] # @return [void] - def normalize_hash(value) - Hash[value.collect { |k, v| [normalize(k), normalize(v)] }] + def normalize_hash(fact_name, value) + Hash[value.collect { |k, v| [normalize(fact_name, k), normalize(fact_name, v)] }] + end + + def log + Facter::Log.new(self) end end end diff --git a/lib/facter/facts/linux/cloud/provider.rb b/lib/facter/facts/linux/cloud/provider.rb index 1e16a19aef..3b5395cb42 100644 --- a/lib/facter/facts/linux/cloud/provider.rb +++ b/lib/facter/facts/linux/cloud/provider.rb @@ -11,9 +11,18 @@ def call_the_resolver when 'hyperv' metadata = Facter::Resolvers::Az.resolve(:metadata) 'azure' unless metadata.nil? || metadata.empty? - when 'kvm', 'xen', 'xenhvm' + when 'kvm', 'xen', 'xenhvm', 'xenu' metadata = Facter::Resolvers::Ec2.resolve(:metadata) - 'aws' unless metadata.nil? || metadata.empty? + if metadata && !metadata.empty? + if Process.uid.zero? && File.executable?('/opt/puppetlabs/puppet/bin/virt-what') # virt-what needs to be run as root + output = Facter::Core::Execution.execute('/opt/puppetlabs/puppet/bin/virt-what') + # rubocop:disable Metrics/BlockNesting + output.lines(chomp: true).any?('aws') ? 'aws' : nil + # rubocop:enable Metrics/BlockNesting + else + 'aws' + end + end when 'gce' metadata = Facter::Resolvers::Gce.resolve(:metadata) 'gce' unless metadata.nil? || metadata.empty? diff --git a/lib/facter/facts/windows/os/release.rb b/lib/facter/facts/windows/os/release.rb index de59acebe5..3abf4b646f 100644 --- a/lib/facter/facts/windows/os/release.rb +++ b/lib/facter/facts/windows/os/release.rb @@ -17,7 +17,8 @@ def call_the_resolver } fact_value = Facter::Util::Facts::WindowsReleaseFinder.find_release(input) - arr << Facter::ResolvedFact.new(FACT_NAME, ({ full: fact_value, major: fact_value } if fact_value)) + resolved_value = { full: fact_value, major: fact_value } if fact_value + arr << Facter::ResolvedFact.new(FACT_NAME, resolved_value) ALIASES.each { |aliass| arr << Facter::ResolvedFact.new(aliass, fact_value, :legacy) } arr end diff --git a/lib/facter/fixtures/facter_help_header b/lib/facter/fixtures/facter_help_header deleted file mode 100644 index 5a17763c8d..0000000000 --- a/lib/facter/fixtures/facter_help_header +++ /dev/null @@ -1,7 +0,0 @@ -Usage -===== - - facter [options] [query] [query] [...] - -Options -======= diff --git a/lib/facter/framework/cli/cli.rb b/lib/facter/framework/cli/cli.rb index 55d9c13c19..0ca3fa7858 100755 --- a/lib/facter/framework/cli/cli.rb +++ b/lib/facter/framework/cli/cli.rb @@ -167,9 +167,9 @@ def list_cache_groups end desc '--help, -h', 'Help for all arguments' - def help(*args) + def help(*_args) help_string = +'' - help_string << help_header(args) + help_string << help_header help_string << add_class_options_to_help help_string << add_commands_to_help @@ -177,10 +177,16 @@ def help(*args) end no_commands do - def help_header(_args) - path = File.join(File.dirname(__FILE__), '../../') + def help_header + <<~HELP + Usage + ===== - Facter::Util::FileHelper.safe_read("#{path}fixtures/facter_help_header") + facter [options] [query] [query] [...] + + Options + ======= + HELP end def add_class_options_to_help diff --git a/lib/facter/framework/core/fact_manager.rb b/lib/facter/framework/core/fact_manager.rb index ccd735c29a..acee18f4dd 100644 --- a/lib/facter/framework/core/fact_manager.rb +++ b/lib/facter/framework/core/fact_manager.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true +require_relative '../../custom_facts/util/normalization' + module Facter class FactManager include Singleton + include LegacyFacter::Util::Normalization def initialize @internal_fact_mgr = InternalFactManager.new @@ -30,6 +33,8 @@ def resolve_facts(user_query = []) FactFilter.new.filter_facts!(resolved_facts, user_query) + validate_resolved_facts(resolved_facts) + log_resolved_facts(resolved_facts) resolved_facts end @@ -57,6 +62,8 @@ def resolve_fact(user_query) @cache_manager.cache_facts(resolved_facts) + validate_resolved_facts(resolved_facts) + log_resolved_facts(resolved_facts) resolved_facts end @@ -64,11 +71,23 @@ def resolve_fact(user_query) def resolve_core(user_query = [], options = {}) log_resolving_method @cache_manager = CacheManager.new - core_fact(user_query, options) + resolved_facts = core_fact(user_query, options) + validate_resolved_facts(resolved_facts) + resolved_facts end private + def validate_resolved_facts(resolved_facts) + resolved_facts.each do |fact| + normalize(fact.name, fact.value) + rescue LegacyFacter::Util::Normalization::NormalizationError => e + detail = e.message + detail += "\n#{e.backtrace.join("\n")}" if @options[:trace] + @log.warn("Fact '#{fact.name}' contains an invalid value: #{detail}") + end + end + def log_resolving_method if Options[:sequential] @log.debugonce('Resolving facts sequentially') diff --git a/lib/facter/resolvers/aix/ffi/ffi_helper.rb b/lib/facter/resolvers/aix/ffi/ffi_helper.rb index c1c6535e75..169fb9da6b 100644 --- a/lib/facter/resolvers/aix/ffi/ffi_helper.rb +++ b/lib/facter/resolvers/aix/ffi/ffi_helper.rb @@ -59,7 +59,7 @@ def self.read_interfaces when FFI::RTM_IFINFO link_addr = FFI::SockaddrDl.new(hdr.to_ptr + hdr.size) - interface_name = link_addr[:sdl_data].to_s[0, link_addr[:sdl_nlen]] + interface_name = link_addr[:sdl_data].to_s[0, link_addr[:sdl_nlen]].dup.force_encoding(Encoding::UTF_8) interfaces[interface_name] ||= {} when FFI::RTM_NEWADDR @@ -101,9 +101,9 @@ def self.read_interfaces bindings = family == FFI::AF_INET ? :bindings : :bindings6 interfaces[interface_name][bindings] ||= [] interfaces[interface_name][bindings] << { - netmask: netmask.read_string, - address: address.read_string, - network: network.read_string + netmask: netmask, + address: address, + network: network } else log.debug("got an unknown RT_IFLIST message: #{hdr[:ifm_type]}") @@ -135,7 +135,7 @@ def self.address_to_string(sockaddr, mask = nil) buffer = ::FFI::MemoryPointer.new(:char, FFI::INET_ADDRSTRLEN) Libc.inet_ntop(FFI::AF_INET, in_addr_ip[:sin_addr].to_ptr, buffer, 16) - buffer + buffer.read_string.dup.force_encoding(Encoding::UTF_8) elsif sockaddr[:sa_family] == FFI::AF_INET6 in_addr_ip = FFI::SockaddrIn6.new(sockaddr.to_ptr) if mask && sockaddr[:sa_family] == mask[:sa_family] @@ -148,7 +148,7 @@ def self.address_to_string(sockaddr, mask = nil) buffer = ::FFI::MemoryPointer.new(:char, FFI::INET6_ADDRSTRLEN) Libc.inet_ntop(FFI::AF_INET6, in_addr_ip[:sin6_addr].to_ptr, buffer, 16) - buffer + buffer.read_string.dup.force_encoding(Encoding::UTF_8) end end diff --git a/lib/facter/resolvers/containers.rb b/lib/facter/resolvers/containers.rb index 030d7267a4..4a468c7611 100644 --- a/lib/facter/resolvers/containers.rb +++ b/lib/facter/resolvers/containers.rb @@ -3,36 +3,59 @@ module Facter module Resolvers class Containers < BaseResolver - # :virtual + # :vm # :hypervisor init_resolver - INFO = { 'docker' => 'id', 'lxc' => 'name' }.freeze - class << self private def post_resolve(fact_name, _options) @fact_list.fetch(fact_name) do - read_environ(fact_name) || read_cgroup(fact_name) + vm, hypervisor = read_cgroup || read_environ + + @fact_list[:vm] = vm + @fact_list[:hypervisor] = hypervisor + @fact_list[fact_name] end end - def read_cgroup(fact_name) + # Read /proc/1/cgroup to determine if we're running in docker or lxc + # returning the vm and hypervisor info. If none found, return nil. + # + # @return Array[], nil + def read_cgroup output_cgroup = Facter::Util::FileHelper.safe_read('/proc/1/cgroup', nil) return unless output_cgroup - output_docker = %r{docker/(.+)}.match(output_cgroup) - output_lxc = %r{^/lxc/([^/]+)}.match(output_cgroup) + # '+' matches one or more characters, so if there's a match, the + # capture group must be non-empty + output_docker = %r{docker/(?.+)}.match(output_cgroup) + output_lxc = %r{^/lxc/(?[^/]+)}.match(output_cgroup) - info, vm = extract_vm_and_info(output_docker, output_lxc) - @fact_list[:vm] = vm - @fact_list[:hypervisor] = { vm.to_sym => info } if vm - @fact_list[fact_name] + if File.exist?('/.dockerenv') + vm = 'docker' + info = output_docker && output_docker[:id] ? { 'id' => output_docker[:id] } : {} + elsif output_docker + vm = 'docker' + info = { 'id' => output_docker[:id] } + elsif output_lxc + vm = 'lxc' + info = { 'name' => output_lxc[:name] } + else + # fallback to read_environ + return nil + end + + [vm, { vm.to_sym => info }] end - def read_environ(fact_name) + # Read the `container` environment variable from /proc/1/environ to + # determine the vm and hypervisor info. If none found, return nil. + # + # @return Array[], nil + def read_environ begin container = Facter::Util::Linux::Proc.getenv_for_pid(1, 'container') rescue StandardError => e @@ -55,27 +78,18 @@ def read_environ(fact_name) return nil when 'systemd-nspawn' vm = 'systemd_nspawn' - info = { 'id' => Facter::Util::FileHelper.safe_read('/etc/machine-id', nil).strip } + machine_id = Facter::Util::FileHelper.safe_read('/etc/machine-id', nil) + info = if machine_id + { 'id' => machine_id.strip } + else + {} + end else - vm = 'container_other' - log.warn("Container runtime, '#{container}', is unsupported, setting to '#{vm}'") - end - @fact_list[:vm] = vm - @fact_list[:hypervisor] = { vm.to_sym => info } if vm - @fact_list[fact_name] - end - - def extract_vm_and_info(output_docker, output_lxc) - vm = nil - if output_docker - vm = 'docker' - info = output_docker[1] - elsif output_lxc - vm = 'lxc' - info = output_lxc[1] + log.debug("Container runtime '#{container}' is not recognized, ignoring") + return nil end - [info ? { INFO[vm] => info } : {}, vm] + [vm, { vm.to_sym => info }] end end end diff --git a/lib/facter/resolvers/dmi.rb b/lib/facter/resolvers/dmi.rb index d27e40111f..4b36e38d3c 100644 --- a/lib/facter/resolvers/dmi.rb +++ b/lib/facter/resolvers/dmi.rb @@ -50,7 +50,7 @@ def chassis_to_name(chassis_type) 'Space-Saving', 'Lunch Box', 'Main System Chassis', 'Expansion Chassis', 'SubChassis', 'Bus Expansion Chassis', 'Peripheral Chassis', 'Storage Chassis', 'Rack Mount Chassis', 'Sealed-Case PC', 'Multi-system', 'CompactPCI', 'AdvancedTCA', 'Blade', 'Blade Enclosure', - 'Tablet', 'Convertible', 'Detachable'] + 'Tablet', 'Convertible', 'Detachable', 'IoT Gateway', 'Embedded PC', 'Mini PC', 'Stick PC'] @fact_list[:chassis_type] = types[chassis_type.to_i - 1] end end diff --git a/lib/facter/resolvers/hostname.rb b/lib/facter/resolvers/hostname.rb index 53c7f9ac3c..c8fd7ec410 100644 --- a/lib/facter/resolvers/hostname.rb +++ b/lib/facter/resolvers/hostname.rb @@ -18,7 +18,7 @@ def post_resolve(fact_name, _options) def retrieve_info(fact_name) require 'socket' - output = Socket.gethostname + output = Socket.gethostname&.dup&.force_encoding(Encoding::UTF_8) || nil unless output log.debug('Socket.gethostname failed to return hostname') return @@ -55,7 +55,7 @@ def retrieve_with_addrinfo(host) end return if name.nil? || name.empty? || host == name[2] || name[2] == name[3] - name[2] + name[2]&.dup&.force_encoding(Encoding::UTF_8) end def exists_and_valid_fqdn?(fqdn, hostname) diff --git a/lib/facter/resolvers/linux/hostname.rb b/lib/facter/resolvers/linux/hostname.rb index 32e0688559..cba8a0c558 100644 --- a/lib/facter/resolvers/linux/hostname.rb +++ b/lib/facter/resolvers/linux/hostname.rb @@ -37,7 +37,7 @@ def retrieve_info(fact_name) end def retrieving_hostname - output = Socket.gethostname || '' + output = Socket.gethostname&.dup&.force_encoding(Encoding::UTF_8) || '' if output.empty? || output['0.0.0.0'] begin require_relative '../../../facter/util/resolvers/ffi/hostname' @@ -70,7 +70,7 @@ def retrieve_fqdn_for_host(host) log.debug("Socket.getaddrinfo failed to retrieve fqdn for hostname #{host} with: #{e}") end - return name[2] if !name.nil? && !name.empty? && host != name[2] && name[2] != name[3] + return name[2].dup.force_encoding(Encoding::UTF_8) if !name.nil? && !name.empty? && host != name[2] && name[2] != name[3] retrieve_fqdn_for_host_with_ffi(host) end diff --git a/lib/facter/resolvers/linux/networking.rb b/lib/facter/resolvers/linux/networking.rb index 1a900f98e5..5af33d3f81 100644 --- a/lib/facter/resolvers/linux/networking.rb +++ b/lib/facter/resolvers/linux/networking.rb @@ -57,11 +57,7 @@ def operstate(interface_name, iface) end def physical(ifname, iface) - iface[:physical] = if File.exist?("/sys/class/net/#{ifname}/device") - true - else - false - end + iface[:physical] = File.exist?("/sys/class/net/#{ifname}/device") end def duplex(interface_name, iface) diff --git a/lib/facter/resolvers/processors.rb b/lib/facter/resolvers/processors.rb index 601499cedb..d54bbb7b75 100644 --- a/lib/facter/resolvers/processors.rb +++ b/lib/facter/resolvers/processors.rb @@ -56,7 +56,7 @@ def count_processors(tokens) end def construct_models_list(tokens) - return unless tokens.first.strip == 'model name' || tokens.first.strip == 'cpu' + return unless ['model name', 'cpu'].include?(tokens.first.strip) @fact_list[:models] << tokens.last.strip end diff --git a/lib/facter/resolvers/ruby.rb b/lib/facter/resolvers/ruby.rb index 094a6af630..4b3fb78b44 100644 --- a/lib/facter/resolvers/ruby.rb +++ b/lib/facter/resolvers/ruby.rb @@ -13,7 +13,7 @@ def post_resolve(fact_name, _options) end def retrieve_ruby_information(fact_name) - @fact_list[:sitedir] = RbConfig::CONFIG['sitelibdir'] if RbConfig::CONFIG['sitedir'] + @fact_list[:sitedir] = RbConfig::CONFIG['sitelibdir']&.dup&.force_encoding(Encoding::UTF_8) if RbConfig::CONFIG['sitedir'] @fact_list[:platform] = RUBY_PLATFORM @fact_list[:version] = RUBY_VERSION @fact_list[fact_name] diff --git a/lib/facter/resolvers/solaris/ffi/structs.rb b/lib/facter/resolvers/solaris/ffi/structs.rb index 0e73f27252..86cf9b1478 100644 --- a/lib/facter/resolvers/solaris/ffi/structs.rb +++ b/lib/facter/resolvers/solaris/ffi/structs.rb @@ -64,7 +64,7 @@ class Lifreq < ::FFI::Struct :pad, [:char, 80] def name - self[:lifr_name].to_s + self[:lifr_name].to_s.dup.force_encoding(Encoding::UTF_8) end def ss_family diff --git a/lib/facter/resolvers/solaris/networking.rb b/lib/facter/resolvers/solaris/networking.rb index ad6e1c1293..08e64fe389 100644 --- a/lib/facter/resolvers/solaris/networking.rb +++ b/lib/facter/resolvers/solaris/networking.rb @@ -110,7 +110,8 @@ def inet_ntop(lifreq, ss_family) buffer = ::FFI::MemoryPointer.new(:char, buffer_size) - FFI::Ioctl.inet_ntop(ss_family, ip.to_ptr, buffer.to_ptr, buffer.size) + str = FFI::Ioctl.inet_ntop(ss_family, ip.to_ptr, buffer.to_ptr, buffer.size) + str.dup.force_encoding(Encoding::UTF_8) end def get_ipv4(lifreq) diff --git a/lib/facter/resolvers/windows/networking.rb b/lib/facter/resolvers/windows/networking.rb index 6c2c5bfe65..7b0cb7df97 100644 --- a/lib/facter/resolvers/windows/networking.rb +++ b/lib/facter/resolvers/windows/networking.rb @@ -131,8 +131,8 @@ def find_bindings(sock_addr, unicast, addr) def find_primary_interface(sock_addr, name, addr) if !@fact_list[:primary_interface] && - ([NetworkingFFI::AF_INET, NetworkingFFI::AF_INET6].include?(sock_addr[:sa_family]) && - !::Facter::Util::Resolvers::Networking.ignored_ip_address(addr)) + [NetworkingFFI::AF_INET, NetworkingFFI::AF_INET6].include?(sock_addr[:sa_family]) && + !::Facter::Util::Resolvers::Networking.ignored_ip_address(addr) @fact_list[:primary_interface] = name.to_s end end diff --git a/lib/facter/resolvers/windows/system32.rb b/lib/facter/resolvers/windows/system32.rb index d627c37f53..620a9ecb25 100644 --- a/lib/facter/resolvers/windows/system32.rb +++ b/lib/facter/resolvers/windows/system32.rb @@ -15,7 +15,13 @@ def post_resolve(fact_name, _options) def retrieve_windows_binaries_path require_relative '../../../facter/resolvers/windows/ffi/system32_ffi' - windows_path = ENV['SystemRoot'] + # Ruby < 3 returned env key/values based on the active code page + # Ruby 3+ use UTF-8, see ruby/ruby@ca76337a00244635faa331afd04f4b75161ce6fb + windows_path = if RUBY_VERSION.to_f < 3.0 + ENV['SystemRoot']&.dup&.force_encoding(Encoding.default_external) + else + ENV['SystemRoot'] + end if !windows_path || windows_path.empty? @log.debug 'Unable to find correct value for SystemRoot enviroment variable' diff --git a/lib/facter/util/facts/posix/virtual_detector.rb b/lib/facter/util/facts/posix/virtual_detector.rb index cc4ba11b70..b5eadfab7b 100644 --- a/lib/facter/util/facts/posix/virtual_detector.rb +++ b/lib/facter/util/facts/posix/virtual_detector.rb @@ -16,7 +16,7 @@ def platform private def check_docker_lxc - Facter::Resolvers::Containers.resolve(:vm) + resolve(Facter::Resolvers::Containers, :vm) end def check_gce @@ -29,35 +29,35 @@ def check_illumos_lx end def check_vmware - Facter::Resolvers::Vmware.resolve(:vm) + resolve(Facter::Resolvers::Vmware, :vm) end def retrieve_from_virt_what - Facter::Resolvers::VirtWhat.resolve(:vm) + resolve(Facter::Resolvers::VirtWhat, :vm) end def check_open_vz - Facter::Resolvers::OpenVz.resolve(:vm) + resolve(Facter::Resolvers::OpenVz, :vm) end def check_vserver - Facter::Resolvers::VirtWhat.resolve(:vserver) + resolve(Facter::Resolvers::VirtWhat, :vserver) end def check_xen - Facter::Resolvers::Xen.resolve(:vm) + resolve(Facter::Resolvers::Xen, :vm) end def check_freebsd return unless Object.const_defined?('Facter::Resolvers::Freebsd::Virtual') - Facter::Resolvers::Freebsd::Virtual.resolve(:vm) + resolve(Facter::Resolvers::Freebsd::Virtual, :vm) end def check_openbsd return unless Object.const_defined?('Facter::Resolvers::Openbsd::Virtual') - Facter::Resolvers::Openbsd::Virtual.resolve(:vm) + resolve(Facter::Resolvers::Openbsd::Virtual, :vm) end def check_other_facts @@ -73,7 +73,13 @@ def check_other_facts end def check_lspci - Facter::Resolvers::Lspci.resolve(:vm) + resolve(Facter::Resolvers::Lspci, :vm) + end + + def resolve(resolver, fact_name) + value = resolver.resolve(fact_name) + resolver.log.debug("Resolved '#{fact_name}' to '#{value}'") + value end end end diff --git a/lib/facter/util/facts/windows_release_finder.rb b/lib/facter/util/facts/windows_release_finder.rb index de285fde6b..ac721a76fe 100644 --- a/lib/facter/util/facts/windows_release_finder.rb +++ b/lib/facter/util/facts/windows_release_finder.rb @@ -25,10 +25,12 @@ def find_release(input) def check_version_10_11(consumerrel, kernel_version) build_number = kernel_version[/([^.]*)$/].to_i - return '11' if build_number >= 22_000 + return '11' if consumerrel && build_number >= 22_000 return '10' if consumerrel - if build_number >= 20_348 + if build_number >= 26_100 + '2025' + elsif build_number >= 20_348 '2022' elsif build_number >= 17_623 '2019' diff --git a/lib/facter/util/linux/socket_parser.rb b/lib/facter/util/linux/socket_parser.rb index c7fd6b2ea5..442be40124 100644 --- a/lib/facter/util/linux/socket_parser.rb +++ b/lib/facter/util/linux/socket_parser.rb @@ -19,7 +19,7 @@ def retrieve_interfaces(logger) private def populate_interface_info(ifaddr) - interface_name = ifaddr.name + interface_name = ifaddr.name.dup.force_encoding(Encoding::UTF_8) @interfaces[interface_name] = {} if @interfaces[interface_name].nil? mac(ifaddr) @@ -80,7 +80,7 @@ def read_ip_link_show_data def mac_from(ifaddr) if Socket.const_defined? :PF_LINK - ifaddr.addr&.getnameinfo&.first # sometimes it returns localhost or ip + ifaddr.addr&.getnameinfo&.first&.dup&.force_encoding(Encoding::UTF_8) # sometimes it returns localhost or ip elsif Socket.const_defined? :PF_PACKET return if ifaddr.addr.nil? @@ -93,7 +93,7 @@ def mac_from(ifaddr) def mac_from_sockaddr_of(ifaddr) result = ifaddr.addr.inspect_sockaddr - result&.match(/hwaddr=([\h:]+)/)&.captures&.first + result&.match(/hwaddr=([\h:]+)/)&.captures&.first&.force_encoding(Encoding::UTF_8) end def ip_info_of(ifaddr) @@ -117,8 +117,8 @@ def add_binding(interface_name, ifaddr) def binding_data(ifaddr) # ipv6 ips are retrieved as % - ip = ifaddr.addr.ip_address.split('%').first - netmask = ifaddr.netmask.ip_address + ip = ifaddr.addr.ip_address.split('%').first.force_encoding(Encoding::UTF_8) + netmask = ifaddr.netmask.ip_address.dup.force_encoding(Encoding::UTF_8) binding_key = ifaddr.addr.ipv4? ? :bindings : :bindings6 [ip, netmask, binding_key] diff --git a/lib/facter/util/resolvers/ffi/hostname.rb b/lib/facter/util/resolvers/ffi/hostname.rb index 3a1a0b1804..55397c8891 100644 --- a/lib/facter/util/resolvers/ffi/hostname.rb +++ b/lib/facter/util/resolvers/ffi/hostname.rb @@ -33,7 +33,7 @@ def self.getffihostname res = Hostname.gethostname(raw_hostname, HOST_NAME_MAX) return unless res.zero? - raw_hostname.read_string + raw_hostname.read_string&.force_encoding(Encoding::UTF_8) end def self.getffiaddrinfo(hostname) @@ -54,7 +54,7 @@ def self.getffiaddrinfo(hostname) begin addr = Facter::Util::Resolvers::Ffi::AddrInfo.new(ret.read_pointer) - name = addr[:ai_canonname] + name = addr[:ai_canonname]&.dup&.force_encoding(Encoding::UTF_8) log.debug("FFI AddrInfo struct has the fqdn: #{name}") return if !name || hostname == name diff --git a/tasks/check.rake b/rakelib/check.rake similarity index 100% rename from tasks/check.rake rename to rakelib/check.rake diff --git a/tasks/fact_list_generator.rake b/rakelib/fact_list_generator.rake similarity index 100% rename from tasks/fact_list_generator.rake rename to rakelib/fact_list_generator.rake diff --git a/tasks/manpages.rake b/rakelib/manpages.rake similarity index 100% rename from tasks/manpages.rake rename to rakelib/manpages.rake diff --git a/tasks/rubocop.rake b/rakelib/rubocop.rake similarity index 100% rename from tasks/rubocop.rake rename to rakelib/rubocop.rake diff --git a/tasks/spec.rake b/rakelib/spec.rake similarity index 88% rename from tasks/spec.rake rename to rakelib/spec.rake index 3220e1aa17..278f08a78d 100644 --- a/tasks/spec.rake +++ b/rakelib/spec.rake @@ -14,7 +14,7 @@ begin desc 'Run rspec integration test in random order' RSpec::Core::RakeTask.new(:spec_integration) do |t| t.rspec_opts = '--pattern spec_integration/**/*_spec.rb '\ - '--default-path spec_integration --order random' + '--order random' end rescue LoadError puts 'Could not load rspec' diff --git a/spec/custom_facts/util/confine_spec.rb b/spec/custom_facts/util/confine_spec.rb index 24021eab88..1a903ff6d3 100755 --- a/spec/custom_facts/util/confine_spec.rb +++ b/spec/custom_facts/util/confine_spec.rb @@ -97,7 +97,7 @@ def confined(fact_value, *confines) end it "returns true if any of the provided ranges matches the fact's value" do - expect(confined(6, (5..7))).to be true + expect(confined(6, 5..7)).to be true end it "returns false if none of the provided values matches the fact's value" do @@ -105,7 +105,7 @@ def confined(fact_value, *confines) end it "returns false if none of the provided integer values matches the fact's value" do - expect(confined(2, 1, [3, 4], (5..7))).to be false + expect(confined(2, 1, [3, 4], 5..7)).to be false end it "returns false if none of the provided boolan values matches the fact's value" do @@ -117,7 +117,7 @@ def confined(fact_value, *confines) end it "returns false if none of the provided ranges matches the fact's value" do - expect(confined(8, (5..7))).to be false + expect(confined(8, 5..7)).to be false end it 'accepts and evaluate a block argument against the fact' do diff --git a/spec/custom_facts/util/normalization_spec.rb b/spec/custom_facts/util/normalization_spec.rb index 62cb4437c4..eaabd7eaaf 100644 --- a/spec/custom_facts/util/normalization_spec.rb +++ b/spec/custom_facts/util/normalization_spec.rb @@ -23,32 +23,49 @@ def utf8(str) describe 'and string encoding is supported', if: String.instance_methods.include?(:encoding) do it 'accepts strings that are ASCII and match their encoding and converts them to UTF-8' do str = 'ASCII'.encode(Encoding::ASCII) - normalized_str = normalization.normalize(str) + normalized_str = normalization.normalize('', str) expect(normalized_str.encoding).to eq(Encoding::UTF_8) end + it 'accepts ASCII 8-BIT (binary) string that can be successfully force encoded to UTF-8 and logs a debug message with the fact name and value and returns UTF-8 encoded version of the passed in string' do + str = 'test'.encode(Encoding::ASCII_8BIT) + logger = Facter::Log.new(self) + allow(normalization).to receive(:log).and_return logger # rubocop:disable RSpec/SubjectStub + expect(logger).to receive(:debug).with(/custom fact "" with value: #{str} contained binary data and was force encoded to UTF-8 and now has the value: #{str}/) + value = normalization.normalize('', str) + expect(value).to eq('test') + expect(value.encoding).to eq(Encoding::UTF_8) + end + + it 'errors when given binary string that cannot be successfully force encoded to UTF-8' do + str = "\xc0".dup.force_encoding(Encoding::ASCII_8BIT) # rubocop:disable Performance/UnfreezeString + expect do + normalization.normalize('', str) + end.to raise_error(LegacyFacter::Util::Normalization::NormalizationError) + end + it 'accepts strings that are UTF-8 and match their encoding' do str = "let's make a ☃!".encode(Encoding::UTF_8) - expect(normalization.normalize(str)).to eq(str) + expect(normalization.normalize('', str)).to eq(str) end it 'converts valid non UTF-8 strings to UTF-8' do str = "let's make a ☃!".encode(Encoding::UTF_16LE) - enc = normalization.normalize(str).encoding + enc = normalization.normalize('', str).encoding expect(enc).to eq(Encoding::UTF_8) end it 'normalizes a frozen string returning a non-frozen string' do str = 'factvalue'.encode(Encoding::UTF_16LE).freeze - normalized_str = normalization.normalize(str) + normalized_str = normalization.normalize('', str) expect(normalized_str).not_to be_frozen end it 'rejects strings that are not UTF-8 and do not match their claimed encoding' do invalid_shift_jis = (+"\xFF\x5C!").force_encoding(Encoding::SHIFT_JIS) expect do - normalization.normalize(invalid_shift_jis) + normalization.normalize('', invalid_shift_jis) end.to raise_error(LegacyFacter::Util::Normalization::NormalizationError, /String encoding Shift_JIS is not UTF-8 and could not be converted to UTF-8/) end @@ -56,7 +73,7 @@ def utf8(str) it "rejects strings that claim to be UTF-8 encoded but aren't" do str = (+"\255ay!").force_encoding(Encoding::UTF_8) expect do - normalization.normalize(str) + normalization.normalize('', str) end.to raise_error(LegacyFacter::Util::Normalization::NormalizationError, /String "\\xADay!" doesn't match the reported encoding UTF-8/) end @@ -64,7 +81,7 @@ def utf8(str) it 'rejects strings that only have the start of a valid UTF-8 sequence' do str = "\xc3\x28" expect do - normalization.normalize(str) + normalization.normalize('', str) end.to raise_error(LegacyFacter::Util::Normalization::NormalizationError, /String "\\xC3\(" doesn't match the reported encoding UTF-8/) end @@ -73,7 +90,7 @@ def utf8(str) str = 'Mitteleuropäische Zeit'.encode(Encoding::UTF_16LE) str.force_encoding(Encoding::UTF_8) expect do - normalization.normalize(str) + normalization.normalize('', str) end.to raise_error(LegacyFacter::Util::Normalization::NormalizationError, /String.*doesn't match the reported encoding UTF-8/) end @@ -85,7 +102,7 @@ def utf8(str) "null byte \x00 in the middle"] test_strings.each do |str| expect do - normalization.normalize(str) + normalization.normalize('', str) end.to raise_error(LegacyFacter::Util::Normalization::NormalizationError, /String.*contains a null byte reference/) end @@ -95,13 +112,13 @@ def utf8(str) describe 'and string encoding is not supported', unless: String.instance_methods.include?(:encoding) do it 'accepts strings that are UTF-8 and match their encoding' do str = "let's make a ☃!" - expect(normalization.normalize(str)).to eq(str) + expect(normalization.normalize('', str)).to eq(str) end it 'rejects strings that are not UTF-8' do str = "let's make a \255\255\255!" expect do - normalization.normalize(str) + normalization.normalize('', str) end.to raise_error(LegacyFacter::Util::Normalization::NormalizationError, /String .* is not valid UTF-8/) end end @@ -112,7 +129,7 @@ def utf8(str) arr = [utf16('first'), utf16('second'), [utf16('third'), utf16('fourth')]] expected_arr = [utf8('first'), utf8('second'), [utf8('third'), utf8('fourth')]] - expect(normalization.normalize_array(arr)).to eq(expected_arr) + expect(normalization.normalize_array('', arr)).to eq(expected_arr) end end @@ -121,7 +138,7 @@ def utf8(str) hsh = { utf16('first') => utf16('second'), utf16('third') => [utf16('fourth'), utf16('fifth')] } expected_hsh = { utf8('first') => utf8('second'), utf8('third') => [utf8('fourth'), utf8('fifth')] } - expect(normalization.normalize_hash(hsh)).to eq(expected_hsh) + expect(normalization.normalize_hash('', hsh)).to eq(expected_hsh) end end @@ -131,7 +148,7 @@ def utf8(str) value = Time.utc(2020) expected = '2020-01-01T00:00:00Z' - expect(normalization.normalize(value)).to eq(expected) + expect(normalization.normalize('', value)).to eq(expected) end end @@ -140,20 +157,20 @@ def utf8(str) value = Date.new(2020) expected = '2020-01-01' - expect(normalization.normalize(value)).to eq(expected) + expect(normalization.normalize('', value)).to eq(expected) end end [1, 1.0, true, false, nil, :symbol].each do |val| it "accepts #{val.inspect}:#{val.class}" do - expect(normalization.normalize(val)).to eq(val) + expect(normalization.normalize('', val)).to eq(val) end end [Object.new, Set.new].each do |val| it "rejects #{val.inspect}:#{val.class}" do expect do - normalization.normalize(val) + normalization.normalize('', val) end.to raise_error(LegacyFacter::Util::Normalization::NormalizationError, /Expected .*but was #{val.class}/) end end diff --git a/spec/facter/facts/linux/cloud/provider_spec.rb b/spec/facter/facts/linux/cloud/provider_spec.rb index 0e32fbb757..7bdfc7e06b 100644 --- a/spec/facter/facts/linux/cloud/provider_spec.rb +++ b/spec/facter/facts/linux/cloud/provider_spec.rb @@ -46,15 +46,54 @@ allow(Facter::Util::Facts::Posix::VirtualDetector).to receive(:platform).and_return('kvm') end - describe 'Ec2 data exists and aws fact is set' do + describe 'Ec2 data exists and not running as root' do let(:value) { { 'some' => 'fact' } } - it 'Testing things' do + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(512) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ have_attributes(name: 'cloud.provider', value: 'aws') end end + describe 'Ec2 data exists and virt-what is not executable' do + let(:value) { { 'some' => 'fact' } } + + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(false) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: 'aws') + end + end + + describe 'Ec2 data exists and virt-what returns aws' do + let(:value) { { 'some' => 'fact' } } + + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('aws') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: 'aws') + end + end + + describe 'Ec2 data exists and virt-what returns not aws' do + let(:value) { { 'some' => 'fact' } } + + it 'returns nil' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: nil) + end + end + context 'when Ec2 data does not exist nil is returned' do let(:value) { {} } @@ -71,15 +110,54 @@ allow(Facter::Util::Facts::Posix::VirtualDetector).to receive(:platform).and_return('xen') end - describe 'Ec2 data exists and aws fact is set' do + describe 'Ec2 data exists and not running as root' do + let(:value) { { 'some' => 'fact' } } + + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(512) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: 'aws') + end + end + + describe 'Ec2 data exists and virt-what is not executable' do + let(:value) { { 'some' => 'fact' } } + + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(false) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: 'aws') + end + end + + describe 'Ec2 data exists and virt-what returns aws' do let(:value) { { 'some' => 'fact' } } - it 'Testing things' do + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('aws') expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ have_attributes(name: 'cloud.provider', value: 'aws') end end + describe 'Ec2 data exists and virt-what returns not aws' do + let(:value) { { 'some' => 'fact' } } + + it 'returns nil' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: nil) + end + end + context 'when Ec2 data does not exist nil is returned' do let(:value) { {} } @@ -96,15 +174,54 @@ allow(Facter::Util::Facts::Posix::VirtualDetector).to receive(:platform).and_return('xenhvm') end - describe 'Ec2 data exists and aws fact is set' do + describe 'Ec2 data exists and not running as root' do + let(:value) { { 'some' => 'fact' } } + + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(512) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: 'aws') + end + end + + describe 'Ec2 data exists and virt-what is not executable' do + let(:value) { { 'some' => 'fact' } } + + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(false) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: 'aws') + end + end + + describe 'Ec2 data exists and virt-what returns aws' do let(:value) { { 'some' => 'fact' } } - it 'Testing things' do + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('aws') expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ have_attributes(name: 'cloud.provider', value: 'aws') end end + describe 'Ec2 data exists and virt-what returns not aws' do + let(:value) { { 'some' => 'fact' } } + + it 'returns nil' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: nil) + end + end + context 'when Ec2 data does not exist nil is returned' do let(:value) { {} } @@ -115,6 +232,70 @@ end end + describe 'when on xenu' do + before do + allow(Facter::Resolvers::Ec2).to receive(:resolve).with(:metadata).and_return(value) + allow(Facter::Util::Facts::Posix::VirtualDetector).to receive(:platform).and_return('xenu') + end + + describe 'Ec2 data exists and not running as root' do + let(:value) { { 'some' => 'fact' } } + + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(512) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: 'aws') + end + end + + describe 'Ec2 data exists and virt-what is not executable' do + let(:value) { { 'some' => 'fact' } } + + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(false) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: 'aws') + end + end + + describe 'Ec2 data exists and virt-what returns aws' do + let(:value) { { 'some' => 'fact' } } + + it 'returns aws' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('aws') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: 'aws') + end + end + + describe 'Ec2 data exists and virt-what returns not aws' do + let(:value) { { 'some' => 'fact' } } + + it 'returns nil' do + allow(File).to receive(:executable?).with('/opt/puppetlabs/puppet/bin/virt-what').and_return(true) + allow(Process).to receive(:uid).and_return(0) + allow(Facter::Core::Execution).to receive(:execute).with('/opt/puppetlabs/puppet/bin/virt-what').and_return('kvm') + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: nil) + end + end + + context 'when Ec2 data does not exist, nil is returned' do + let(:value) { {} } + + it 'returns nil' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'cloud.provider', value: nil) + end + end + end + describe 'when on gce' do before do allow(Facter::Resolvers::Gce).to receive(:resolve).with(:metadata).and_return(value) diff --git a/spec/facter/resolvers/containers_spec.rb b/spec/facter/resolvers/containers_spec.rb index 64348c3794..afa7507ddf 100644 --- a/spec/facter/resolvers/containers_spec.rb +++ b/spec/facter/resolvers/containers_spec.rb @@ -10,12 +10,31 @@ allow(Facter::Util::FileHelper).to receive(:safe_readlines) .with('/proc/1/environ', [], "\0", chomp: true) .and_return(environ_output) + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with('/.dockerenv').and_return(false) end after do containers_resolver.invalidate_cache end + context 'when hypervisor is docker desktop' do + let(:cgroup_output) { '0::/' } + let(:environ_output) { ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'] } + + before do + allow(File).to receive(:exist?).with('/.dockerenv').and_return(true) + end + + it 'returns docker for vm' do + expect(containers_resolver.resolve(:vm)).to eq('docker') + end + + it 'returns empty hypervisor info' do + expect(containers_resolver.resolve(:hypervisor)).to eq(docker: {}) + end + end + context 'when hypervisor is docker' do let(:cgroup_output) { load_fixture('docker_cgroup').read } let(:environ_output) { ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'] } @@ -48,6 +67,14 @@ it 'return nspawn info for hypervisor' do expect(containers_resolver.resolve(:hypervisor)).to eq(result) end + + it 'omits hypervisor info if it fails to read /etc/machine-id' do + allow(Facter::Util::FileHelper).to receive(:safe_read) + .with('/etc/machine-id', nil) + .and_return(nil) + + expect(containers_resolver.resolve(:hypervisor)).to eq(systemd_nspawn: {}) + end end context 'when hypervisor is lxc and it is discovered by cgroup' do @@ -119,11 +146,11 @@ context 'when hypervisor is an unknown container runtime discovered by environ' do let(:cgroup_output) { load_fixture('cgroup_file').read } let(:environ_output) { ['container=UNKNOWN'] } - let(:logger) { Facter::Log.class_variable_get(:@@logger) } + let(:logger) { containers_resolver.log } it 'return container_other for vm' do - expect(logger).to receive(:warn).with(/Container runtime, 'UNKNOWN', is unsupported, setting to 'container_other'/) - expect(containers_resolver.resolve(:vm)).to eq('container_other') + expect(logger).to receive(:debug).with(/Container runtime 'UNKNOWN' is not recognized, ignoring/) + expect(containers_resolver.resolve(:vm)).to eq(nil) end end end diff --git a/spec/facter/util/facts/windows_release_finder_spec.rb b/spec/facter/util/facts/windows_release_finder_spec.rb index a6a81d4d22..4740bcde08 100644 --- a/spec/facter/util/facts/windows_release_finder_spec.rb +++ b/spec/facter/util/facts/windows_release_finder_spec.rb @@ -36,6 +36,17 @@ end end + describe '#find windows release when version is 2025' do + let(:cons) { false } + let(:desc) {} + let(:k_version) { '10.0.26100' } + let(:version) { '10.0' } + + it 'returns 2025' do + expect(Facter::Util::Facts::WindowsReleaseFinder.find_release(input)).to eql('2025') + end + end + describe '#find windows release when version is 2022' do let(:cons) { false } let(:desc) {} diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9ba3469616..3e63323dac 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,62 +1,15 @@ # frozen_string_literal: true require 'pathname' - -ROOT_DIR = Pathname.new(File.expand_path('..', __dir__)) unless defined?(ROOT_DIR) - -ENV['RACK_ENV'] = 'test' - -require 'bundler/setup' - -require 'simplecov' - -# Configure SimpleCov -SimpleCov.start do - track_files 'lib/**/*.rb' - add_filter 'spec' -end - -require 'open3' -require 'thor' require 'fileutils' - -require_relative '../lib/facter/resolvers/base_resolver' - -unit_tests = ARGV.grep(/spec_integration/).empty? -Dir[ROOT_DIR.join('spec/mocks/*.rb')].sort.each { |file| require file } if unit_tests - -require_relative 'custom_facts/puppetlabs_spec/files' - -require 'facter' -require 'facter/framework/cli/cli' -require 'facter/framework/cli/cli_launcher' - -if unit_tests - Dir.glob(File.join('./lib/facter/util', '/**/*/', '*.rb')).sort.each(&method(:require)) - Dir.glob(File.join('./lib/facter/facts', '/**/*/', '*.rb')).sort.each(&method(:require)) - Dir.glob(File.join('./lib/facter/resolvers', '/**/*/', '*.rb')).sort.each(&method(:require)) -end - -default_coverage = 90 -SimpleCov.minimum_coverage ENV['COVERAGE'] || default_coverage - -def colorize(str, color) - "#{color}#{str}#{Facter::RESET}" -end - -# Configure webmock -require 'webmock/rspec' -WebMock.disable_net_connect! - # Configure test logger +require 'logger' +require 'stringio' logdest = ENV['FACTER_TEST_LOG'] ? File.new(ENV['FACTER_TEST_LOG'], 'w') : StringIO.new logger = Logger.new(logdest) # Configure RSpec RSpec.configure do |config| - # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = '.rspec_status' - # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! config.expose_dsl_globally = true @@ -87,20 +40,89 @@ def colorize(str, color) config.before do |test| m = test.metadata logger.info("*** BEGIN TEST #{m[:file_path]}:#{m[:line_number]}") - - LegacyFacter.clear - Facter.clear_messages - end - - config.after do - Facter::OptionStore.reset - Facter::ConfigReader.clear - Facter::ConfigFileOptions.clear end # This will cleanup any files that were created with tmpdir or tmpfile + require_relative 'custom_facts/puppetlabs_spec/files' config.extend PuppetlabsSpec::Files config.after do PuppetlabsSpec::Files.cleanup end end + +# Configure unit vs integration +ROOT_DIR = Pathname.new(File.expand_path('..', __dir__)) unless defined?(ROOT_DIR) +SPEC_DIR = File.join(ROOT_DIR, 'spec') +INTEGRATION_DIR = File.join(ROOT_DIR, 'spec_integration') + +unit_tests = ARGV.grep(/spec_integration/).empty? +if unit_tests + # Normally facter lazily loads facts and resolvers for the specific platform + # it's running on, because some classes depend on libraries that can't be + # required on the host running test, like WIN32OLE. So first, mock out + # platform-specific classes + Dir[ROOT_DIR.join('spec/mocks/*.rb')].sort.each { |file| require file } + + # Second, require facter + require 'facter' + + # Third, eagerly load all facts and resolvers + Dir.glob(File.join('./lib/facter/util', '/**/*/', '*.rb')).sort.each(&method(:require)) + Dir.glob(File.join('./lib/facter/facts', '/**/*/', '*.rb')).sort.each(&method(:require)) + Dir.glob(File.join('./lib/facter/resolvers', '/**/*/', '*.rb')).sort.each(&method(:require)) + + # Configure webmock + require 'webmock/rspec' + WebMock.disable_net_connect! + + # Unit specific config + RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = '.rspec_status' + + config.before do + LegacyFacter.clear + Facter.clear_messages + end + + config.after do + Facter::OptionStore.reset + Facter::ConfigReader.clear + Facter::ConfigFileOptions.clear + Facter.instance_variable_set(:@logger, nil) + Facter::FactLoader.instance.instance_variable_set(:@logger, nil) + Facter::FactManager.instance.instance_variable_set(:@logger, nil) + end + end + + def colorize(str, color) + "#{color}#{str}#{Facter::RESET}" + end +else + require 'facter' + + $LOAD_PATH << INTEGRATION_DIR + require 'integration_helper' + + # prevent facter from loading its spec files as facts + $LOAD_PATH.delete_if { |entry| entry == SPEC_DIR } + + # Integration specific config + RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = '.rspec_integration_status' + + # exclude `skip_outside_ci` tests if not running on CI + config.filter_run_excluding :skip_outside_ci unless ENV['CI'] + + config.after do + Facter::OptionStore.reset + Facter::ConfigReader.clear + Facter::ConfigFileOptions.clear + + Facter.reset + Facter.clear + LegacyFacter.clear + end + end +end diff --git a/spec_integration/facter_block_legacy_spec.rb b/spec_integration/facter_block_legacy_spec.rb index 274693edbd..2fa14cfe50 100644 --- a/spec_integration/facter_block_legacy_spec.rb +++ b/spec_integration/facter_block_legacy_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'json' -require_relative 'spec_helper' describe 'Facter' do context 'when legacy facts is blocked' do diff --git a/spec_integration/facter_resolve_spec.rb b/spec_integration/facter_resolve_spec.rb index 195ed69b8e..8ec8e42ff2 100644 --- a/spec_integration/facter_resolve_spec.rb +++ b/spec_integration/facter_resolve_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'spec_helper' +require 'tmpdir' describe Facter do context 'when recursively calling facter in an external/custom fact' do diff --git a/spec_integration/facter_spec.rb b/spec_integration/facter_spec.rb index e6cf8b1b0f..d8af9fc403 100644 --- a/spec_integration/facter_spec.rb +++ b/spec_integration/facter_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'spec_helper' - describe 'Facter' do include PuppetlabsSpec::Files diff --git a/spec_integration/facter_to_hash_spec.rb b/spec_integration/facter_to_hash_spec.rb index 261f932196..8aa363d1c9 100644 --- a/spec_integration/facter_to_hash_spec.rb +++ b/spec_integration/facter_to_hash_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'spec_helper' - describe Facter do context 'when calling facter cli' do context 'with no user query' do diff --git a/spec_integration/spec_helper.rb b/spec_integration/spec_helper.rb deleted file mode 100644 index 516663da39..0000000000 --- a/spec_integration/spec_helper.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -require 'facter' -require_relative 'integration_helper' -require_relative '../spec/custom_facts/puppetlabs_spec/files' - -# prevent facter from loading its spec files as facts -$LOAD_PATH.delete_if { |entry| entry =~ %r{facter/spec} } - -# Configure RSpec -RSpec.configure do |config| - # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = '.rspec_status' - - # exclude `skip_outside_ci` tests if not running on CI - config.filter_run_excluding :skip_outside_ci unless ENV['CI'] - - # Disable RSpec exposing methods globally on `Module` and `main` - config.disable_monkey_patching! - config.expose_dsl_globally = true - - config.expect_with :rspec do |c| - c.syntax = :expect - end - - config.mock_with :rspec do |mocks| - # This option should be set when all dependencies are being loaded - # before a spec run, as is the case in a typical spec helper. It will - # cause any verifying double instantiation for a class that does not - # exist to raise, protecting against incorrectly spelt names. - mocks.verify_doubled_constant_names = true - - # This option forces the same argument and method existence checks that are - # performed for object_double are also performed on partial doubles. - # You should set this unless you have a good reason not to. - # It defaults to off only for backwards compatibility. - - mocks.verify_partial_doubles = true - end - - config.after do - Facter.reset - Facter.clear - Facter::OptionStore.reset - LegacyFacter.clear - end - - # This will cleanup any files that were created with tmpdir or tmpfile - config.extend PuppetlabsSpec::Files - config.after(:all) do - PuppetlabsSpec::Files.cleanup - end -end