From 1ba2bed578debd251d2b9514039082eaa3f136df Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 May 2008 14:30:29 -0500 Subject: Moving all of the support classes to util/. This makes it easier for our loader to distinguish between code that Facter uses and new facts. --- lib/facter.rb | 9 ++- lib/facter/collection.rb | 84 ---------------------------- lib/facter/confine.rb | 41 -------------- lib/facter/fact.rb | 102 --------------------------------- lib/facter/loader.rb | 127 ------------------------------------------ lib/facter/resolution.rb | 107 ----------------------------------- lib/facter/util/collection.rb | 84 ++++++++++++++++++++++++++++ lib/facter/util/confine.rb | 41 ++++++++++++++ lib/facter/util/fact.rb | 102 +++++++++++++++++++++++++++++++++ lib/facter/util/loader.rb | 127 ++++++++++++++++++++++++++++++++++++++++++ lib/facter/util/resolution.rb | 107 +++++++++++++++++++++++++++++++++++ 11 files changed, 467 insertions(+), 464 deletions(-) delete mode 100644 lib/facter/collection.rb delete mode 100644 lib/facter/confine.rb delete mode 100644 lib/facter/fact.rb delete mode 100644 lib/facter/loader.rb delete mode 100644 lib/facter/resolution.rb create mode 100644 lib/facter/util/collection.rb create mode 100644 lib/facter/util/confine.rb create mode 100644 lib/facter/util/fact.rb create mode 100644 lib/facter/util/loader.rb create mode 100644 lib/facter/util/resolution.rb (limited to 'lib') diff --git a/lib/facter.rb b/lib/facter.rb index 913060e..0f00f7d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -18,8 +18,11 @@ #-- module Facter - require 'facter/fact' - require 'facter/collection' + # This is just so the other classes have the constant. + module Util; end + + require 'facter/util/fact' + require 'facter/util/collection' include Comparable include Enumerable @@ -47,7 +50,7 @@ module Facter def self.collection unless defined?(@collection) and @collection - @collection = Facter::Collection.new + @collection = Facter::Util::Collection.new end @collection end diff --git a/lib/facter/collection.rb b/lib/facter/collection.rb deleted file mode 100644 index 5e6845a..0000000 --- a/lib/facter/collection.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'facter' -require 'facter/resolution' - -# Manage which facts exist and how we access them. Largely just a wrapper -# around a hash of facts. -class Facter::Collection - # Return a fact object by name. If you use this, you still have to call - # 'value' on it to retrieve the actual value. - def [](name) - value(name) - end - - # Add a resolution mechanism for a named fact. This does not distinguish - # between adding a new fact and adding a new way to resolve a fact. - def add(name, options = {}, &block) - name = canonize(name) - - unless fact = @facts[name] - fact = Facter::Fact.new(name, options) - @facts[name] = fact - end - - fact.add(&block) if block - - return fact - end - - include Enumerable - - # Iterate across all of the facts. - def each - @facts.each do |name, fact| - value = fact.value - unless value.nil? - yield name.to_s, value - end - end - end - - # Return a fact by name. - def fact(name) - @facts[canonize(name)] - end - - # Flush all cached values. - def flush - @facts.each { |name, fact| fact.flush } - end - - def initialize - @facts = Hash.new - end - - # Return a list of all of the facts. - def list - return @facts.keys - end - - # Return a hash of all of our facts. - def to_hash - @facts.inject({}) do |h, ary| - value = ary[1].value - if ! value.nil? - # For backwards compatibility, convert the fact name to a string. - h[ary[0].to_s] = value - end - h - end - end - - def value(name) - if fact = @facts[canonize(name)] - fact.value - end - end - - private - - # Provide a consistent means of getting the exact same fact name - # every time. - def canonize(name) - name.to_s.downcase.to_sym - end -end diff --git a/lib/facter/confine.rb b/lib/facter/confine.rb deleted file mode 100644 index 529c968..0000000 --- a/lib/facter/confine.rb +++ /dev/null @@ -1,41 +0,0 @@ -# A restricting tag for fact resolution mechanisms. The tag must be true -# for the resolution mechanism to be suitable. -class Facter::Confine - attr_accessor :fact, :values - - # Add the restriction. Requires the fact name, an operator, and the value - # we're comparing to. - def initialize(fact, *values) - raise ArgumentError, "The fact name must be provided" unless fact - raise ArgumentError, "One or more values must be provided" if values.empty? - fact = fact.to_s if fact.is_a? Symbol - @fact = fact - @values = values.collect do |value| - if value.is_a? String - value - else - value.to_s - end - end - end - - def to_s - return "'%s' '%s'" % [@fact, @values.join(",")] - end - - # Evaluate the fact, returning true or false. - def true? - unless fact = Facter[@fact] - Facter.debug "No fact for %s" % @fact - return false - end - value = fact.value - - return false if value.nil? - - @values.each { |v| - return true if value.downcase == v.downcase - } - return false - end -end diff --git a/lib/facter/fact.rb b/lib/facter/fact.rb deleted file mode 100644 index 9514cd0..0000000 --- a/lib/facter/fact.rb +++ /dev/null @@ -1,102 +0,0 @@ -require 'facter' -require 'facter/resolution' - -class Facter::Fact - attr_accessor :name, :searching, :ldapname - - # Create a new fact, with no resolution mechanisms. - def initialize(name, options = {}) - @name = name.to_s.downcase.intern - - # LAK:NOTE: This is slow for many options, but generally we won't have any and at - # worst we'll have one. If we add more, this should be made more efficient. - options.each do |name, value| - case name - when :ldapname: self.ldapname = value - else - raise ArgumentError, "Invalid fact option '%s'" % name - end - end - - @ldapname ||= @name.to_s - - @resolves = [] - @searching = false - - @value = nil - end - - # Add a new resolution mechanism. This requires a block, which will then - # be evaluated in the context of the new mechanism. - def add(&block) - raise ArgumentError, "You must pass a block to Fact.add" unless block_given? - - resolve = Facter::Resolution.new(@name) - - resolve.instance_eval(&block) - - @resolves << resolve - - # Immediately sort the resolutions, so that we always have - # a sorted list for looking up values. - # We always want to look them up in the order of number of - # confines, so the most restricted resolution always wins. - @resolves.sort! { |a, b| b.length <=> a.length } - - return resolve - end - - # Flush any cached values. - def flush - @value = nil - @suitable = nil - end - - # Return the value for a given fact. Searches through all of the mechanisms - # and returns either the first value or nil. - def value - unless @value - # make sure we don't get stuck in recursive dependency loops - if @searching - Facter.debug "Caught recursion on %s" % @name - - # return a cached value if we've got it - if @value - return @value - else - return nil - end - end - @value = nil - - if @resolves.length == 0 - Facter.debug "No resolves for %s" % @name - return nil - end - - @searching = true - foundsuits = false - @value = @resolves.inject(nil) { |result, resolve| - next unless resolve.suitable? - foundsuits = true - - tmp = resolve.value - - break tmp unless tmp.nil? or tmp == "" - } - @searching = false - - unless foundsuits - Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] - end - end - - if @value.nil? - # nothing - Facter.debug("value for %s is still nil" % @name) - return nil - else - return @value - end - end -end diff --git a/lib/facter/loader.rb b/lib/facter/loader.rb deleted file mode 100644 index 3284cc4..0000000 --- a/lib/facter/loader.rb +++ /dev/null @@ -1,127 +0,0 @@ -require 'facter' - -# Load facts on demand. -class Facter::Loader - # Load all resolutions for a single fact. - def load(fact) - # Now load from the search path - shortname = fact.to_s.downcase - load_env(shortname) - - filename = shortname + ".rb" - search_path.each do |dir| - # Load individual files - file = File.join(dir, filename) - - # We have to specify Kernel.load, because we have a load method. - Kernel.load(file) if FileTest.exist?(file) - - # And load any directories matching the name - factdir = File.join(dir, shortname) - load_dir(factdir) if FileTest.directory?(factdir) - end - end - - # Load all facts from all directories. - def load_all - load_env - - search_path.each do |dir| - Dir.entries(dir).each do |file| - path = File.join(dir, file) - if File.directory?(path) - load_dir(path) - elsif file =~ /\.rb$/ - Kernel.load(File.join(dir, file)) - end - end - end - end - - # The list of directories we're going to search through for facts. - def search_path - result = [] - result += $LOAD_PATH.collect { |d| File.join(d, "facter") } - if ENV.include?("FACTERLIB") - result += ENV["FACTERLIB"].split(":") - end - - if defined?(Puppet) - result << Puppet.settings.value(:factdest) - result << File.join(Puppet.settings.value(:libdir), "facter") - end - result - end - - def old_stuff - # See if we can find any other facts in the regular Ruby lib - # paths - $:.each do |dir| - fdir = File.join(dir, "facter") - if FileTest.exists?(fdir) and FileTest.directory?(fdir) - factdirs.push(fdir) - end - end - # Also check anything in 'FACTERLIB' - if ENV['FACTERLIB'] - ENV['FACTERLIB'].split(":").each do |fdir| - factdirs.push(fdir) - end - end - factdirs.each do |fdir| - Dir.glob("#{fdir}/*.rb").each do |file| - # Load here, rather than require, because otherwise - # the facts won't get reloaded if someone calls - # "loadfacts". Really only important in testing, but, - # well, it's important in testing. - begin - load file - rescue => detail - warn "Could not load %s: %s" % - [file, detail] - end - end - end - - - # Now try to get facts from the environment - ENV.each do |name, value| - if name =~ /^facter_?(\w+)$/i - Facter.add($1) do - setcode { value } - end - end - end - end - - private - - def load_dir(dir) - return if dir =~ /\/util$/ - Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.each do |file| - Kernel.load(File.join(dir, file)) - end - end - - # Load facts from the environment. If no name is provided, - # all will be loaded. - def load_env(fact = nil) - # Load from the environment, if possible - ENV.each do |name, value| - # Skip anything that doesn't match our regex. - next unless name =~ /^facter_?(\w+)$/i - env_name = $1 - - # If a fact name was specified, skip anything that doesn't - # match it. - next if fact and env_name != fact - - Facter.add($1) do - setcode { value } - end - - # Short-cut, if we are only looking for one value. - break if fact - end - end -end diff --git a/lib/facter/resolution.rb b/lib/facter/resolution.rb deleted file mode 100644 index 4df55b7..0000000 --- a/lib/facter/resolution.rb +++ /dev/null @@ -1,107 +0,0 @@ -# An actual fact resolution mechanism. These are largely just chunks of -# code, with optional confinements restricting the mechanisms to only working on -# specific systems. Note that the confinements are always ANDed, so any -# confinements specified must all be true for the resolution to be -# suitable. -require 'facter/confine' - -class Facter::Resolution - attr_accessor :interpreter, :code, :name - - def self.have_which - if @have_which.nil? - %x{which which 2>/dev/null} - @have_which = ($? == 0) - end - @have_which - end - - # Execute a chunk of code. - def self.exec(code, interpreter = "/bin/sh") - raise ArgumentError, "non-sh interpreters are not currently supported" unless interpreter == "/bin/sh" - binary = code.split(/\s+/).shift - - if have_which - path = nil - if binary !~ /^\// - path = %x{which #{binary} 2>/dev/null}.chomp - # we don't have the binary necessary - return nil if path == "" - else - path = binary - end - - return nil unless FileTest.exists?(path) - end - - out = nil - begin - out = %x{#{code}}.chomp - rescue => detail - $stderr.puts detail - return nil - end - if out == "" - return nil - else - return out - end - end - - # Add a new confine to the resolution mechanism. - def confine(confines) - confines.each do |fact, values| - @confines.push Facter::Confine.new(fact, *values) - end - end - - # Create a new resolution mechanism. - def initialize(name) - @name = name - @confines = [] - @value = nil - end - - # Return the number of confines. - def length - @confines.length - end - - # Set our code for returning a value. - def setcode(string = nil, interp = nil, &block) - if string - @code = string - @interpreter = interp || "/bin/sh" - else - unless block_given? - raise ArgumentError, "You must pass either code or a block" - end - @code = block - end - end - - # Is this resolution mechanism suitable on the system in question? - def suitable? - unless defined? @suitable - @suitable = ! @confines.detect { |confine| ! confine.true? } - end - - return @suitable - end - - def to_s - return self.value() - end - - # How we get a value for our resolution mechanism. - def value - if @code.is_a?(Proc) - result = @code.call() - else - result = Facter::Resolution.exec(@code,@interpreter) - end - - return nil if result == "" - return result - end -end diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb new file mode 100644 index 0000000..f76b578 --- /dev/null +++ b/lib/facter/util/collection.rb @@ -0,0 +1,84 @@ +require 'facter' +require 'facter/util/fact' + +# Manage which facts exist and how we access them. Largely just a wrapper +# around a hash of facts. +class Facter::Util::Collection + # Return a fact object by name. If you use this, you still have to call + # 'value' on it to retrieve the actual value. + def [](name) + value(name) + end + + # Add a resolution mechanism for a named fact. This does not distinguish + # between adding a new fact and adding a new way to resolve a fact. + def add(name, options = {}, &block) + name = canonize(name) + + unless fact = @facts[name] + fact = Facter::Util::Fact.new(name, options) + @facts[name] = fact + end + + fact.add(&block) if block + + return fact + end + + include Enumerable + + # Iterate across all of the facts. + def each + @facts.each do |name, fact| + value = fact.value + unless value.nil? + yield name.to_s, value + end + end + end + + # Return a fact by name. + def fact(name) + @facts[canonize(name)] + end + + # Flush all cached values. + def flush + @facts.each { |name, fact| fact.flush } + end + + def initialize + @facts = Hash.new + end + + # Return a list of all of the facts. + def list + return @facts.keys + end + + # Return a hash of all of our facts. + def to_hash + @facts.inject({}) do |h, ary| + value = ary[1].value + if ! value.nil? + # For backwards compatibility, convert the fact name to a string. + h[ary[0].to_s] = value + end + h + end + end + + def value(name) + if fact = @facts[canonize(name)] + fact.value + end + end + + private + + # Provide a consistent means of getting the exact same fact name + # every time. + def canonize(name) + name.to_s.downcase.to_sym + end +end diff --git a/lib/facter/util/confine.rb b/lib/facter/util/confine.rb new file mode 100644 index 0000000..a430bbe --- /dev/null +++ b/lib/facter/util/confine.rb @@ -0,0 +1,41 @@ +# A restricting tag for fact resolution mechanisms. The tag must be true +# for the resolution mechanism to be suitable. +class Facter::Util::Confine + attr_accessor :fact, :values + + # Add the restriction. Requires the fact name, an operator, and the value + # we're comparing to. + def initialize(fact, *values) + raise ArgumentError, "The fact name must be provided" unless fact + raise ArgumentError, "One or more values must be provided" if values.empty? + fact = fact.to_s if fact.is_a? Symbol + @fact = fact + @values = values.collect do |value| + if value.is_a? String + value + else + value.to_s + end + end + end + + def to_s + return "'%s' '%s'" % [@fact, @values.join(",")] + end + + # Evaluate the fact, returning true or false. + def true? + unless fact = Facter[@fact] + Facter.debug "No fact for %s" % @fact + return false + end + value = fact.value + + return false if value.nil? + + @values.each { |v| + return true if value.downcase == v.downcase + } + return false + end +end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb new file mode 100644 index 0000000..c73dfbf --- /dev/null +++ b/lib/facter/util/fact.rb @@ -0,0 +1,102 @@ +require 'facter' +require 'facter/util/resolution' + +class Facter::Util::Fact + attr_accessor :name, :searching, :ldapname + + # Create a new fact, with no resolution mechanisms. + def initialize(name, options = {}) + @name = name.to_s.downcase.intern + + # LAK:NOTE: This is slow for many options, but generally we won't have any and at + # worst we'll have one. If we add more, this should be made more efficient. + options.each do |name, value| + case name + when :ldapname: self.ldapname = value + else + raise ArgumentError, "Invalid fact option '%s'" % name + end + end + + @ldapname ||= @name.to_s + + @resolves = [] + @searching = false + + @value = nil + end + + # Add a new resolution mechanism. This requires a block, which will then + # be evaluated in the context of the new mechanism. + def add(&block) + raise ArgumentError, "You must pass a block to Fact.add" unless block_given? + + resolve = Facter::Util::Resolution.new(@name) + + resolve.instance_eval(&block) + + @resolves << resolve + + # Immediately sort the resolutions, so that we always have + # a sorted list for looking up values. + # We always want to look them up in the order of number of + # confines, so the most restricted resolution always wins. + @resolves.sort! { |a, b| b.length <=> a.length } + + return resolve + end + + # Flush any cached values. + def flush + @value = nil + @suitable = nil + end + + # Return the value for a given fact. Searches through all of the mechanisms + # and returns either the first value or nil. + def value + unless @value + # make sure we don't get stuck in recursive dependency loops + if @searching + Facter.debug "Caught recursion on %s" % @name + + # return a cached value if we've got it + if @value + return @value + else + return nil + end + end + @value = nil + + if @resolves.length == 0 + Facter.debug "No resolves for %s" % @name + return nil + end + + @searching = true + foundsuits = false + @value = @resolves.inject(nil) { |result, resolve| + next unless resolve.suitable? + foundsuits = true + + tmp = resolve.value + + break tmp unless tmp.nil? or tmp == "" + } + @searching = false + + unless foundsuits + Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] + end + end + + if @value.nil? + # nothing + Facter.debug("value for %s is still nil" % @name) + return nil + else + return @value + end + end +end diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb new file mode 100644 index 0000000..7031172 --- /dev/null +++ b/lib/facter/util/loader.rb @@ -0,0 +1,127 @@ +require 'facter' + +# Load facts on demand. +class Facter::Util::Loader + # Load all resolutions for a single fact. + def load(fact) + # Now load from the search path + shortname = fact.to_s.downcase + load_env(shortname) + + filename = shortname + ".rb" + search_path.each do |dir| + # Load individual files + file = File.join(dir, filename) + + # We have to specify Kernel.load, because we have a load method. + Kernel.load(file) if FileTest.exist?(file) + + # And load any directories matching the name + factdir = File.join(dir, shortname) + load_dir(factdir) if FileTest.directory?(factdir) + end + end + + # Load all facts from all directories. + def load_all + load_env + + search_path.each do |dir| + Dir.entries(dir).each do |file| + path = File.join(dir, file) + if File.directory?(path) + load_dir(path) + elsif file =~ /\.rb$/ + Kernel.load(File.join(dir, file)) + end + end + end + end + + # The list of directories we're going to search through for facts. + def search_path + result = [] + result += $LOAD_PATH.collect { |d| File.join(d, "facter") } + if ENV.include?("FACTERLIB") + result += ENV["FACTERLIB"].split(":") + end + + if defined?(Puppet) + result << Puppet.settings.value(:factdest) + result << File.join(Puppet.settings.value(:libdir), "facter") + end + result + end + + def old_stuff + # See if we can find any other facts in the regular Ruby lib + # paths + $:.each do |dir| + fdir = File.join(dir, "facter") + if FileTest.exists?(fdir) and FileTest.directory?(fdir) + factdirs.push(fdir) + end + end + # Also check anything in 'FACTERLIB' + if ENV['FACTERLIB'] + ENV['FACTERLIB'].split(":").each do |fdir| + factdirs.push(fdir) + end + end + factdirs.each do |fdir| + Dir.glob("#{fdir}/*.rb").each do |file| + # Load here, rather than require, because otherwise + # the facts won't get reloaded if someone calls + # "loadfacts". Really only important in testing, but, + # well, it's important in testing. + begin + load file + rescue => detail + warn "Could not load %s: %s" % + [file, detail] + end + end + end + + + # Now try to get facts from the environment + ENV.each do |name, value| + if name =~ /^facter_?(\w+)$/i + Facter.add($1) do + setcode { value } + end + end + end + end + + private + + def load_dir(dir) + return if dir =~ /\/util$/ + Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.each do |file| + Kernel.load(File.join(dir, file)) + end + end + + # Load facts from the environment. If no name is provided, + # all will be loaded. + def load_env(fact = nil) + # Load from the environment, if possible + ENV.each do |name, value| + # Skip anything that doesn't match our regex. + next unless name =~ /^facter_?(\w+)$/i + env_name = $1 + + # If a fact name was specified, skip anything that doesn't + # match it. + next if fact and env_name != fact + + Facter.add($1) do + setcode { value } + end + + # Short-cut, if we are only looking for one value. + break if fact + end + end +end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb new file mode 100644 index 0000000..b6aae77 --- /dev/null +++ b/lib/facter/util/resolution.rb @@ -0,0 +1,107 @@ +# An actual fact resolution mechanism. These are largely just chunks of +# code, with optional confinements restricting the mechanisms to only working on +# specific systems. Note that the confinements are always ANDed, so any +# confinements specified must all be true for the resolution to be +# suitable. +require 'facter/util/confine' + +class Facter::Util::Resolution + attr_accessor :interpreter, :code, :name + + def self.have_which + if @have_which.nil? + %x{which which 2>/dev/null} + @have_which = ($? == 0) + end + @have_which + end + + # Execute a chunk of code. + def self.exec(code, interpreter = "/bin/sh") + raise ArgumentError, "non-sh interpreters are not currently supported" unless interpreter == "/bin/sh" + binary = code.split(/\s+/).shift + + if have_which + path = nil + if binary !~ /^\// + path = %x{which #{binary} 2>/dev/null}.chomp + # we don't have the binary necessary + return nil if path == "" + else + path = binary + end + + return nil unless FileTest.exists?(path) + end + + out = nil + begin + out = %x{#{code}}.chomp + rescue => detail + $stderr.puts detail + return nil + end + if out == "" + return nil + else + return out + end + end + + # Add a new confine to the resolution mechanism. + def confine(confines) + confines.each do |fact, values| + @confines.push Facter::Util::Confine.new(fact, *values) + end + end + + # Create a new resolution mechanism. + def initialize(name) + @name = name + @confines = [] + @value = nil + end + + # Return the number of confines. + def length + @confines.length + end + + # Set our code for returning a value. + def setcode(string = nil, interp = nil, &block) + if string + @code = string + @interpreter = interp || "/bin/sh" + else + unless block_given? + raise ArgumentError, "You must pass either code or a block" + end + @code = block + end + end + + # Is this resolution mechanism suitable on the system in question? + def suitable? + unless defined? @suitable + @suitable = ! @confines.detect { |confine| ! confine.true? } + end + + return @suitable + end + + def to_s + return self.value() + end + + # How we get a value for our resolution mechanism. + def value + if @code.is_a?(Proc) + result = @code.call() + else + result = Facter::Util::Resolution.exec(@code,@interpreter) + end + + return nil if result == "" + return result + end +end -- cgit