puppetlabs/facter-ng

View on GitHub
lib/framework/core/cache_manager.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

module Facter
  class CacheManager
    def initialize
      @groups = {}
      @log = Log.new(self)
      @fact_groups = Facter::FactGroups.new
      @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.delete_if do |fact|
        res = resolve_fact(fact)
        if res
          facts << res
          true
        else
          false
        end
      end

      [searched_facts, facts.flatten]
    end

    def cache_facts(resolved_facts)
      return unless Options[:cache]

      resolved_facts.each do |fact|
        cache_fact(fact)
      end

      begin
        write_cache unless @groups.empty?
      rescue Errno::EACCES => e
        @log.warn("Could not write cache: #{e.message}")
      end
    end

    def group_cached?(group_name)
      cached = @fact_groups.get_group_ttls(group_name) ? true : false
      delete_cache(group_name) unless cached
      cached
    end

    private

    def resolve_fact(searched_fact)
      group_name =  if searched_fact.file
                      searched_fact.name
                    else
                      @fact_groups.get_fact_group(searched_fact.name)
                    end

      return unless group_name

      return unless group_cached?(group_name)

      return unless check_ttls?(group_name)

      data = read_group_json(group_name)
      return unless data

      @log.debug("loading cached values for #{group_name} facts")

      create_facts(searched_fact, data)
    end

    def create_facts(searched_fact, data)
      if searched_fact.type == :file
        facts = []
        data.each do |fact_name, fact_value|
          fact = Facter::ResolvedFact.new(fact_name, fact_value, searched_fact.type,
                                          searched_fact.user_query, searched_fact.filter_tokens)
          fact.file = searched_fact.file
          facts << fact
        end
        facts
      else
        [Facter::ResolvedFact.new(searched_fact.name, data[searched_fact.name], searched_fact.type,
                                  searched_fact.user_query, searched_fact.filter_tokens)]
      end
    end

    def cache_fact(fact)
      group_name = if fact.file
                     File.basename(fact.file)
                   else
                     @fact_groups.get_fact_group(fact.name)
                   end
      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 unless check_ttls?(group_name)

        @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
      file = Util::FileHelper.safe_read(cache_file_name)
      begin
        data = JSON.parse(file)
      rescue JSON::ParserError
        delete_cache(group_name)
      end
      @groups[group_name] = data
    end

    def check_ttls?(group_name)
      ttls = @fact_groups.get_group_ttls(group_name)
      return false unless ttls

      cache_file_name = File.join(@cache_dir, group_name)
      if File.readable?(cache_file_name)
        file_time = File.mtime(cache_file_name)
        expire_date = file_time + ttls
        return true if expire_date > Time.now

        File.delete(cache_file_name)
      end

      @log.debug("#{group_name} facts cache file expired/missing")
      true
    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