diff --git a/lib/custom_facts/util/config.rb b/lib/custom_facts/util/config.rb index 74409813f..e8375cead 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 + end + def self.setup_default_ext_facts_dirs if LegacyFacter::Util::Root.root? windows_dir = windows_data_dir diff --git a/lib/framework/config/cache_list.rb b/lib/framework/config/cache_list.rb index ccc1e7054..2644a5e47 100644 --- a/lib/framework/config/cache_list.rb +++ b/lib/framework/config/cache_list.rb @@ -20,7 +20,7 @@ def get_fact_group(fact_name) def get_group_ttls(group_name) return unless (ttls = @groups_ttls.find { |g| g[group_name] }) - ttls[group_name] + ttls_to_seconds(ttls[group_name]) end private @@ -30,5 +30,20 @@ def load_cache_groups options = Options.instance @groups_ttls = ConfigReader.new(options[:config]).ttls || {} end + + def ttls_to_seconds(ttls) + duration, unit = ttls.split(' ', 2) + time = duration.to_i + case unit + when 'seconds' + return time + when 'minutes' + return time * 60 + when 'hours' + return time * 60 * 60 + when 'days' + return time * 60 * 60 * 24 + end + 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..3bdba32fd --- /dev/null +++ b/lib/framework/core/cache_manager.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Facter + class CacheManager + def self.cache_dir + LegacyFacter::Util::Config.facts_cache_dir + end + + def self.resolve_facts(searched_facts) + return searched_facts, [] unless File.directory?(cache_dir) + + facts = [] + searched_facts.each do |fact| + res = resolve_fact(fact) + facts << res unless res.nil? + end + facts.each do |fact| + searched_facts.delete_if { |f| f.name == fact.name } + end + [searched_facts, facts] + end + + def self.resolve_fact(searched_fact) + group_name = Facter::CacheList.instance.get_fact_group(searched_fact.name) + return nil unless group_name + + check_ttls(group_name) + data = read_group_json(group_name) + return nil if data.nil? || data[searched_fact.name].nil? + + create_fact(searched_fact, data[searched_fact.name]) + end + + def self.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 self.cache_facts(resolved_facts) + unless File.directory?(cache_dir) + require 'fileutils' + FileUtils.mkdir_p(cache_dir) + end + + resolved_facts.each do |fact| + cache_fact(fact) + end + end + + def self.cache_fact(fact) + group_name = Facter::CacheList.instance.get_fact_group(fact.name) + return if group_name.nil? || fact.value.nil? + + return unless check_ttls(group_name) + + data = read_group_json(group_name) || {} + cache_file_name = File.join(cache_dir, group_name) + data[fact.name] = fact.value + File.write(cache_file_name, JSON.pretty_generate(data)) + end + + def self.read_group_json(group_name) + cache_file_name = File.join(cache_dir, group_name) + data = nil + if File.exist?(cache_file_name) + file = File.read(cache_file_name) + data = JSON.parse(file) + end + data + end + + def self.check_ttls(group_name) + ttls = Facter::CacheList.instance.get_group_ttls(group_name) + return nil unless ttls + + cache_file_name = File.join(cache_dir, group_name) + return nil unless File.exist?(cache_file_name) + + file_time = File.mtime(cache_file_name) + expire_date = file_time + ttls + File.delete(cache_file_name) if expire_date < Time.now + ttls + end + end +end diff --git a/lib/framework/core/fact_manager.rb b/lib/framework/core/fact_manager.rb index d96022bd5..fdd39f70d 100644 --- a/lib/framework/core/fact_manager.rb +++ b/lib/framework/core/fact_manager.rb @@ -20,10 +20,15 @@ def resolve_facts(user_query = []) loaded_facts = @fact_loader.load(Options.get) searched_facts = QueryParser.parse(user_query, loaded_facts) + searched_facts, cached_facts = Facter::CacheManager.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) + + Facter::CacheManager.cache_facts(resolved_facts) + resolved_facts = resolved_facts.concat(cached_facts) + FactFilter.new.filter_facts!(resolved_facts) resolved_facts diff --git a/lib/models/resolved_fact.rb b/lib/models/resolved_fact.rb index f05fa35f7..5ffada8dc 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/model/resolved_fact_spec.rb b/spec/facter/model/resolved_fact_spec.rb index 9a1e48c4a..594bf6edf 100644 --- a/spec/facter/model/resolved_fact_spec.rb +++ b/spec/facter/model/resolved_fact_spec.rb @@ -24,12 +24,4 @@ expect(resolved_fact.core?).to be(true) end 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/core/fact_manager_spec.rb b/spec/framework/core/fact_manager_spec.rb index 4bd01cdd3..2a9c60ec4 100644 --- a/spec/framework/core/fact_manager_spec.rb +++ b/spec/framework/core/fact_manager_spec.rb @@ -28,19 +28,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(Facter::CacheManager) + .to receive(:resolve_facts) + .with(seached_facts) + .and_return([seached_facts, []]) + + allow(Facter::CacheManager) + .to receive(:cache_facts) + .with([resolved_fact]) resolved_facts = Facter::FactManager.instance.resolve_facts(user_query)