]> git.donarmstrong.com Git - dsa-puppet.git/blob - 3rdparty/modules/stdlib/lib/facter/facter_dot_d.rb
upgrade to stdlib 4.6.1
[dsa-puppet.git] / 3rdparty / modules / stdlib / lib / facter / facter_dot_d.rb
1 # A Facter plugin that loads facts from /etc/facter/facts.d
2 # and /etc/puppetlabs/facter/facts.d.
3 #
4 # Facts can be in the form of JSON, YAML or Text files
5 # and any executable that returns key=value pairs.
6 #
7 # In the case of scripts you can also create a file that
8 # contains a cache TTL.  For foo.sh store the ttl as just
9 # a number in foo.sh.ttl
10 #
11 # The cache is stored in /tmp/facts_cache.yaml as a mode
12 # 600 file and will have the end result of not calling your
13 # fact scripts more often than is needed
14
15 class Facter::Util::DotD
16   require 'yaml'
17
18   def initialize(dir="/etc/facts.d", cache_file=File.join(Puppet[:libdir], "facts_dot_d.cache"))
19     @dir = dir
20     @cache_file = cache_file
21     @cache = nil
22     @types = {".txt" => :txt, ".json" => :json, ".yaml" => :yaml}
23   end
24
25   def entries
26     Dir.entries(@dir).reject { |f| f =~ /^\.|\.ttl$/ }.sort.map { |f| File.join(@dir, f) }
27   rescue
28     []
29   end
30
31   def fact_type(file)
32     extension = File.extname(file)
33
34     type = @types[extension] || :unknown
35
36     type = :script if type == :unknown && File.executable?(file)
37
38     return type
39   end
40
41   def txt_parser(file)
42     File.readlines(file).each do |line|
43       if line =~ /^([^=]+)=(.+)$/
44         var = $1; val = $2
45
46         Facter.add(var) do
47           setcode { val }
48         end
49       end
50     end
51   rescue Exception => e
52     Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}")
53   end
54
55   def json_parser(file)
56     begin
57       require 'json'
58     rescue LoadError
59       retry if require 'rubygems'
60       raise
61     end
62
63     JSON.load(File.read(file)).each_pair do |f, v|
64       Facter.add(f) do
65         setcode { v }
66       end
67     end
68   rescue Exception => e
69     Facter.warn("Failed to handle #{file} as json facts: #{e.class}: #{e}")
70   end
71
72   def yaml_parser(file)
73     require 'yaml'
74
75     YAML.load_file(file).each_pair do |f, v|
76       Facter.add(f) do
77         setcode { v }
78       end
79     end
80   rescue Exception => e
81     Facter.warn("Failed to handle #{file} as yaml facts: #{e.class}: #{e}")
82   end
83
84   def script_parser(file)
85     result = cache_lookup(file)
86     ttl = cache_time(file)
87
88     unless result
89       result = Facter::Util::Resolution.exec(file)
90
91       if ttl > 0
92         Facter.debug("Updating cache for #{file}")
93         cache_store(file, result)
94         cache_save!
95       end
96     else
97       Facter.debug("Using cached data for #{file}")
98     end
99
100     result.split("\n").each do |line|
101       if line =~ /^(.+)=(.+)$/
102         var = $1; val = $2
103
104         Facter.add(var) do
105           setcode { val }
106         end
107       end
108     end
109   rescue Exception => e
110     Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}")
111     Facter.debug(e.backtrace.join("\n\t"))
112   end
113
114   def cache_save!
115     cache = load_cache
116     File.open(@cache_file, "w", 0600) { |f| f.write(YAML.dump(cache)) }
117   rescue
118   end
119
120   def cache_store(file, data)
121     load_cache
122
123     @cache[file] = {:data => data, :stored => Time.now.to_i}
124   rescue
125   end
126
127   def cache_lookup(file)
128     cache = load_cache
129
130     return nil if cache.empty?
131
132     ttl = cache_time(file)
133
134     if cache[file]
135       now = Time.now.to_i
136
137       return cache[file][:data] if ttl == -1
138       return cache[file][:data] if (now - cache[file][:stored]) <= ttl
139       return nil
140     else
141       return nil
142     end
143   rescue
144     return nil
145   end
146
147   def cache_time(file)
148     meta = file + ".ttl"
149
150     return File.read(meta).chomp.to_i
151   rescue
152     return 0
153   end
154
155   def load_cache
156     unless @cache
157       if File.exist?(@cache_file)
158         @cache = YAML.load_file(@cache_file)
159       else
160         @cache = {}
161       end
162     end
163
164     return @cache
165   rescue
166     @cache = {}
167     return @cache
168   end
169
170   def create
171     entries.each do |fact|
172       type = fact_type(fact)
173       parser = "#{type}_parser"
174
175       if respond_to?("#{type}_parser")
176         Facter.debug("Parsing #{fact} using #{parser}")
177
178         send(parser, fact)
179       end
180     end
181   end
182 end
183
184
185 mdata = Facter.version.match(/(\d+)\.(\d+)\.(\d+)/)
186 if mdata
187   (major, minor, patch) = mdata.captures.map { |v| v.to_i }
188   if major < 2
189     # Facter 1.7 introduced external facts support directly
190     unless major == 1 and minor > 6
191       Facter::Util::DotD.new("/etc/facter/facts.d").create
192       Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create
193
194       # Windows has a different configuration directory that defaults to a vendor
195       # specific sub directory of the %COMMON_APPDATA% directory.
196       if Dir.const_defined? 'COMMON_APPDATA' then
197         windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d')
198         Facter::Util::DotD.new(windows_facts_dot_d).create
199       end
200     end
201   end
202 end