From 77991d3a77d17d8f53ae25b0bc9633c9c13db17b Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 6 May 2013 14:54:46 -0700 Subject: Revert "Revert "Revert "Merge branch 'hkenney-ticket/master/2157_remove_facts_dot_d'""" This reverts commit 8fc00ea5b6b39b220ebc6391489915dbeeb364ab. We're restoring facts_dot_d support to stdlib because users are pulling in the latest version of stdlib while on Puppet Enterprise which breaks the operation of PE itself when the fact_stomp_server and fact_stomp_port facts are not defined. They are not defined in PE because PE runs with Facter 1.6.17 and Puppet 2.7.21 --- lib/facter/facter_dot_d.rb | 192 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 lib/facter/facter_dot_d.rb (limited to 'lib') diff --git a/lib/facter/facter_dot_d.rb b/lib/facter/facter_dot_d.rb new file mode 100644 index 0000000..3e528ab --- /dev/null +++ b/lib/facter/facter_dot_d.rb @@ -0,0 +1,192 @@ +# A Facter plugin that loads facts from /etc/facter/facts.d +# and /etc/puppetlabs/facter/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 + retry if require 'rubygems' + raise + 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/facter/facts.d").create +Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create + +# Windows has a different configuration directory that defaults to a vendor +# specific sub directory of the %COMMON_APPDATA% directory. +if Dir.const_defined? 'COMMON_APPDATA' then + windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d') + Facter::Util::DotD.new(windows_facts_dot_d).create +end -- cgit v1.2.3 From 3b887c880c8850ee9fce5531bd36f946a8c82512 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 6 May 2013 16:29:35 -0700 Subject: (#20582) Restore facter_dot_d to stdlib for PE users Without this patch Puppet Enterprise users who install the most recent version of stdlib lose the ability to resolve certain facts critical to the operation of Puppet Enterprise. These facts are defined externally in the file `/etc/puppetlabs/facter/facts.d/puppet_enterprise_installer.txt`. As an example, Puppet Enterprise catalogs fail to compile if the `fact_stomp_server`, and `fact_stomp_port` facts are not defined. `facter_dot_d` was removed from stdlib version 4 because Facter version 1.7 now supports external facts defined in `/etc/puppetlabs/facter/facts.d/puppet_enterprise_installer.txt`. Puppet Enterprise does not yet include Facter 1.7, however. The most recent PE release, 2.8.1, includes Facter 1.6.17. With this version of Facter, users who replace the version of stdlib that ships with PE with the most recent version from the Forge will lose the ability to resolve facts from `/etc/puppetlabs/facter/facts.d/puppet_enterprise_installer.txt`. This patch addresses the problem by detecting if Facter version < 1.7 is loaded. If so, then the facter_dot_d.rb facts will be defined using the stdlib custom fact. If Facter >= 1.7 is being used then stdlib will not define external facts. --- lib/facter/facter_dot_d.rb | 26 +++++++---- spec/unit/facter/pe_required_facts_spec.rb | 69 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 spec/unit/facter/pe_required_facts_spec.rb (limited to 'lib') diff --git a/lib/facter/facter_dot_d.rb b/lib/facter/facter_dot_d.rb index 3e528ab..634a397 100644 --- a/lib/facter/facter_dot_d.rb +++ b/lib/facter/facter_dot_d.rb @@ -181,12 +181,22 @@ class Facter::Util::DotD end end -Facter::Util::DotD.new("/etc/facter/facts.d").create -Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create - -# Windows has a different configuration directory that defaults to a vendor -# specific sub directory of the %COMMON_APPDATA% directory. -if Dir.const_defined? 'COMMON_APPDATA' then - windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d') - Facter::Util::DotD.new(windows_facts_dot_d).create + +mdata = Facter.version.match(/(\d+)\.(\d+)\.(\d+)/) +if mdata + (major, minor, patch) = mdata.captures.map { |v| v.to_i } + if major < 2 + # Facter 1.7 introduced external facts support directly + unless major == 1 and minor > 6 + Facter::Util::DotD.new("/etc/facter/facts.d").create + Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create + + # Windows has a different configuration directory that defaults to a vendor + # specific sub directory of the %COMMON_APPDATA% directory. + if Dir.const_defined? 'COMMON_APPDATA' then + windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d') + Facter::Util::DotD.new(windows_facts_dot_d).create + end + end + end end diff --git a/spec/unit/facter/pe_required_facts_spec.rb b/spec/unit/facter/pe_required_facts_spec.rb new file mode 100644 index 0000000..f219b37 --- /dev/null +++ b/spec/unit/facter/pe_required_facts_spec.rb @@ -0,0 +1,69 @@ +# Puppet Enterprise requires the following facts to be set in order to operate. +# These facts are set using the file ???? and the two facts are +# `fact_stomp_port`, and `fact_stomp_server`. +# + +require 'spec_helper' + +describe "External facts in /etc/puppetlabs/facter/facts.d/puppet_enterprise_installer.txt" do + context "With Facter 1.6.17 which does not have external facts support" do + before :each do + Facter.stubs(:version).returns("1.6.17") + # Stub out the filesystem for stdlib + Dir.stubs(:entries).with("/etc/puppetlabs/facter/facts.d"). + returns(['puppet_enterprise_installer.txt']) + Dir.stubs(:entries).with("/etc/facter/facts.d").returns([]) + File.stubs(:readlines).with('/etc/puppetlabs/facter/facts.d/puppet_enterprise_installer.txt'). + returns([ + "fact_stomp_port=61613\n", + "fact_stomp_server=puppetmaster.acme.com\n", + "fact_is_puppetagent=true\n", + "fact_is_puppetmaster=false\n", + "fact_is_puppetca=false\n", + "fact_is_puppetconsole=false\n", + ]) + if Facter.collection.respond_to? :load + Facter.collection.load(:facter_dot_d) + else + Facter.collection.loader.load(:facter_dot_d) + end + end + + it 'defines fact_stomp_port' do + Facter.fact(:fact_stomp_port).value.should == '61613' + end + it 'defines fact_stomp_server' do + Facter.fact(:fact_stomp_server).value.should == 'puppetmaster.acme.com' + end + it 'defines fact_is_puppetagent' do + Facter.fact(:fact_is_puppetagent).value.should == 'true' + end + it 'defines fact_is_puppetmaster' do + Facter.fact(:fact_is_puppetmaster).value.should == 'false' + end + it 'defines fact_is_puppetca' do + Facter.fact(:fact_is_puppetca).value.should == 'false' + end + it 'defines fact_is_puppetconsole' do + Facter.fact(:fact_is_puppetconsole).value.should == 'false' + end + end + + [ '1.7.1', '2.0.1' ].each do |v| + context "With Facter #{v} which has external facts support" do + before :each do + Facter.stubs(:version).returns(v) + end + + it 'does not call Facter::Util::DotD.new' do + Facter::Util::DotD.expects(:new).never + + if Facter.collection.respond_to? :load + Facter.collection.load(:facter_dot_d) + else + Facter.collection.loader.load(:facter_dot_d) + end + end + end + end +end -- cgit v1.2.3 From 77ea8439fedcb82452a83af78c5378e4d1ee3c10 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 7 May 2013 09:44:43 -0700 Subject: (maint) Indent facter_dot_d with 2 spaces Whitespace only re-flow of facter_dot_d.rb --- lib/facter/facter_dot_d.rb | 250 ++++++++++++++++++++++----------------------- 1 file changed, 125 insertions(+), 125 deletions(-) (limited to 'lib') diff --git a/lib/facter/facter_dot_d.rb b/lib/facter/facter_dot_d.rb index 634a397..e414b20 100644 --- a/lib/facter/facter_dot_d.rb +++ b/lib/facter/facter_dot_d.rb @@ -13,172 +13,172 @@ # fact scripts more often than is needed class Facter::Util::DotD - require 'yaml' + 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 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 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) + def fact_type(file) + extension = File.extname(file) - type = @types[extension] || :unknown + type = @types[extension] || :unknown - type = :script if type == :unknown && File.executable?(file) + type = :script if type == :unknown && File.executable?(file) - return type - end + return type + end - def txt_parser(file) - File.readlines(file).each do |line| - if line =~ /^(.+)=(.+)$/ - var = $1; val = $2 + def txt_parser(file) + File.readlines(file).each do |line| + if line =~ /^(.+)=(.+)$/ + var = $1; val = $2 - Facter.add(var) do - setcode { val } - end - end + Facter.add(var) do + setcode { val } end - rescue Exception => e - Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}") + 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 - retry if require 'rubygems' - raise - end + def json_parser(file) + begin + require 'json' + rescue LoadError + retry if require 'rubygems' + raise + 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}") + 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' + 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}") + 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) + def script_parser(file) + result = cache_lookup(file) + ttl = cache_time(file) - unless result - result = Facter::Util::Resolution.exec(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 + 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 + result.split("\n").each do |line| + if line =~ /^(.+)=(.+)$/ + var = $1; val = $2 - Facter.add(var) do - setcode { val } - end - end + Facter.add(var) do + setcode { val } end - rescue Exception => e - Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}") - Facter.debug(e.backtrace.join("\n\t")) + 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_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 + def cache_store(file, data) + load_cache - @cache[file] = {:data => data, :stored => Time.now.to_i} - rescue - end + @cache[file] = {:data => data, :stored => Time.now.to_i} + rescue + end - def cache_lookup(file) - cache = load_cache + def cache_lookup(file) + cache = load_cache - return nil if cache.empty? + return nil if cache.empty? - ttl = cache_time(file) + ttl = cache_time(file) - if cache[file] - now = Time.now.to_i + 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 + 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" + 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 File.read(meta).chomp.to_i + rescue + return 0 + end - return @cache - rescue + def load_cache + unless @cache + if File.exist?(@cache_file) + @cache = YAML.load_file(@cache_file) + else @cache = {} - return @cache + end end - def create - entries.each do |fact| - type = fact_type(fact) - parser = "#{type}_parser" + return @cache + rescue + @cache = {} + return @cache + end - if respond_to?("#{type}_parser") - Facter.debug("Parsing #{fact} using #{parser}") + def create + entries.each do |fact| + type = fact_type(fact) + parser = "#{type}_parser" - send(parser, fact) - end - end + if respond_to?("#{type}_parser") + Facter.debug("Parsing #{fact} using #{parser}") + + send(parser, fact) + end end + end end -- cgit v1.2.3