# frozen_string_literal: true require 'timeout' # The resolvable mixin defines behavior for evaluating and returning fact # resolutions. # # Classes including this mixin should implement a #name method describing # the value being resolved and a #resolve_value that actually executes the code # to resolve the value. module LegacyFacter module Core module Resolvable # The timeout, in seconds, for evaluating this resolution. # @return [Integer] # @api public attr_accessor :timeout attr_reader :logger # Return the timeout period for resolving a value. # (see #timeout) # @return [Numeric] # @comment requiring 'timeout' stdlib class causes Object#timeout to be # defined which delegates to Timeout.timeout. This method may potentially # overwrite the #timeout attr_reader on this class, so we define #limit to # avoid conflicts. def limit @timeout || 0 end ## # on_flush accepts a block and executes the block when the resolution's value # is flushed. This makes it possible to model a single, expensive system # call inside of a Ruby object and then define multiple dynamic facts which # resolve by sending messages to the model instance. If one of the dynamic # facts is flushed then it can, in turn, flush the data stored in the model # instance to keep all of the dynamic facts in sync without making multiple, # expensive, system calls. # # Please see the Solaris zones fact for an example of how this feature may be # used. # # @see Facter::Util::Fact#flush # @see Facter::Util::Resolution#flush # # @api public def on_flush(&block) @on_flush_block = block end ## # flush executes the block, if any, stored by the {on_flush} method # # @see Facter::Util::Fact#flush # @see Facter::Util::Resolution#on_flush # # @api private def flush @on_flush_block&.call end # Resolves the fact's value or loggs an error if the value # couldn't be resolved. # # @raise Facter::ResolveCustomFactError if an error was raised # (except Timeout::Error or Narmalization::NarmalizationError # in which case an error message is logged and the execution # isn't suspended). # # @return value (hash, string, int, array, etc) or nil # # @api private def value result = nil with_timing do Timeout.timeout(limit) do result = resolve_value end end LegacyFacter::Util::Normalization.normalize(result) rescue Timeout::Error => e Facter.log_exception(e, "Timed out after #{limit} seconds while resolving #{qualified_name}") nil rescue LegacyFacter::Util::Normalization::NormalizationError => e Facter.log_exception(e, "Fact resolution #{qualified_name} resolved to an invalid value: #{e.message}") nil rescue StandardError => e Facter.log_exception(e, "Error while resolving custom fact #{qualified_name}: #{e.message}") raise Facter::ResolveCustomFactError end private def with_timing starttime = Time.now.to_f yield finishtime = Time.now.to_f ms = (finishtime - starttime) * 1000 Facter::Log.show_time format('%s: %.2fms', qn: qualified_name, ms: ms) end def qualified_name "fact='#{@fact.name}', resolution='#{@name || ''}'" end end end end