diff --git a/lib/custom_facts/core/execution.rb b/lib/custom_facts/core/execution.rb index ef8875e4a..1ba220f93 100644 --- a/lib/custom_facts/core/execution.rb +++ b/lib/custom_facts/core/execution.rb @@ -51,7 +51,14 @@ def which(bin) # @param path [String] the path to check # @param platform [:posix,:windows,nil] the platform logic to use def absolute_path?(path, platform = nil) - @@impl.absolute_path?(path, platform) + case platform + when :posix + Facter::Core::Execution::Posix.new.absolute_path?(path) + when :windows + Facter::Core::Execution::Windows.new.absolute_path?(path) + else + @@impl.absolute_path?(path) + end end # Given a command line, this returns the command line with the diff --git a/lib/facter.rb b/lib/facter.rb index 8cbcf5818..56e7e73a9 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -13,13 +13,12 @@ class ResolveCustomFactError < StandardError; end Options.init Log.add_legacy_logger(STDOUT) - @logger = Log.new(self) @already_searched = {} @trace = false class << self def clear_messages - @logger.debug('clear_messages is not implemented') + logger.debug('clear_messages is not implemented') end # Alias method for Facter.fact() @@ -80,7 +79,7 @@ def core_value(user_query) def debug(msg) return unless debugging? - @logger.debug(msg) + logger.debug(msg) nil end @@ -239,9 +238,8 @@ def version # # @api private def to_user_output(cli_options, *args) - cli_options = cli_options.map { |(k, v)| [k.to_sym, v] }.to_h - Facter::Options.init_from_cli(cli_options, args) - @logger.info("executed with command line: #{ARGV.drop(1).join(' ')}") + init_cli_options(cli_options) + logger.info("executed with command line: #{ARGV.drop(1).join(' ')}") log_blocked_facts resolved_facts = Facter::FactManager.instance.resolve_facts(args) @@ -265,11 +263,20 @@ def log_exception(exception, message = :default) arr.concat(exception.backtrace) end - @logger.error(arr.flatten.join("\n")) + logger.error(arr.flatten.join("\n")) end private + def logger + @logger ||= Log.new(self) + end + + def init_cli_options(options) + options = options.map { |(k, v)| [k.to_sym, v] }.to_h + Facter::Options.init_from_cli(options, args) + end + def add_fact_to_searched_facts(user_query, value) @already_searched[user_query] ||= ResolvedFact.new(user_query, value) @already_searched[user_query].value = value @@ -327,7 +334,7 @@ def log_blocked_facts block_list = Facter::FactGroups.new(Facter::Options[:config]).block_list return unless block_list.any? && Facter::Options[:block] - @logger.debug("blocking collection of #{block_list.join("\s")} facts") + logger.debug("blocking collection of #{block_list.join("\s")} facts") end # Used for printing errors regarding CLI user input validation @@ -340,7 +347,7 @@ def log_blocked_facts # @api private def log_errors(missing_names) missing_names.each do |missing_name| - @logger.error("fact \"#{missing_name}\" does not exist.", true) + logger.error("fact \"#{missing_name}\" does not exist.", true) end end @@ -354,7 +361,7 @@ def log_errors(missing_names) # # @api private def method_missing(name, *args, &block) - @logger.error( + logger.error( "--#{name}-- not implemented but required \n" \ 'with params: ' \ "#{args.inspect} \n" \ diff --git a/lib/facts/freebsd/os/release.rb b/lib/facts/freebsd/os/release.rb new file mode 100644 index 000000000..714f00626 --- /dev/null +++ b/lib/facts/freebsd/os/release.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Facts + module Freebsd + module Os + class Release + FACT_NAME = 'os.release' + ALIASES = %w[operatingsystemmajrelease operatingsystemrelease].freeze + + def call_the_resolver + installed_userland = Facter::Resolvers::Freebsd::FreebsdVersion.resolve(:installed_userland) + + return Facter::ResolvedFact.new(FACT_NAME, nil) if !installed_userland || installed_userland.empty? + + value = build_release_hash_from_version(installed_userland) + + [Facter::ResolvedFact.new(FACT_NAME, value), + Facter::ResolvedFact.new(ALIASES.first, value[:major], :legacy), + Facter::ResolvedFact.new(ALIASES.last, installed_userland, :legacy)] + end + + private + + def build_release_hash_from_version(version_string) + version, branch_value = version_string.split('-', 2) + major_value, minor_value = version.split('.') + patchlevel_value = branch_value.split('-p')[1] + + value = { + full: version_string, + major: major_value, + branch: branch_value + } + + value[:minor] = minor_value if minor_value + value[:patchlevel] = patchlevel_value if patchlevel_value + + value + end + end + end + end +end diff --git a/lib/framework/core/fact_loaders/internal_fact_loader.rb b/lib/framework/core/fact_loaders/internal_fact_loader.rb index 45556ce6e..21f804fd2 100644 --- a/lib/framework/core/fact_loaders/internal_fact_loader.rb +++ b/lib/framework/core/fact_loaders/internal_fact_loader.rb @@ -12,10 +12,10 @@ def legacy_facts @facts.select { |fact| fact.type == :legacy } end - def initialize + def initialize(os_descendents = nil) @facts = [] - os_descendents = OsDetector.instance.hierarchy + os_descendents ||= OsDetector.instance.hierarchy load_all_oses_in_descending_order(os_descendents) end diff --git a/lib/framework/detector/os_detector.rb b/lib/framework/detector/os_detector.rb index 01a5baab5..391997998 100644 --- a/lib/framework/detector/os_detector.rb +++ b/lib/framework/detector/os_detector.rb @@ -24,6 +24,8 @@ def detect :macosx when /linux/ detect_distro + when /freebsd/ + :freebsd when /bsd/ :bsd when /solaris/ diff --git a/lib/resolvers/freebsd/freebsd_version_resolver.rb b/lib/resolvers/freebsd/freebsd_version_resolver.rb new file mode 100644 index 000000000..a9e891bb7 --- /dev/null +++ b/lib/resolvers/freebsd/freebsd_version_resolver.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Facter + module Resolvers + module Freebsd + class FreebsdVersion < BaseResolver + @semaphore = Mutex.new + @fact_list ||= {} + + class << self + private + + def post_resolve(fact_name) + @fact_list.fetch(fact_name) { freebsd_version_system_call(fact_name) } + end + + def freebsd_version_system_call(fact_name) + output, _stderr, status = Open3.capture3('/bin/freebsd-version -kru') + return nil unless status.success? + + build_fact_list(output) + + @fact_list[fact_name] + rescue Errno::ENOENT + nil + end + + def build_fact_list(output) + freebsd_version_results = output.split("\n") + + @fact_list[:installed_kernel] = freebsd_version_results[0].strip + @fact_list[:running_kernel] = freebsd_version_results[1].strip + @fact_list[:installed_userland] = freebsd_version_results[2].strip + end + end + end + end + end +end diff --git a/os_hierarchy.json b/os_hierarchy.json index c28010203..bc2300fe6 100644 --- a/os_hierarchy.json +++ b/os_hierarchy.json @@ -24,7 +24,11 @@ }, { "Solaris": [ - "Bsd" + { + "Bsd": [ + "Freebsd" + ] + } ] }, "Macosx", diff --git a/spec/custom_facts/core/aggregate_spec.rb b/spec/custom_facts/core/aggregate_spec.rb index a0364b27c..509ac6d99 100644 --- a/spec/custom_facts/core/aggregate_spec.rb +++ b/spec/custom_facts/core/aggregate_spec.rb @@ -38,7 +38,7 @@ end it 'fact_type does not raise error' do - expect { aggregate_res.options(fact_type: 'custom') }.not_to raise_error(ArgumentError) + expect { aggregate_res.options(fact_type: 'custom') }.not_to raise_error end end diff --git a/spec/custom_facts/core/execution_spec.rb b/spec/custom_facts/core/execution_spec.rb index 91dbe4543..7ebf7821c 100644 --- a/spec/custom_facts/core/execution_spec.rb +++ b/spec/custom_facts/core/execution_spec.rb @@ -5,8 +5,13 @@ describe Facter::Core::Execution do subject(:execution) { Facter::Core::Execution } + let(:windows_impl) { instance_spy(Facter::Core::Execution::Windows) } let(:impl) { Facter::Core::Execution.impl } + before do + allow(Facter::Core::Execution::Windows).to receive(:new).and_return(windows_impl) + end + it 'delegates #search_paths to the implementation' do expect(impl).to receive(:search_paths) execution.search_paths @@ -18,12 +23,12 @@ end it 'delegates #absolute_path? to the implementation' do - expect(impl).to receive(:absolute_path?).with('waffles', nil) + expect(impl).to receive(:absolute_path?).with('waffles') execution.absolute_path?('waffles') end it 'delegates #absolute_path? with an optional platform to the implementation' do - expect(impl).to receive(:absolute_path?).with('waffles', :windows) + expect(windows_impl).to receive(:absolute_path?).with('waffles') execution.absolute_path?('waffles', :windows) end diff --git a/spec/custom_facts/util/parser_spec.rb b/spec/custom_facts/util/parser_spec.rb index 823448b00..25f7799a9 100755 --- a/spec/custom_facts/util/parser_spec.rb +++ b/spec/custom_facts/util/parser_spec.rb @@ -54,7 +54,7 @@ shared_examples_for 'handling a not readable file' do before do allow(Facter::Util::FileHelper).to receive(:safe_read).with(data_file, nil).and_return(nil) - allow(LegacyFacter).to receive(:warn).at_least(:one) + allow(Facter).to receive(:log_exception).at_least(:once) end it 'handles not readable file' do @@ -72,11 +72,10 @@ expect(LegacyFacter::Util::Parser.parser_for(data_file).results).to eq data end - it 'handles exceptions and warn' do - # YAML data with an error + it 'handles exceptions' do allow(Facter::Util::FileHelper).to receive(:safe_read) .with(data_file, nil).and_return(data_in_yaml + '}') - allow(LegacyFacter).to receive(:warn).at_least(:one) + allow(Facter).to receive(:log_exception).at_least(:once) expect { LegacyFacter::Util::Parser.parser_for(data_file).results }.not_to raise_error end diff --git a/spec/custom_facts/util/resolution_spec.rb b/spec/custom_facts/util/resolution_spec.rb index 98f05e6d0..37c037301 100755 --- a/spec/custom_facts/util/resolution_spec.rb +++ b/spec/custom_facts/util/resolution_spec.rb @@ -102,7 +102,7 @@ end it 'fact_type does not raise error' do - expect { resolution.options(fact_type: 'simple') }.not_to raise_error(ArgumentError) + expect { resolution.options(fact_type: 'simple') }.not_to raise_error end it 'fails on unhandled options' do @@ -120,8 +120,9 @@ describe 'evaluating' do it 'evaluates the block in the context of the given resolution' do - expect(resolution).to receive(:weight).with(5) - resolution.evaluate { weight(5) } + expect(resolution).to receive(:setcode).with('code') + + resolution.evaluate { setcode('code') } end it 'raises a warning if the resolution is evaluated twice' do diff --git a/spec/facter/facter_spec.rb b/spec/facter/facter_spec.rb index fa7736fc4..ce60494ab 100644 --- a/spec/facter/facter_spec.rb +++ b/spec/facter/facter_spec.rb @@ -21,11 +21,11 @@ allow(config_reader_double).to receive(:ttls).and_return([]) allow(config_reader_double).to receive(:block_list).and_return([]) - allow(Facter::FactGroups).to receive(:instance).and_return(block_list_double) + allow(Facter::FactGroups).to receive(:new).and_return(block_list_double) allow(block_list_double).to receive(:blocked_facts).and_return([]) allow(block_list_double).to receive(:block_list).and_return([]) - Facter.instance_variable_set(:@logger, logger) + allow(Facter::Log).to receive(:new).and_return(logger) Facter.clear allow(Facter::SessionCache).to receive(:invalidate_all_caches) allow(Facter::FactManager).to receive(:instance).and_return(fact_manager_spy) @@ -33,7 +33,7 @@ end after do - Facter.remove_instance_variable(:@logger) + Facter.instance_variable_set(:@logger, nil) end def mock_fact_manager(method, return_value) @@ -101,7 +101,6 @@ def mock_collection(method, os_name = nil, error = nil) expected_json_output = '{}' allow(Facter::Options).to receive(:[]).and_call_original allow(Facter::Options).to receive(:[]).with(:strict).and_return(true) - allow(OsDetector).to receive(:detect).and_return(:solaris) formatted_facts = Facter.to_user_output({}, *user_query) @@ -208,10 +207,11 @@ def mock_collection(method, os_name = nil, error = nil) describe '#search' do it 'sends call to Facter::Options' do + allow(Facter::Options).to receive(:[]=) dirs = ['/dir1', '/dir2'] - - expect(Facter::Options).to receive(:[]=).with(:custom_dir, dirs) Facter.search(*dirs) + + expect(Facter::Options).to have_received(:[]=).with(:custom_dir, dirs) end end @@ -224,10 +224,11 @@ def mock_collection(method, os_name = nil, error = nil) describe '#search_external' do it 'sends call to Facter::Options' do + allow(Facter::Options).to receive(:[]=) dirs = ['/dir1', '/dir2'] - expect(Facter::Options).to receive(:[]=).with(:external_dir, dirs) - Facter.search_external(dirs) + + expect(Facter::Options).to have_received(:[]=).with(:external_dir, dirs) end end @@ -314,8 +315,10 @@ def mock_collection(method, os_name = nil, error = nil) describe '#debugging' do it 'sets log level to debug' do - expect(Facter::Options).to receive(:[]=).with(:debug, true) + allow(Facter::Options).to receive(:[]=) Facter.debugging(true) + + expect(Facter::Options).to have_received(:[]=).with(:debug, true) end end diff --git a/spec/facter/facts/freebsd/os/release_spec.rb b/spec/facter/facts/freebsd/os/release_spec.rb new file mode 100644 index 000000000..11970d3dc --- /dev/null +++ b/spec/facter/facts/freebsd/os/release_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +describe Facts::Freebsd::Os::Release do + describe '#call_the_resolver' do + subject(:fact) { Facts::Freebsd::Os::Release.new } + + before do + allow(Facter::Resolvers::Freebsd::FreebsdVersion).to receive(:resolve).with(:installed_userland).and_return(value) + end + + context 'when FreeBSD RELEASE' do + let(:value) { '12.1-RELEASE-p3' } + + it 'calls Facter::Resolvers::Freebsd::FreebsdVersion' do + fact.call_the_resolver + expect(Facter::Resolvers::Freebsd::FreebsdVersion).to have_received(:resolve).with(:installed_userland) + end + + it 'returns release fact' do + expect(fact.call_the_resolver).to be_an_instance_of(Array).and \ + contain_exactly(an_object_having_attributes(name: 'os.release', value: { 'full' => value, + 'major' => '12', + 'minor' => '1', + 'branch' => 'RELEASE-p3', + 'patchlevel' => '3' }), + an_object_having_attributes(name: 'operatingsystemmajrelease', value: '12', + type: :legacy), + an_object_having_attributes(name: 'operatingsystemrelease', value: value, type: :legacy)) + end + end + + context 'when FreeBSD STABLE' do + let(:value) { '12.1-STABLE' } + + it 'calls Facter::Resolvers::Freebsd::FreebsdVersion' do + fact.call_the_resolver + expect(Facter::Resolvers::Freebsd::FreebsdVersion).to have_received(:resolve).with(:installed_userland) + end + + it 'returns release fact' do + expect(fact.call_the_resolver).to be_an_instance_of(Array).and \ + contain_exactly(an_object_having_attributes(name: 'os.release', value: { 'full' => value, + 'major' => '12', + 'minor' => '1', + 'branch' => 'STABLE' }), + an_object_having_attributes(name: 'operatingsystemmajrelease', value: '12', + type: :legacy), + an_object_having_attributes(name: 'operatingsystemrelease', value: value, type: :legacy)) + end + end + + context 'when FreeBSD CURRENT' do + let(:value) { '13-CURRENT' } + + it 'calls Facter::Resolvers::Freebsd::FreebsdVersion' do + fact.call_the_resolver + expect(Facter::Resolvers::Freebsd::FreebsdVersion).to have_received(:resolve).with(:installed_userland) + end + + it 'returns release fact' do + expect(fact.call_the_resolver).to be_an_instance_of(Array).and \ + contain_exactly(an_object_having_attributes(name: 'os.release', value: { 'full' => value, + 'major' => '13', + 'branch' => 'CURRENT' }), + an_object_having_attributes(name: 'operatingsystemmajrelease', value: '13', + type: :legacy), + an_object_having_attributes(name: 'operatingsystemrelease', value: value, type: :legacy)) + end + end + end +end diff --git a/spec/facter/resolvers/freebsd/freebsd_version_resolver_spec.rb b/spec/facter/resolvers/freebsd/freebsd_version_resolver_spec.rb new file mode 100644 index 000000000..326fecac3 --- /dev/null +++ b/spec/facter/resolvers/freebsd/freebsd_version_resolver_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +describe Facter::Resolvers::Freebsd::FreebsdVersion do + before do + status = instance_double('Process::Status') + allow(status).to receive(:success?).and_return(true) + allow(Open3).to receive(:capture3) + .with('/bin/freebsd-version -kru') + .and_return(['13.0-CURRENT + 12.1-RELEASE-p3 + 12.0-STABLE', nil, status]) + end + + it 'returns installed kernel' do + result = Facter::Resolvers::Freebsd::FreebsdVersion.resolve(:installed_kernel) + + expect(result).to eq('13.0-CURRENT') + end + + it 'returns running kernel' do + result = Facter::Resolvers::Freebsd::FreebsdVersion.resolve(:running_kernel) + + expect(result).to eq('12.1-RELEASE-p3') + end + + it 'returns installed userland' do + result = Facter::Resolvers::Freebsd::FreebsdVersion.resolve(:installed_userland) + + expect(result).to eq('12.0-STABLE') + end +end diff --git a/spec/facter/resolvers/solaris/current_zone_resolver_spec.rb b/spec/facter/resolvers/solaris/current_zone_resolver_spec.rb index 44bc8a139..6302f596f 100644 --- a/spec/facter/resolvers/solaris/current_zone_resolver_spec.rb +++ b/spec/facter/resolvers/solaris/current_zone_resolver_spec.rb @@ -8,7 +8,6 @@ .and_return(true) allow(Open3).to receive(:capture2) .with('/bin/zonename') - .ordered .and_return([zone_name_output, status]) end diff --git a/spec/facter/resolvers/solaris/zfs_resolver_spec.rb b/spec/facter/resolvers/solaris/zfs_resolver_spec.rb index 66a3c00c5..28e950cae 100644 --- a/spec/facter/resolvers/solaris/zfs_resolver_spec.rb +++ b/spec/facter/resolvers/solaris/zfs_resolver_spec.rb @@ -5,7 +5,6 @@ status = double(Process::Status, to_s: st) allow(Open3).to receive(:capture2) .with('zfs upgrade -v') - .ordered .and_return([output, status]) end diff --git a/spec/facter/resolvers/solaris/zone_resolver_spec.rb b/spec/facter/resolvers/solaris/zone_resolver_spec.rb index fd1e3910d..3dd82206d 100644 --- a/spec/facter/resolvers/solaris/zone_resolver_spec.rb +++ b/spec/facter/resolvers/solaris/zone_resolver_spec.rb @@ -8,7 +8,6 @@ .and_return(true) allow(Open3).to receive(:capture2) .with('/usr/sbin/zoneadm list -cp') - .ordered .and_return([output, status]) end diff --git a/spec/facter/resolvers/solaris/zpool_resolver_spec.rb b/spec/facter/resolvers/solaris/zpool_resolver_spec.rb index 54377cc18..dc2c39f6a 100644 --- a/spec/facter/resolvers/solaris/zpool_resolver_spec.rb +++ b/spec/facter/resolvers/solaris/zpool_resolver_spec.rb @@ -5,7 +5,6 @@ status = double(Process::Status, to_s: st) allow(Open3).to receive(:capture2) .with('zpool upgrade -v') - .ordered .and_return([output, status]) end diff --git a/spec/framework/core/fact/external/external_fact_manager_spec.rb b/spec/framework/core/fact/external/external_fact_manager_spec.rb index 72908e2f6..a64776103 100644 --- a/spec/framework/core/fact/external/external_fact_manager_spec.rb +++ b/spec/framework/core/fact/external/external_fact_manager_spec.rb @@ -8,8 +8,6 @@ let(:custom_fact_manager) { Facter::ExternalFactManager.new } before do - allow(LegacyFacter).to receive(:search) - allow(LegacyFacter).to receive(:search_external) allow(LegacyFacter).to receive(:value).with(custom_fact_name).and_return(custom_fact_value) end diff --git a/spec/framework/core/fact_loaders/external_fact_loader_spec.rb b/spec/framework/core/fact_loaders/external_fact_loader_spec.rb index ec473bf95..c92b94916 100644 --- a/spec/framework/core/fact_loaders/external_fact_loader_spec.rb +++ b/spec/framework/core/fact_loaders/external_fact_loader_spec.rb @@ -38,20 +38,6 @@ expect(external_fact_loader.custom_facts).to eq([]) end end - - context 'when it blocks custom facts' do - before do - allow(collection).to receive(:custom_facts).and_return([]) - end - - it 'does not load custom facts (does not call LegacyFacter.search)' do - allow(Facter::Options).to receive(:custom_dir?).and_return(false) - expect(LegacyFacter).not_to receive(:search) - - external_fact_loader = Facter::ExternalFactLoader.new - external_fact_loader.custom_facts - end - end end describe '#external_facts' do @@ -83,19 +69,5 @@ expect(external_fact_loader.external_facts).to eq([]) end end - - context 'when it blocks external facts' do - before do - allow(collection).to receive(:external_facts).and_return([]) - end - - it 'does not load custom facts (does not call LegacyFacter.search_external)' do - allow(Facter::Options).to receive(:external_dir?).and_return(false) - expect(LegacyFacter).not_to receive(:search_external) - - external_fact_loader = Facter::ExternalFactLoader.new - external_fact_loader.external_facts - end - end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d56d808fb..f2b5b929f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -52,6 +52,14 @@ # 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. + + # TODO: (FACT-2420) - enable this when windows specs are refactored + # mocks.verify_partial_doubles = true end config.after do diff --git a/spec/spec_helper_legacy.rb b/spec/spec_helper_legacy.rb index 761dc66b1..71e4323a3 100644 --- a/spec/spec_helper_legacy.rb +++ b/spec/spec_helper_legacy.rb @@ -32,8 +32,8 @@ config.before do # Ensure that we don't accidentally cache facts and environment - # between test cases. - allow(LegacyFacter::Util::Loader).to receive(:load_all) + # between test cases + LegacyFacter.clear LegacyFacter.clear_messages diff --git a/tasks/fact_list_generator.rake b/tasks/fact_list_generator.rake new file mode 100644 index 000000000..793bcd323 --- /dev/null +++ b/tasks/fact_list_generator.rake @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +desc 'Create a fact list for the specified os' +task :fact_list_generator, [:os_name] do |_, args| + ROOT_DIR = Pathname.new(File.expand_path('..', __dir__)) unless defined?(ROOT_DIR) + + require "#{ROOT_DIR}/lib/framework/core/file_loader" + load_lib_dirs('facts', '**') + + os_hierarchy = Facter::OsHierarchy.new + hierarchy = os_hierarchy.construct_hierarchy(args[:os_name]) + + internal_fact_loader = Facter::InternalFactLoader.new(hierarchy) + facts = internal_fact_loader.facts + + fact_mapping = [] + facts.each do |loaded_fact| + fact_hash = {} + fact_hash[:name] = loaded_fact.name + fact_hash[:klass] = loaded_fact.klass + fact_hash[:type] = loaded_fact.type + fact_mapping << fact_hash + end + + puts JSON.pretty_generate(fact_mapping) +end