From d9c2a048d530d5c9aeef669d8423f010cea5e19d Mon Sep 17 00:00:00 2001 From: Florin Dragos Date: Wed, 1 Apr 2020 11:19:12 +0300 Subject: [PATCH] (FACT-2486) Add facts cache --- .rubocop_todo.yml | 7 +- lib/custom_facts/util/config.rb | 6 +- lib/facter.rb | 2 +- lib/framework/cli/cli.rb | 4 +- lib/framework/config/block_list.rb | 32 ---- lib/framework/config/cache_list.rb | 34 ----- lib/framework/config/fact_groups.rb | 55 +++++++ lib/framework/config/group_list.rb | 14 -- lib/framework/core/cache_manager.rb | 131 +++++++++++++++++ lib/framework/core/fact_manager.rb | 6 + lib/framework/core/file_loader.rb | 4 +- .../core/options/config_file_options.rb | 2 +- lib/models/resolved_fact.rb | 4 - spec/facter/cache_manager_spec.rb | 138 ++++++++++++++++++ spec/facter/facter_spec.rb | 4 +- spec/facter/model/resolved_fact_spec.rb | 8 - spec/framework/config/block_list_spec.rb | 63 -------- spec/framework/config/fact_groups_spec.rb | 125 ++++++++++++++++ spec/framework/core/fact_manager_spec.rb | 19 ++- 19 files changed, 485 insertions(+), 173 deletions(-) delete mode 100644 lib/framework/config/block_list.rb delete mode 100644 lib/framework/config/cache_list.rb create mode 100644 lib/framework/config/fact_groups.rb delete mode 100644 lib/framework/config/group_list.rb create mode 100644 lib/framework/core/cache_manager.rb create mode 100644 spec/facter/cache_manager_spec.rb delete mode 100644 spec/framework/config/block_list_spec.rb create mode 100644 spec/framework/config/fact_groups_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 92c4d5334..b60be8fba 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --exclude-limit 1000` -# on 2020-04-15 10:09:42 +0300 using RuboCop version 0.74.0. +# on 2020-04-23 16:10:19 +0300 using RuboCop version 0.74.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -71,8 +71,8 @@ RSpec/FilePath: - 'spec/facter/resolvers/windows/system32_resolver_spec.rb' - 'spec/facter/resolvers/windows/virtualization_resolver_spec.rb' - 'spec/facter/resolvers/windows/win_os_description_resolver_spec.rb' - - 'spec/framework/config/block_list_spec.rb' - 'spec/framework/config/config_reader_spec.rb' + - 'spec/framework/config/fact_groups_spec.rb' - 'spec/framework/core/fact/external/external_fact_manager_spec.rb' - 'spec/framework/core/fact/internal/internal_fact_manager_spec.rb' - 'spec/framework/core/fact_loaders/class_discoverer_spec.rb' @@ -190,7 +190,7 @@ RSpec/SubjectStub: - 'spec/custom_facts/util/fact_spec.rb' - 'spec/custom_facts/util/resolution_spec.rb' -# Offense count: 179 +# Offense count: 176 # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. RSpec/VerifiedDoubles: Exclude: @@ -247,7 +247,6 @@ RSpec/VerifiedDoubles: - 'spec/facter/resolvers/windows/uptime_resolver_spec.rb' - 'spec/facter/resolvers/windows/virtualization_resolver_spec.rb' - 'spec/facter/resolvers/windows/win_os_description_resolver_spec.rb' - - 'spec/framework/config/block_list_spec.rb' - 'spec/framework/core/fact_loaders/external_fact_loader_spec.rb' - 'spec/framework/core/fact_loaders/fact_loader_spec.rb' - 'spec/framework/core/fact_manager_spec.rb' diff --git a/lib/custom_facts/util/config.rb b/lib/custom_facts/util/config.rb index 74409813f..85b72d24b 100644 --- a/lib/custom_facts/util/config.rb +++ b/lib/custom_facts/util/config.rb @@ -36,6 +36,10 @@ def self.external_facts_dirs @external_facts_dirs end + def self.facts_cache_dir + @facts_cache_dir ||= setup_default_cache_dir + end + def self.setup_default_ext_facts_dirs if LegacyFacter::Util::Root.root? windows_dir = windows_data_dir @@ -77,8 +81,6 @@ def self.setup_default_cache_dir end end - setup_default_cache_dir - def self.setup_default_override_binary_dir @override_binary_dir = if LegacyFacter::Util::Config.windows? nil diff --git a/lib/facter.rb b/lib/facter.rb index 066a75c99..7199ccacf 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -327,7 +327,7 @@ def error_check(args, resolved_facts) # # @api private def log_blocked_facts - block_list = Facter::BlockList.new(Facter::Options[:config]).block_list + 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") diff --git a/lib/framework/cli/cli.rb b/lib/framework/cli/cli.rb index c727e1a78..a57fb99ca 100755 --- a/lib/framework/cli/cli.rb +++ b/lib/framework/cli/cli.rb @@ -122,13 +122,13 @@ def version desc '--list-block-groups', 'List block groups' map ['--list-block-groups'] => :list_block_groups def list_block_groups(*_args) - puts Facter::BlockList.new.block_groups.to_yaml.lines[1..-1].join + puts Facter::FactGroups.new.groups.to_yaml.lines[1..-1].join end desc '--list-cache-groups', 'List cache groups' map ['--list-cache-groups'] => :list_cache_groups def list_cache_groups(*_args) - puts Facter::CacheList.instance.cache_groups.to_yaml.lines[1..-1].join + puts Facter::FactGroups.new.groups.to_yaml.lines[1..-1].join end def self.exit_on_failure? diff --git a/lib/framework/config/block_list.rb b/lib/framework/config/block_list.rb deleted file mode 100644 index 966473481..000000000 --- a/lib/framework/config/block_list.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Facter - class BlockList - attr_reader :block_groups, :block_list - - def initialize(block_list_path = nil) - @block_groups_file_path = block_list_path || File.join(ROOT_DIR, 'fact_groups.conf') - load_block_groups - end - - # Breakes down blocked groups in blocked facts - def blocked_facts - fact_list = [] - - @block_list.each do |group_name| - facts_for_block = @block_groups[group_name] - - fact_list += facts_for_block || [group_name] - end - - fact_list - end - - private - - def load_block_groups - @block_groups = File.readable?(@block_groups_file_path) ? Hocon.load(@block_groups_file_path) : {} - @block_list = ConfigReader.init(Options[:config]).block_list || {} - end - end -end diff --git a/lib/framework/config/cache_list.rb b/lib/framework/config/cache_list.rb deleted file mode 100644 index ccc1e7054..000000000 --- a/lib/framework/config/cache_list.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Facter - class CacheList - include Singleton - - attr_reader :cache_groups, :groups_ttls - - def initialize(block_list_path = nil) - @block_groups_file_path = block_list_path || File.join(ROOT_DIR, 'fact_groups.conf') - load_cache_groups - end - - # Get the group name a fact is part of - def get_fact_group(fact_name) - @cache_groups.detect { |k, v| break k if Array(v).include?(fact_name) } - end - - # Get config ttls for a given group - def get_group_ttls(group_name) - return unless (ttls = @groups_ttls.find { |g| g[group_name] }) - - ttls[group_name] - end - - private - - def load_cache_groups - @cache_groups = Facter::GroupList.instance.groups - options = Options.instance - @groups_ttls = ConfigReader.new(options[:config]).ttls || {} - end - end -end diff --git a/lib/framework/config/fact_groups.rb b/lib/framework/config/fact_groups.rb new file mode 100644 index 000000000..7932288dd --- /dev/null +++ b/lib/framework/config/fact_groups.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Facter + class FactGroups + attr_reader :groups, :block_list + + @groups_ttls = [] + + STRING_TO_SECONDS = { 'seconds' => 1, 'minutes' => 60, 'hours' => 3600, 'days' => 3600 * 24 }.freeze + + def initialize(group_list_path = nil) + @groups_file_path = group_list_path || File.join(ROOT_DIR, 'fact_groups.conf') + @groups ||= File.readable?(@groups_file_path) ? Hocon.load(@groups_file_path) : {} + load_groups + end + + # Breakes down blocked groups in blocked facts + def blocked_facts + fact_list = [] + + @block_list.each do |group_name| + facts_for_block = @groups[group_name] + + fact_list += facts_for_block || [group_name] + end + + fact_list + end + + # Get the group name a fact is part of + def get_fact_group(fact_name) + @groups.detect { |k, v| break k if Array(v).find { |f| fact_name =~ /^#{f}.*/ } } + end + + # Get config ttls for a given group + def get_group_ttls(group_name) + return unless (ttls = @groups_ttls.find { |g| g[group_name] }) + + ttls_to_seconds(ttls[group_name]) + end + + private + + def load_groups + config = ConfigReader.init(Options[:config]) + @block_list = config.block_list || {} + @groups_ttls = ConfigReader.init(Options[:config]).ttls || {} + end + + def ttls_to_seconds(ttls) + duration, unit = ttls.split(' ', 2) + duration.to_i * STRING_TO_SECONDS[unit] + end + end +end diff --git a/lib/framework/config/group_list.rb b/lib/framework/config/group_list.rb deleted file mode 100644 index 7883a429a..000000000 --- a/lib/framework/config/group_list.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Facter - class GroupList - include Singleton - - attr_reader :groups - - def initialize(group_list_path = nil) - @groups_file_path = group_list_path || File.join(ROOT_DIR, 'fact_groups.conf') - @groups ||= File.readable?(@groups_file_path) ? Hocon.load(@groups_file_path) : {} - end - end -end diff --git a/lib/framework/core/cache_manager.rb b/lib/framework/core/cache_manager.rb new file mode 100644 index 000000000..40867b535 --- /dev/null +++ b/lib/framework/core/cache_manager.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +module Facter + class CacheManager + def initialize + @groups = {} + @log = Log.new(self) + @fact_groups = Facter::FactGroups.new + end + + def cache_dir + LegacyFacter::Util::Config.facts_cache_dir + end + + def resolve_facts(searched_facts) + return searched_facts, [] if !File.directory?(cache_dir) || !Options[:cache] + + facts = [] + searched_facts.each do |fact| + res = resolve_fact(fact) + facts << res if res + end + facts.each do |fact| + searched_facts.delete_if { |f| f.name == fact.name } + end + [searched_facts, facts] + end + + def cache_facts(resolved_facts) + return unless Options[:cache] + + resolved_facts.each do |fact| + cache_fact(fact) + end + + write_cache unless @groups.empty? + end + + private + + def resolve_fact(searched_fact) + group_name = @fact_groups.get_fact_group(searched_fact.name) + return unless group_name + + return unless group_cached?(group_name) + + return if check_ttls(group_name).zero? + + data = read_group_json(group_name) + return unless data + + @log.debug("loading cached values for #{group_name} facts") + create_fact(searched_fact, data[searched_fact.name]) + end + + def create_fact(searched_fact, value) + resolved_fact = Facter::ResolvedFact.new(searched_fact.name, value, searched_fact.type) + resolved_fact.user_query = searched_fact.user_query + resolved_fact.filter_tokens = searched_fact.filter_tokens + resolved_fact + end + + def cache_fact(fact) + group_name = @fact_groups.get_fact_group(fact.name) + return if !group_name || fact.value.nil? + + return unless group_cached?(group_name) + + @groups[group_name] ||= {} + @groups[group_name][fact.name] = fact.value + end + + def write_cache + unless File.directory?(cache_dir) + require 'fileutils' + FileUtils.mkdir_p(cache_dir) + end + + @groups.each do |group_name, data| + next if check_ttls(group_name).zero? + + @log.debug("caching values for #{group_name} facts") + cache_file_name = File.join(cache_dir, group_name) + File.write(cache_file_name, JSON.pretty_generate(data)) + end + end + + def read_group_json(group_name) + return @groups[group_name] if @groups.key?(group_name) + + cache_file_name = File.join(cache_dir, group_name) + data = nil + if File.readable?(cache_file_name) + file = Util::FileHelper.safe_read(cache_file_name) + begin + data = JSON.parse(file) + rescue JSON::ParserError + delete_cache(group_name) + end + end + @groups[group_name] = data + end + + def group_cached?(group_name) + cached = @fact_groups.get_group_ttls(group_name) ? true : false + delete_cache(group_name) unless cached + cached + end + + def check_ttls(group_name) + ttls = @fact_groups.get_group_ttls(group_name) + return 0 unless ttls + + cache_file_name = File.join(cache_dir, group_name) + return ttls unless File.readable?(cache_file_name) + + file_time = File.mtime(cache_file_name) + expire_date = file_time + ttls + if expire_date < Time.now + File.delete(cache_file_name) + return ttls + end + expire_date.to_i + end + + def delete_cache(group_name) + cache_file_name = File.join(cache_dir, group_name) + File.delete(cache_file_name) if File.readable?(cache_file_name) + end + end +end diff --git a/lib/framework/core/fact_manager.rb b/lib/framework/core/fact_manager.rb index d96022bd5..dfa005a50 100644 --- a/lib/framework/core/fact_manager.rb +++ b/lib/framework/core/fact_manager.rb @@ -20,10 +20,16 @@ def resolve_facts(user_query = []) loaded_facts = @fact_loader.load(Options.get) searched_facts = QueryParser.parse(user_query, loaded_facts) + cache_manager = Facter::CacheManager.new + searched_facts, cached_facts = cache_manager.resolve_facts(searched_facts) internal_facts = @internal_fact_mgr.resolve_facts(searched_facts) external_facts = @external_fact_mgr.resolve_facts(searched_facts) resolved_facts = override_core_facts(internal_facts, external_facts) + + cache_manager.cache_facts(resolved_facts) + resolved_facts = resolved_facts.concat(cached_facts) + FactFilter.new.filter_facts!(resolved_facts) resolved_facts diff --git a/lib/framework/core/file_loader.rb b/lib/framework/core/file_loader.rb index c506653f3..711b5a3e4 100644 --- a/lib/framework/core/file_loader.rb +++ b/lib/framework/core/file_loader.rb @@ -31,9 +31,7 @@ def load_lib_dirs(*dirs) require "#{ROOT_DIR}/lib/framework/detector/os_detector" require "#{ROOT_DIR}/lib/framework/config/config_reader" -require "#{ROOT_DIR}/lib/framework/config/group_list" -require "#{ROOT_DIR}/lib/framework/config/block_list" -require "#{ROOT_DIR}/lib/framework/config/cache_list" +require "#{ROOT_DIR}/lib/framework/config/fact_groups" load_dir(['config']) diff --git a/lib/framework/core/options/config_file_options.rb b/lib/framework/core/options/config_file_options.rb index 3ce97453f..05d8073da 100644 --- a/lib/framework/core/options/config_file_options.rb +++ b/lib/framework/core/options/config_file_options.rb @@ -71,7 +71,7 @@ def augment_show_legacy(global_conf) end def augment_facts(ttls) - blocked_facts = Facter::BlockList.new.blocked_facts + blocked_facts = Facter::FactGroups.new.blocked_facts @options[:blocked_facts] = blocked_facts unless blocked_facts.nil? @options[:ttls] = ttls unless ttls.nil? diff --git a/lib/models/resolved_fact.rb b/lib/models/resolved_fact.rb index caa949bb3..c4629b521 100644 --- a/lib/models/resolved_fact.rb +++ b/lib/models/resolved_fact.rb @@ -6,10 +6,6 @@ class ResolvedFact attr_accessor :user_query, :filter_tokens, :value def initialize(name, value = '', type = :core) - unless type =~ /core|legacy|custom/ - raise ArgumentError, 'The type provided for fact is not legacy, core or custom!' - end - @name = name @value = Utils.deep_stringify_keys(value) @type = type diff --git a/spec/facter/cache_manager_spec.rb b/spec/facter/cache_manager_spec.rb new file mode 100644 index 000000000..df1d3386b --- /dev/null +++ b/spec/facter/cache_manager_spec.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +describe Facter::CacheManager do + subject(:cache_manager) { Facter::CacheManager.new } + + let(:cache_dir) { '/etc/facter/cache' } + let(:searched_core_fact) do + instance_spy(Facter::SearchedFact, name: 'os', fact_class: instance_spy(Facts::Debian::Os::Name), + filter_tokens: [], user_query: '', type: :core) + end + let(:searched_custom_fact) do + instance_spy(Facter::SearchedFact, name: 'my_custom_fact', fact_class: nil, filter_tokens: [], + user_query: '', type: :custom) + end + let(:searched_facts) { [searched_core_fact, searched_custom_fact] } + let(:cached_core_fact) { "{\n \"os\": \"Ubuntu\"\n}" } + + let(:resolved_core_fact) { mock_resolved_fact('os', 'Ubuntu', '', []) } + let(:resolved_facts) { [resolved_core_fact] } + let(:group_name) { 'operating system' } + let(:cache_file_name) { File.join(cache_dir, group_name) } + let(:fact_groups) { instance_spy(Facter::FactGroups) } + + before do + allow(LegacyFacter::Util::Config).to receive(:facts_cache_dir).and_return(cache_dir) + allow(Facter::FactGroups).to receive(:new).and_return(fact_groups) + allow(Facter::Options).to receive(:[]).with(:debug).and_return(false) + end + + describe '#resolve_facts' do + context 'with no cache dir' do + before do + allow(File).to receive(:directory?).with(cache_dir).and_return(false) + allow(Facter::Options).to receive(:[]).with(:cache).and_return(true) + end + + it 'returns searched facts' do + sf, _cf = cache_manager.resolve_facts(searched_facts) + expect(sf).to eq(searched_facts) + end + + it 'returns no cached facts' do + _, cf = cache_manager.resolve_facts(searched_facts) + expect(cf).to be_empty + end + end + + context 'with no cache false' do + before do + allow(File).to receive(:directory?).with(cache_dir).and_return(true) + allow(Facter::Options).to receive(:[]).with(:cache).and_return(false) + end + + it 'returns searched facts' do + sf, _cf = cache_manager.resolve_facts(searched_facts) + expect(sf).to eq(searched_facts) + end + + it 'returns no cached facts' do + _, cf = cache_manager.resolve_facts(searched_facts) + expect(cf).to be_empty + end + end + + context 'with cached facts' do + before do + allow(File).to receive(:directory?).with(cache_dir).and_return(true) + allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name) + allow(fact_groups).to receive(:get_fact_group).with('my_custom_fact').and_return(nil) + allow(File).to receive(:readable?).with(cache_file_name).and_return(true) + allow(File).to receive(:mtime).with(cache_file_name).and_return(Time.now) + allow(Facter::Util::FileHelper).to receive(:safe_read).with(cache_file_name).and_return(cached_core_fact) + allow(Facter::Options).to receive(:[]).with(:cache).and_return(true) + end + + it 'returns cached fact' do + allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000) + + _, cached_facts = cache_manager.resolve_facts(searched_facts) + expect(cached_facts).to be_an_instance_of(Array).and contain_exactly( + an_instance_of(Facter::ResolvedFact).and(having_attributes(name: 'os', value: 'Ubuntu', type: :core)) + ) + end + + it 'returns searched fact' do + allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000) + + sf, _cf = cache_manager.resolve_facts(searched_facts) + expect(sf).to be_an_instance_of(Array).and contain_exactly( + an_object_having_attributes(name: 'my_custom_fact', type: :custom) + ) + end + + it 'deletes cache file' do + allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil) + allow(File).to receive(:delete).with(cache_file_name) + + cache_manager.resolve_facts(searched_facts) + expect(File).to have_received(:delete).with(cache_file_name) + end + end + end + + describe '#cache_facts' do + context 'with group not cached' do + before do + allow(File).to receive(:directory?).with(cache_dir).and_return(true) + allow(File).to receive(:readable?).with(cache_file_name).and_return(false) + allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil) + allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name) + allow(File).to receive(:write).with(cache_file_name, cached_core_fact) + allow(Facter::Options).to receive(:[]).with(:cache).and_return(true) + end + + it 'returns without caching' do + cache_manager.cache_facts(resolved_facts) + expect(File).not_to have_received(:write).with(cache_file_name, cached_core_fact) + end + end + + context 'with cache group' do + before do + allow(File).to receive(:directory?).with(cache_dir).and_return(true) + allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name) + allow(fact_groups).to receive(:get_fact_group).with('my_custom_fact').and_return(nil) + allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000) + allow(File).to receive(:readable?).with(cache_file_name).and_return(false) + allow(File).to receive(:write).with(cache_file_name, cached_core_fact) + allow(Facter::Options).to receive(:[]).with(:cache).and_return(true) + end + + it 'caches fact' do + cache_manager.cache_facts(resolved_facts) + expect(File).to have_received(:write).with(cache_file_name, cached_core_fact) + end + end + end +end diff --git a/spec/facter/facter_spec.rb b/spec/facter/facter_spec.rb index ee2af3532..553a512f1 100644 --- a/spec/facter/facter_spec.rb +++ b/spec/facter/facter_spec.rb @@ -12,7 +12,7 @@ let(:fact_collection_spy) { instance_spy(Facter::FactCollection) } let(:key_error) { KeyError.new('key error') } let(:config_reader_double) { double(Facter::ConfigReader) } - let(:block_list_double) { double(Facter::BlockList) } + let(:block_list_double) { instance_spy(Facter::FactGroups) } before do allow(Facter::ConfigReader).to receive(:init).and_return(config_reader_double) @@ -21,7 +21,7 @@ allow(config_reader_double).to receive(:ttls).and_return([]) allow(config_reader_double).to receive(:block_list).and_return([]) - allow(Facter::BlockList).to receive(:instance).and_return(block_list_double) + allow(Facter::FactGroups).to receive(:instance).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([]) diff --git a/spec/facter/model/resolved_fact_spec.rb b/spec/facter/model/resolved_fact_spec.rb index 7bcfd1d9e..ab9876ef3 100644 --- a/spec/facter/model/resolved_fact_spec.rb +++ b/spec/facter/model/resolved_fact_spec.rb @@ -35,12 +35,4 @@ end # rubocop:enable Style/UnneededInterpolation end - - context 'when is an invalid type' do - it 'raises an ArgumentError' do - expect do - Facter::ResolvedFact.new('fact_name', 'fact_value', :type) - end.to raise_error(ArgumentError, 'The type provided for fact is not legacy, core or custom!') - end - end end diff --git a/spec/framework/config/block_list_spec.rb b/spec/framework/config/block_list_spec.rb deleted file mode 100644 index 505b7cd85..000000000 --- a/spec/framework/config/block_list_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -describe Facter::BlockList do - subject(:block_list) { Facter::BlockList } - - let(:config_reader) { double(Facter::ConfigReader) } - - before do - allow(Facter::ConfigReader).to receive(:init).and_return(config_reader) - allow(config_reader).to receive(:block_list).and_return([]) - end - - describe '#initialize' do - it 'sets @block_groups_file_path to parameter value' do - blk_list = block_list.new('path/to/block/groups') - - expect(blk_list.instance_variable_get(:@block_groups_file_path)).to eq('path/to/block/groups') - end - - it 'sets @block_groups_file_path to default path' do - blk_list = block_list.new - - expect(blk_list.instance_variable_get(:@block_groups_file_path)).to eq(File.join(ROOT_DIR, 'block_groups.conf')) - end - end - - describe '#blocked_facts' do - context 'with block_list' do - before do - allow(File).to receive(:readable?).and_return(true) - allow(Hocon).to receive(:load) - .with(File.join(ROOT_DIR, 'fact_groups.conf')) - .and_return('blocked_group' => %w[fact1 fact2]) - allow(config_reader).to receive(:block_list).and_return(%w[blocked_group blocked_fact]) - end - - it 'returns a list of blocked facts' do - blk_list = block_list.new - - expect(blk_list.blocked_facts).to eq(%w[fact1 fact2 blocked_fact]) - end - end - - context 'without block_list' do - before do - allow(File).to receive(:readable?).and_return(false) - allow(config_reader).to receive(:block_list).and_return([]) - end - - it 'finds no block group file' do - allow(File).to receive(:readable?).and_return(false) - - config_reader = double(Facter::ConfigReader) - allow(Facter::ConfigReader).to receive(:new).and_return(config_reader) - allow(config_reader).to receive(:block_list).and_return(nil) - - blk_list = block_list.new - - expect(blk_list.blocked_facts).to eq([]) - end - end - end -end diff --git a/spec/framework/config/fact_groups_spec.rb b/spec/framework/config/fact_groups_spec.rb new file mode 100644 index 000000000..ccebccf97 --- /dev/null +++ b/spec/framework/config/fact_groups_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +describe Facter::FactGroups do + subject(:fact_groups) { Facter::FactGroups } + + let(:fg) { fact_groups.new } + let(:config_reader) { class_spy(Facter::ConfigReader) } + + before do + allow(Facter::ConfigReader).to receive(:init).and_return(config_reader) + allow(config_reader).to receive(:block_list).and_return([]) + allow(config_reader).to receive(:ttls).and_return([]) + end + + describe '#initialize' do + it 'sets @groups_file_path to parameter value' do + blk_list = fact_groups.new('path/to/block/groups') + + expect(blk_list.instance_variable_get(:@groups_file_path)).to eq('path/to/block/groups') + end + + it 'sets @groups_file_path to default path' do + blk_list = fact_groups.new + + expect(blk_list.instance_variable_get(:@groups_file_path)).to eq(File.join(ROOT_DIR, 'fact_groups.conf')) + end + end + + describe '#blocked_facts' do + context 'with block_list' do + before do + allow(File).to receive(:readable?).and_return(true) + allow(Hocon).to receive(:load) + .with(File.join(ROOT_DIR, 'fact_groups.conf')) + .and_return('blocked_group' => %w[fact1 fact2]) + allow(config_reader).to receive(:block_list).and_return(%w[blocked_group blocked_fact]) + end + + it 'returns a list of blocked facts' do + blk_list = fact_groups.new + + expect(blk_list.blocked_facts).to eq(%w[fact1 fact2 blocked_fact]) + end + end + + context 'without block_list' do + before do + allow(File).to receive(:readable?).and_return(false) + allow(config_reader).to receive(:block_list).and_return([]) + allow(Facter::ConfigReader).to receive(:new).and_return(config_reader) + allow(config_reader).to receive(:block_list).and_return(nil) + end + + it 'finds no block group file' do + blk_list = fact_groups.new + + expect(blk_list.blocked_facts).to eq([]) + end + end + end + + describe '#get_fact_group' do + context 'when it finds group file' do + before do + allow(File).to receive(:readable?).and_return(true) + allow(Hocon).to receive(:load) + .with(File.join(ROOT_DIR, 'fact_groups.conf')) + .and_return('operating system' => %w[os os.name]) + + allow(config_reader).to receive(:ttls).and_return(['operating system' => '30 minutes']) + end + + it 'returns group' do + expect(fg.get_fact_group('os')).to eq('operating system') + end + + it 'returns nil' do + expect(fg.get_fact_group('memory')).to be_nil + end + end + + context 'when it does not find group file' do + before do + allow(File).to receive(:readable?).and_return(false) + allow(config_reader).to receive(:ttls).and_return(nil) + end + + it 'returns nil' do + expect(fg.get_fact_group('os')).to be_nil + end + end + end + + describe '#get_group_ttls' do + context 'when it finds group file' do + before do + allow(File).to receive(:readable?).and_return(true) + allow(Hocon).to receive(:load) + .with(File.join(ROOT_DIR, 'fact_groups.conf')) + .and_return('operating system' => %w[os os.name]) + + allow(config_reader).to receive(:ttls).and_return(['operating system' => '30 minutes']) + end + + it 'returns group' do + expect(fg.get_group_ttls('operating system')).to eq(1800) + end + + it 'returns nil' do + expect(fg.get_group_ttls('memory')).to be_nil + end + end + + context 'when it does not find group file' do + before do + allow(File).to receive(:readable?).and_return(false) + allow(config_reader).to receive(:ttls).and_return(nil) + end + + it 'returns nil' do + expect(fg.get_group_ttls('os')).to be_nil + end + end + end +end diff --git a/spec/framework/core/fact_manager_spec.rb b/spec/framework/core/fact_manager_spec.rb index 4bd01cdd3..eb9190932 100644 --- a/spec/framework/core/fact_manager_spec.rb +++ b/spec/framework/core/fact_manager_spec.rb @@ -3,11 +3,13 @@ describe Facter::FactManager do let(:internal_manager) { instance_spy(Facter::InternalFactManager) } let(:external_manager) { instance_spy(Facter::ExternalFactManager) } + let(:cache_manager) { instance_spy(Facter::CacheManager) } before do Singleton.__init__(Facter::FactManager) allow(Facter::InternalFactManager).to receive(:new).and_return(internal_manager) allow(Facter::ExternalFactManager).to receive(:new).and_return(external_manager) + allow(Facter::CacheManager).to receive(:new).and_return(cache_manager) end describe '#resolve_facts' do @@ -28,19 +30,30 @@ resolved_fact = mock_resolved_fact('os', 'Ubuntu', '', []) + seached_facts = [searched_fact1, searched_fact2] + allow(Facter::QueryParser) .to receive(:parse) .with(user_query, loaded_facts) - .and_return([searched_fact1, searched_fact2]) + .and_return(seached_facts) allow(internal_manager) .to receive(:resolve_facts) - .with([searched_fact1, searched_fact2]) + .with(seached_facts) .and_return([resolved_fact]) allow(external_manager) .to receive(:resolve_facts) - .with([searched_fact1, searched_fact2]) + .with(seached_facts) + + allow(cache_manager) + .to receive(:resolve_facts) + .with(seached_facts) + .and_return([seached_facts, []]) + + allow(cache_manager) + .to receive(:cache_facts) + .with([resolved_fact]) resolved_facts = Facter::FactManager.instance.resolve_facts(user_query)