Skip to content

Commit

Permalink
Do not query for the exact facter version
Browse files Browse the repository at this point in the history
This assumes that no exact match can be found in most cases. This saves
a call to FacterDB. Some testing on puppet-nginx this reduced the time
for a dry run by about 1 to 2 seconds, from about ~11 to ~9 seconds.

Implementation wise this switches to using Gem::Requirement to detect
matching versions. This means you can also specify `~> 3.8` as a valid
facterversion, or `>= 3 < 5`.
  • Loading branch information
ekohl committed Jul 5, 2023
1 parent 0921dff commit b60270c
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 50 deletions.
45 changes: 25 additions & 20 deletions lib/rspec-puppet-facts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,32 +113,31 @@ def on_supported_os_implementation(opts = {})
end
end

facterversion_obj = Gem::Version.new(facterversion)
# TODO: what if facterversion is a complex expression
exact_match = Gem::Version.new(facterversion)
facter_requirement = Gem::Requirement.new(RspecPuppetFacts::facter_version_to_requirement(facterversion))

# FacterDB may have newer versions of facter data for which it contains a subset of all possible
# facter data (see FacterDB 0.5.2 for Facter releases 3.8 and 3.9). In this situation we need to
# cycle through and downgrade Facter versions per platform type until we find matching Facter data.
filter.each do |filter_spec|
facter_version_filter = RspecPuppetFacts.facter_version_to_filter(facterversion)
db = FacterDB.get_facts(filter_spec.merge({ :facterversion => facter_version_filter }))
versions = FacterDB.get_facts(filter_spec).map { |facts| Gem::Version.new(facts[:facterversion]) }.sort.reverse
next unless versions.any?

if db.empty?
version = versions.include?(exact_match) ? exact_match : versions.detect { |v| facter_requirement =~ v }

next unless version
version = version.to_s

unless version == facterversion
if RspecPuppetFacts.spec_facts_strict?
raise ArgumentError, "No facts were found in the FacterDB for Facter v#{facterversion} on #{filter_spec}, aborting"
end

version = FacterDB.get_facts(filter_spec).map { |facts| Gem::Version.new(facts[:facterversion]) }.sort.reverse.detect { |v| v <= facterversion_obj }

next unless version
version = version.to_s
facter_version_filter = "/\\A#{Regexp.escape(version)}/"

unless version == facterversion
RspecPuppetFacts.warning "No facts were found in the FacterDB for Facter v#{facterversion} on #{filter_spec}, using v#{version} instead"
end
RspecPuppetFacts.warning "No facts were found in the FacterDB for Facter v#{facterversion} on #{filter_spec}, using v#{version} instead"
end

filter_spec[:facterversion] = facter_version_filter
filter_spec[:facterversion] = "/\\A#{Regexp.escape(version)}\\Z/"
end

received_facts = FacterDB::get_facts(filter)
Expand Down Expand Up @@ -353,13 +352,19 @@ def self.reset
@metadata = nil
end

# Generates a JGrep statement expression for a specific facter version
# @return [String] JGrep statement expression
# @param version [String] the Facter version
# Construct the facter version requirement
# @return [String] The version requirement to match
# @api private
def self.facter_version_to_filter(version)
major, minor = version.split('.')
"/\\A#{major}\\.#{minor}\\./"
def self.facter_version_to_requirement(version)
if (m = /\A(?<major>[0-9]+)\.(?<minor>[0-9]+)(?:\.(?<patch>[0-9]+))?\Z/.match(version))
# Interpret 3.1 as < 3.2 and 3.2.1 as < 3.3
"< #{m[:major]}.#{m[:minor].to_i + 1}"
elsif /\A[0-9]+\Z/.match?(version)
# Interpret 3 as < 4
"< #{version.to_i + 1}"
else
version
end
end

def self.facter_version_for_puppet_version(puppet_version)
Expand Down
99 changes: 69 additions & 30 deletions spec/rspec_puppet_facts_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@
end

before do
RSpec.configuration.default_facter_version = '3.1.0'
RSpec.configuration.default_facter_version = '3.1.6'
end

after do
Expand Down Expand Up @@ -721,11 +721,10 @@
end


it 'returns facts from a facter version matching future and below' do
major, minor = Facter.version.split('.')
it 'returns facts from a facter version matching version and below' do
is_expected.to match(
'centos-7-x86_64' => include(
:facterversion => /\A#{major}\.[#{minor}#{minor.to_i + 1}]\./,
:facterversion => /\A2\.[0-6]\./,
),
)
end
Expand Down Expand Up @@ -862,7 +861,7 @@

it 'returns CentOS facts from a facter version matching 3.8' do
is_expected.to include(
'centos-7-x86_64' => include(:facterversion => '3.8.0'),
'centos-7-x86_64' => include(:facterversion => '3.9.6'),
)
end

Expand Down Expand Up @@ -976,68 +975,108 @@ module MCollectiveStub # rubocop:todo Lint/ConstantDefinitionInBlock
end
end

describe '.facter_version_to_filter' do
describe '.facter_version_to_requirement' do
context 'when passed a version that is a complex requirement (1)' do
subject { described_class.facter_version_to_requirement('~> 2.4') }

it 'returns the correct expression' do
is_expected.to eq('~> 2.4')
end
end

context 'when passed a version that is a complex requirement (2)' do
subject { described_class.facter_version_to_requirement('>= 3 < 5') }

it 'returns the correct expression' do
is_expected.to eq('>= 3 < 5')
end
end

context 'when passed a version that is major (1)' do
subject { described_class.facter_version_to_requirement('1') }

it 'returns the correct expression' do
is_expected.to eq('< 2')
end
end

context 'when passed a version that is major (2)' do
subject { described_class.facter_version_to_requirement('9') }

it 'returns the correct expression' do
is_expected.to eq('< 10')
end
end

context 'when passed a version that is major (3)' do
subject { described_class.facter_version_to_requirement('10') }

it 'returns the correct expression' do
is_expected.to eq('< 11')
end
end

context 'when passed a version that is major.minor (1)' do
subject { described_class.facter_version_to_filter('1.2') }
subject { described_class.facter_version_to_requirement('1.2') }

it 'returns the correct JGrep statement expression' do
is_expected.to eq('/\A1\.2\./')
it 'returns the correct expression' do
is_expected.to eq('< 1.3')
end
end

context 'when passed a version that is major.minor (2)' do
subject { described_class.facter_version_to_filter('10.2') }
subject { described_class.facter_version_to_requirement('10.2') }

it 'returns the correct JGrep statement expression' do
is_expected.to eq('/\A10\.2\./')
it 'returns the correct expression' do
is_expected.to eq('< 10.3')
end
end

context 'when passed a version that is major.minor (3)' do
subject { described_class.facter_version_to_filter('1.20') }
subject { described_class.facter_version_to_requirement('1.20') }

it 'returns the correct JGrep statement expression' do
is_expected.to eq('/\A1\.20\./')
it 'returns the correct expression' do
is_expected.to eq('< 1.21')
end
end

context 'when passed a version that is major.minor (4)' do
subject { described_class.facter_version_to_filter('10.20') }
subject { described_class.facter_version_to_requirement('10.20') }

it 'returns the correct JGrep statement expression' do
is_expected.to eq('/\A10\.20\./')
it 'returns the correct expression' do
is_expected.to eq('< 10.21')
end
end

context 'when passed a version that is major.minor.patch (1)' do
subject { described_class.facter_version_to_filter('1.2.3') }
subject { described_class.facter_version_to_requirement('1.2.3') }

it 'returns the correct JGrep statement expression' do
is_expected.to eq('/\A1\.2\./')
it 'returns the correct expression' do
is_expected.to eq('< 1.3')
end
end

context 'when passed a version that is major.minor.patch (2)' do
subject { described_class.facter_version_to_filter('10.2.3') }
subject { described_class.facter_version_to_requirement('10.2.3') }

it 'returns the correct JGrep statement expression' do
is_expected.to eq('/\A10\.2\./')
it 'returns the correct expression' do
is_expected.to eq('< 10.3')
end
end

context 'when passed a version that is major.minor.patch (3)' do
subject { described_class.facter_version_to_filter('1.20.3') }
subject { described_class.facter_version_to_requirement('1.20.3') }

it 'returns the correct JGrep statement expression' do
is_expected.to eq('/\A1\.20\./')
it 'returns the correct expression' do
is_expected.to eq('< 1.21')
end
end

context 'when passed a version that is major.minor.patch (4)' do
subject { described_class.facter_version_to_filter('10.20.3') }
subject { described_class.facter_version_to_requirement('10.20.3') }

it 'returns the correct JGrep statement expression' do
is_expected.to eq('/\A10\.20\./')
it 'returns the correct expression' do
is_expected.to eq('< 10.21')
end
end
end
Expand Down

0 comments on commit b60270c

Please sign in to comment.