diff --git a/CHANGELOG b/CHANGELOG index 7ae069d..da1a439 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +2011-08-17 Puppet Labs - 2.1.0 +* Add R.I. Pienaar's facts.d custom facter fact +* facts defined in /etc/facts.d and /etc/puppetlabs/facts.d are + automatically loaded now. + 2011-08-04 Puppet Labs - 2.0.0 * Rename whole_line to file_line * This is an API change and as such motivating a 2.0.0 release according to semver.org. diff --git a/Modulefile b/Modulefile index dfdfe2e..2ed85cc 100644 --- a/Modulefile +++ b/Modulefile @@ -1,5 +1,5 @@ name 'puppetlabs-stdlib' -version '2.0.0' +version '2.1.0' source 'git://github.com/puppetlabs/puppetlabs-stdlib' author 'puppetlabs' license 'Apache 2.0' diff --git a/README.markdown b/README.markdown index 68186c2..1e682ca 100644 --- a/README.markdown +++ b/README.markdown @@ -49,3 +49,12 @@ before the function is used. $namespace = 'site::data' include "${namespace}" $myvar = getvar("${namespace}::myvar") + +## Facts ## + +Facts in `/etc/facts.d` and `/etc/puppetlabs/facts.d` are now loaded +automatically. This is a direct copy of R.I. Pienaar's custom facter fact +located at: +[https://github.com/ripienaar/facter-facts/tree/master/facts-dot-d](https://github.com/ripienaar/facter-facts/tree/master/facts-dot-d) + + diff --git a/lib/facter/facter_dot_d.rb b/lib/facter/facter_dot_d.rb new file mode 100644 index 0000000..2e5d865 --- /dev/null +++ b/lib/facter/facter_dot_d.rb @@ -0,0 +1,183 @@ +# A Facter plugin that loads facts from /etc/facts.d. +# +# Facts can be in the form of JSON, YAML or Text files +# and any executable that returns key=value pairs. +# +# In the case of scripts you can also create a file that +# contains a cache TTL. For foo.sh store the ttl as just +# a number in foo.sh.ttl +# +# The cache is stored in /tmp/facts_cache.yaml as a mode +# 600 file and will have the end result of not calling your +# fact scripts more often than is needed +class Facter::Util::DotD + require 'yaml' + + def initialize(dir="/etc/facts.d", cache_file="/tmp/facts_cache.yml") + @dir = dir + @cache_file = cache_file + @cache = nil + @types = {".txt" => :txt, ".json" => :json, ".yaml" => :yaml} + end + + def entries + Dir.entries(@dir).reject{|f| f =~ /^\.|\.ttl$/}.sort.map {|f| File.join(@dir, f) } + rescue + [] + end + + def fact_type(file) + extension = File.extname(file) + + type = @types[extension] || :unknown + + type = :script if type == :unknown && File.executable?(file) + + return type + end + + def txt_parser(file) + File.readlines(file).each do |line| + if line =~ /^(.+)=(.+)$/ + var = $1; val = $2 + + Facter.add(var) do + setcode { val } + end + end + end + rescue Exception => e + Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}") + end + + def json_parser(file) + begin + require 'json' + rescue LoadError + require 'rubygems' + retry + end + + JSON.load(File.read(file)).each_pair do |f, v| + Facter.add(f) do + setcode { v } + end + end + rescue Exception => e + Facter.warn("Failed to handle #{file} as json facts: #{e.class}: #{e}") + end + + def yaml_parser(file) + require 'yaml' + + YAML.load_file(file).each_pair do |f, v| + Facter.add(f) do + setcode { v } + end + end + rescue Exception => e + Facter.warn("Failed to handle #{file} as yaml facts: #{e.class}: #{e}") + end + + def script_parser(file) + result = cache_lookup(file) + ttl = cache_time(file) + + unless result + result = Facter::Util::Resolution.exec(file) + + if ttl > 0 + Facter.debug("Updating cache for #{file}") + cache_store(file, result) + cache_save! + end + else + Facter.debug("Using cached data for #{file}") + end + + result.split("\n").each do |line| + if line =~ /^(.+)=(.+)$/ + var = $1; val = $2 + + Facter.add(var) do + setcode { val } + end + end + end + rescue Exception => e + Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}") + Facter.debug(e.backtrace.join("\n\t")) + end + + def cache_save! + cache = load_cache + File.open(@cache_file, "w", 0600) {|f| f.write(YAML.dump(cache)) } + rescue + end + + def cache_store(file, data) + load_cache + + @cache[file] = {:data => data, :stored => Time.now.to_i} + rescue + end + + def cache_lookup(file) + cache = load_cache + + return nil if cache.empty? + + ttl = cache_time(file) + + if cache[file] + now = Time.now.to_i + + return cache[file][:data] if ttl == -1 + return cache[file][:data] if (now - cache[file][:stored]) <= ttl + return nil + else + return nil + end + rescue + return nil + end + + def cache_time(file) + meta = file + ".ttl" + + return File.read(meta).chomp.to_i + rescue + return 0 + end + + def load_cache + unless @cache + if File.exist?(@cache_file) + @cache = YAML.load_file(@cache_file) + else + @cache = {} + end + end + + return @cache + rescue + @cache = {} + return @cache + end + + def create + entries.each do |fact| + type = fact_type(fact) + parser = "#{type}_parser" + + if respond_to?("#{type}_parser") + Facter.debug("Parsing #{fact} using #{parser}") + + send(parser, fact) + end + end + end +end + +Facter::Util::DotD.new("/etc/facts.d").create +Facter::Util::DotD.new("/etc/puppetlabs/facts.d").create