puppetlabs/facter-ng

View on GitHub
lib/custom_facts/util/resolution.rb

Summary

Maintainability
A
45 mins
Test Coverage
# This represents a fact resolution. A resolution is a concrete
# implementation of a fact. A single fact can have many resolutions and
# the correct resolution will be chosen at runtime. Each time
# {Facter.add} is called, a new resolution is created and added to the
# set of resolutions for the fact named in the call.  Each resolution
# has a {#has_weight weight}, which defines its priority over other
# resolutions, and a set of {#confine _confinements_}, which defines the
# conditions under which it will be chosen. All confinements must be
# satisfied for a fact to be considered _suitable_.
#
# @api public
module Facter
  module Util
    class Resolution
      # @api private
      attr_accessor :code, :fact_type
      attr_writer :value

      extend Facter::Core::Execution

      class << self
        # Expose command execution methods that were extracted into
        # Facter::Core::Execution from Facter::Util::Resolution in Facter 2.0.0 for
        # compatibility.
        #
        # @deprecated
        public :search_paths, :which, :absolute_path?, :expand_command, :with_env, :exec
      end

      include LegacyFacter::Core::Resolvable
      include LegacyFacter::Core::Suitable

      # @!attribute [rw] name
      # The name of this resolution. The resolution name should be unique with
      # respect to the given fact.
      # @return [String]
      # @api public
      attr_accessor :name

      # @!attribute [r] fact
      # @return [Facter::Util::Fact]
      # @api private
      attr_reader :fact

      # Create a new resolution mechanism.
      #
      # @param name [String] The name of the resolution.
      # @return [void]
      #
      # @api private
      def initialize(name, fact)
        @name = name
        @fact = fact
        @confines = []
        @value = nil
        @timeout = 0
        @weight = nil
      end

      def resolution_type
        :simple
      end

      # Evaluate the given block in the context of this resolution. If a block has
      # already been evaluated emit a warning to that effect.
      #
      # @return [void]
      def evaluate(&block)
        if @last_evaluated
          msg = "Already evaluated #{@name}"
          msg << " at #{@last_evaluated}" if msg.is_a? String
          msg << ', reevaluating anyways'
          LegacyFacter.warn msg
        end

        instance_eval(&block)

        # Ruby 1.9+ provides the source location of procs which can provide useful
        # debugging information if a resolution is being evaluated twice. Since 1.8
        # doesn't support this we opportunistically provide this information.
        @last_evaluated = if block.respond_to? :source_location
                            block.source_location.join(':')
                          else
                            true
                          end
      end

      def options(options)
        accepted_options = %i[name value timeout weight fact_type file]

        accepted_options.each do |option_name|
          instance_variable_set("@#{option_name}", options.delete(option_name)) if options.key?(option_name)
        end

        raise ArgumentError, "Invalid resolution options #{options.keys.inspect}" unless options.keys.empty?
      end

      # Sets the code block or external program that will be evaluated to
      # get the value of the fact.
      #
      # @return [void]
      #
      # @overload setcode(string)
      #   Sets an external program to call to get the value of the resolution
      #   @param [String] string the external program to run to get the
      #     value
      #
      # @overload setcode(&block)
      #   Sets the resolution's value by evaluating a block at runtime
      #   @param [Proc] block The block to determine the resolution's value.
      #     This block is run when the fact is evaluated. Errors raised from
      #     inside the block are rescued and printed to stderr.
      #
      # @api public
      def setcode(string = nil, &block)
        if string
          @code = proc do
            output = Facter::Core::Execution.execute(string, on_fail: nil)
            if output.nil? || output.empty?
              nil
            else
              output
            end
          end
        elsif block_given?
          @code = block
        else
          raise ArgumentError, 'You must pass either code or a block'
        end
      end

      # Comparation is done based on weight and fact type.
      # The greatter the weight, the higher the priority.
      # If weights are equal, we consider external facts greater than custom facts
      def <=>(other)
        return compare_equal_weights(other) if weight == other.weight
        return 1 if weight > other.weight
        return -1 if weight < other.weight
      end

      private

      # If the weights are equal, we consider external facts greater tan custom facts
      def compare_equal_weights(other)
        # Other is considered greater because self is custom fact and other is external
        return -1 if fact_type == :custom && other.fact_type == :external

        # Self is considered greater, because it is external fact and other is custom
        return 1 if fact_type == :external && other.fact_type == :custom

        # They are considered equal
        0
      end

      def resolve_value
        if @value
          @value
        elsif @code.nil?
          nil
        elsif @code
          @code.call
        end
      end
    end
  end
end