fqdn_facts 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df9ca38b346ae900a649ac39a6815d26643ff556
4
- data.tar.gz: 1f629c750963efc5f7020baaa237f359e51ad7ae
3
+ metadata.gz: 37772163234e2641a79f0961962983973d326c04
4
+ data.tar.gz: 2c232820195afc81d2b6635d38b681389c30822f
5
5
  SHA512:
6
- metadata.gz: 2b39dc3811576c37969b974bf723578d99bce42897e9f79f4ae2d9f3f4fb5d1ac6426f66c65bdb28c57295a64048eaccf62cb0abc383324ddde1212bafaf9433
7
- data.tar.gz: 2218beac65bf4292abde592da849aece27ee2926715317af70cf5351919315ea7cd1c17d6508335ada84d6f1304ba99e1c3363c9b07522e1586d51bb775d54f9
6
+ metadata.gz: 02d5404556ad9b9ca9e13ad6952da6c6baaf276797c9fa0739a3d545be8b264f9e0b41118443864bb6b48182899da561b4e7da7fac2483a40408d5414db46c06
7
+ data.tar.gz: 6de0e4d9597ec6b5ee6677727d9d0e9f970c33f3026974b4b79ecd4e34a1d85c725e5d2cedd254320cb5d406b0fb4a59bb7def57b8bd3bbc6b37a4e8c57b1b1c
data/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  FqdnFacts allows you to create fact handlers for different FQDN formats. This is primarily intended for use with Puppet/Facter to facilitate dynamic fact generation based on FQDNs.
4
4
 
5
+ ## Status
6
+
7
+ <img src="https://travis-ci.org/rabbitt/fqdn_facts.svg?branch=master" />
8
+
5
9
  ## Installation
6
10
 
7
11
  Add this line to your application's Gemfile:
@@ -21,10 +21,32 @@ class Object
21
21
  # provides an empty? check for all objects
22
22
  # unless an object already has an empty? check
23
23
  #
24
- # @return false
24
+ # @return [false]
25
25
  def empty?
26
26
  return false
27
27
  end
28
+
29
+ # returns true if the object "contains" data
30
+ # (object dependant)
31
+ # @return [Boolean]
32
+ def present?
33
+ !empty?
34
+ end
35
+
36
+ # attempts to call a public method
37
+ #
38
+ # @param method <Symbol> the method to attempt to call
39
+ # @param args [Array] optional arguments to pass to the method
40
+ # @param block [Block] optional block to pass to the method
41
+ #
42
+ # @return the result of the method call, if the method exists, or nil if it doesn't
43
+ def try(method, *args, &block)
44
+ begin
45
+ self.public_send(method, *args, &block)
46
+ rescue NoMethodError
47
+ nil
48
+ end
49
+ end
28
50
  end
29
51
 
30
52
  # @see NilClass
@@ -38,6 +60,10 @@ end
38
60
 
39
61
  # @see String
40
62
  class String
63
+ def empty?
64
+ !!(self !~ /\S/)
65
+ end
66
+
41
67
  # converts a camelized string to underscored
42
68
  # @return String
43
69
  def underscore
@@ -49,7 +75,39 @@ class String
49
75
  def camelize
50
76
  self.split('_').collect(&:capitalize).join
51
77
  end
78
+
79
+ # Attempts to transform string into a class constant
80
+ def constantize(base=Object)
81
+ self.split('/')
82
+ .collect(&:camelize)
83
+ .inject(base) { |obj,klass| obj.const_get(klass) }
84
+ end
85
+ end
86
+
87
+ class Proc
88
+ # @see http://stackoverflow.com/a/10059209/988225
89
+ def call_with_vars(vars, *args)
90
+ begin
91
+ Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)
92
+ rescue NameError => e
93
+ # don't error out - just warn
94
+ file, line = e.backtrace.first.split(':')
95
+ name = e.message.split(/[`']/)[1]
96
+ warn "Couldn't find value for key '#{name}' at #{file}:#{line}"
97
+ end
98
+ end
52
99
  end
53
100
 
101
+ class Symbol
102
+ def include?(needle)
103
+ to_s.include? needle
104
+ end
54
105
 
106
+ def constantize(*args)
107
+ to_s.constantize(*args)
108
+ end
55
109
 
110
+ def split(*args)
111
+ to_s.split(*args)
112
+ end
113
+ end
@@ -0,0 +1,377 @@
1
+ =begin
2
+ Copyright (C) 2015 Carl P. Corliss <rabbitt@gmail.com>
3
+
4
+ This program is free software; you can redistribute it and/or modify
5
+ it under the terms of the GNU General Public License as published by
6
+ the Free Software Foundation; either version 2 of the License, or
7
+ (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public License along
15
+ with this program; if not, write to the Free Software Foundation, Inc.,
16
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
+ =end
18
+
19
+ require_relative 'core_ext'
20
+ require_relative 'errors'
21
+
22
+ module FqdnFacts
23
+ #
24
+ # base class for all handlers.
25
+ #
26
+ class Handler
27
+ class << self
28
+ # Creates a new instance of this object using the internal state
29
+ # of another handler object
30
+ #
31
+ # @param other <Handler> the handler to copy
32
+ # @return <Handler>
33
+ def copy_from(other)
34
+ new(other.export)
35
+ end
36
+ end
37
+
38
+ attr_accessor :priority, :fqdn, :facts
39
+
40
+ # default validation hash of FQDN components
41
+ DEFAULT_COMPONENTS = {
42
+ host: %r'(.+)',
43
+ sub: %r:(.+):,
44
+ tld: %r'(.+)'
45
+ } unless defined? DEFAULT_COMPONENTS
46
+ private_constant :DEFAULT_COMPONENTS
47
+
48
+ # initilalizer
49
+ #
50
+ # @param data [Hash] seeds the handler with base data
51
+ # @option data [Integer] :priority sets the initial priority level
52
+ # @option data [Hash] :conversions sets the initial set of conversions
53
+ # @option data [Hash] :components sets the initial component validations
54
+ # @option data [Array] :order sets the initial order of components
55
+ # @option data [Hash] :facts sets the initial set of facts
56
+ def initialize(data = {})
57
+ @priority = data.delete(:priority) || 1
58
+ @conversions = data.delete(:conversions) || {}
59
+ @components = data.delete(:components) || DEFAULT_COMPONENTS.dup
60
+ @order = data.delete(:order) || DEFAULT_COMPONENTS.keys
61
+ @facts = data.delete(:facts) || {}
62
+ @fqdn = ''
63
+
64
+ add_fact(:fqdn) { fqdn }
65
+ add_fact(:handler_class, self.class.to_s)
66
+ add_fact(:handler_name) { handler_class.split('::').last.underscore }
67
+ end
68
+
69
+ ## DSL Methods
70
+ # @!group DSL
71
+
72
+ # The priority of the registered Fqdn Fact handler
73
+ #
74
+ # Lower priority fqdn fact handlers win out over
75
+ # higer priority handlers.
76
+ #
77
+ # @overload priority(value)
78
+ # Sets the priority level
79
+ # @param value [Integer] higher numbers denote lower priority, lower numbers denote higher
80
+ # @overload priority()
81
+ # Retrieves the current priority level
82
+ # @return Integer
83
+ def priority(value = nil)
84
+ return @priority if value.nil?
85
+ @priority = value.to_i
86
+ end
87
+
88
+ # Defines the order of the defined components
89
+ # that make up the FQDN.
90
+ #
91
+ # This defaults to: host, :sub, :tld
92
+ #
93
+ # @param args <Array<Symbol>> list of ordered components
94
+ # @raise [ArgumentError] if args is empty
95
+ def order(*args)
96
+ raise ArgumentError, 'empty list of components' unless args.present?
97
+ @order = args
98
+ end
99
+ alias_method :components, :order
100
+
101
+ # Define a component's validation rule.
102
+ #
103
+ # Validation rules can be one of the following
104
+ # * :any (the same as /.+/)
105
+ # * A Hash of sub_component => regexp
106
+ # * An array of valid values
107
+ # * A scalar for exact matching
108
+ #
109
+ # @param component <Symbol> the name of the component to set validation for
110
+ # @param validate [Hash{Symbol=>Symbol,Hash,Array,Scalar}] validation to perform for component
111
+ def component(component, validate=:any)
112
+ v = case validate
113
+ when :any then %r:(.+):
114
+ when Hash then
115
+ if @components[component.to_sym].is_a?(Hash)
116
+ basis = @components[component.to_sym]
117
+ else
118
+ basis = {}
119
+ end
120
+
121
+ basis.merge(
122
+ Hash[validate.keys.zip(
123
+ validate.values.collect {|v|
124
+ case v
125
+ when :any then %r:(.+):
126
+ when Regexp then v
127
+ else Regexp.new(v)
128
+ end
129
+ }
130
+ )]
131
+ )
132
+ when Array then
133
+ %r:(#{validate.join('|')}):
134
+ else validate
135
+ end
136
+
137
+ if @components[component.to_sym]
138
+ # if their not the same class, then remove any conversions
139
+ unless @components[component.to_sym].is_a?(v.class)
140
+ @conversions.delete(component.to_sym)
141
+ end
142
+ end
143
+
144
+ @components[component.to_sym] = v
145
+ end
146
+
147
+ # Defines a conversion rule for a given component.
148
+ # The conversion must be a Proc/Lambda
149
+ #
150
+ # @param component <Symbol> the name of the component to set validation for
151
+ # @param conversion [Hash,Proc] optional conversion hash, proc/lambda
152
+ # @param block [Proc] optional block
153
+ # @raise [ArgumentError] if conversion isn't Hash, Proc or Block
154
+ def convert(component, conversion=nil, &block)
155
+ unless [Proc, Hash].any? { |klass| conversion.is_a?(klass) } || block_given?
156
+ raise ArgumentError, 'expected Hash, Proc or Block'
157
+ end
158
+
159
+ component = component.to_sym
160
+ conversion = conversion || block
161
+
162
+ conversion = if conversion.is_a? Hash
163
+ (@conversions[component]||={}).merge(conversion)
164
+ else
165
+ conversion
166
+ end
167
+
168
+ @conversions[component] = conversion
169
+ end
170
+
171
+ # Returns the value of a fact
172
+ #
173
+ # @param name <String,Symbol> name of the fact to retrieve
174
+ def get_fact(name)
175
+ retrieve_facts[name.to_sym]
176
+ end
177
+
178
+ # Adds a fact, either using a static value, or a Proc/Lambda
179
+ # for runtime determination of the value.
180
+ #
181
+ # @param name <String> Symbol name of the fact to add
182
+ # @param value <Scalar,Array,Hash,Proc> value of the fact
183
+ def add_fact(name, value=nil, &block)
184
+ value = block if block_given?
185
+ facts[name.to_sym] = value
186
+ end
187
+
188
+ # Removes a fact from the list of facts.
189
+ # @param name <String,Symbol> name of the fact to remove
190
+ def remove_fact(name)
191
+ facts.delete(name.to_sym)
192
+ end
193
+
194
+ ### End of DSL Methods
195
+ # @!endgroup
196
+
197
+ # retrieve aggregated facts
198
+ #
199
+ # @param options [Hash] options to use when retrieving facts
200
+ # @option options [String] :prefix a string to prefix every fact name with
201
+ # @option options [Array<Symbol>] :only a list of specific facts to retrieve
202
+ #
203
+ # @return [Hash{Symbol=><Scalar,Hash,Array>}]
204
+ def retrieve(options={})
205
+ prefix = options.delete(:prefix)
206
+ only = (o = options.delete(:only)).empty? ? nil : o.collect(&:to_sym)
207
+
208
+ assemble.dup.tap do |facts|
209
+ facts.replace(
210
+ Hash[facts.inject({}) do |hash, (fact, value)|
211
+ next hash unless only.empty? || only.include?(fact)
212
+ key = prefix.empty? ? fact : "#{prefix}_#{fact}"
213
+ hash[key] = value
214
+ hash
215
+ end.sort]
216
+ )
217
+ end
218
+ end
219
+
220
+ # Retrieve all facts, possibly prefixing their names (@see #retrieve)
221
+ # @param prefix <String> a string to prefix every fact name with
222
+ # @return [Hash{Symbol=><Scalar,Hash,Array>}]
223
+ def all(prefix = nil)
224
+ retrieve prefix: prefix
225
+ end
226
+
227
+ # legacy support method
228
+ alias_method :retrieve_facts, :all
229
+
230
+ # @return Hash all aggregate facts
231
+ def to_h
232
+ merge_facts.dup
233
+ end
234
+
235
+ # Checks to see if the fqdn matches this particular FQDN Fact Handler
236
+ # @api private
237
+ def match?(fqdn)
238
+ parts = fqdn.split('.', @order.size)
239
+ return unless parts.size == @order.size
240
+
241
+ debug "Validating #{self.class}:"
242
+
243
+ test_data = @order.zip(parts)
244
+ debug " test data -> #{test_data.inspect}"
245
+
246
+ test_data.all? do |name, value|
247
+ debug " validating component '#{name}'"
248
+ validation = case @components[name]
249
+ when Hash then Regexp.new(@components[name].values.join)
250
+ when nil then %r:.+:
251
+ else @components[name]
252
+ end
253
+
254
+ case validation
255
+ when Regexp then
256
+ (value =~ validation).tap { |r|
257
+ debug " Regexp -> #{value.inspect} =~ #{validation.inspect} == #{r.inspect}"
258
+ }
259
+ when Array then
260
+ validation.include?(value).tap { |r|
261
+ debug " Array -> #{validation.inspect}.include?(#{value.inspect}) == #{r.inspect}"
262
+ }
263
+ else
264
+ (value == validation).tap { |r|
265
+ debug " #{validation.class} -> #{value.inspect} == #{validation.inspect} == #{r.inspect}"
266
+ }
267
+ end
268
+ end.tap { |r| debug " ---> validation #{r ? 'successful' : 'failed'} for #{self.class}"}
269
+ end
270
+
271
+ # Compares the priority of this handler to another handler
272
+ #
273
+ # @param other <Handler> the handler to compare against
274
+ # @return [-1, 0, 1] if other is <=, =, or >= self
275
+ def <=>(other)
276
+ self.priority <=> other.priority
277
+ end
278
+
279
+ # Exports the internal state as a hash
280
+ # @api private
281
+ def export
282
+ instance_variables.inject({}) do |exports, name|
283
+ varname = name.to_s.tr('@', '').to_sym
284
+ exports[varname] = begin
285
+ Marshal.load(Marshal.dump(instance_variable_get(name)))
286
+ rescue TypeError
287
+ instance_variable_get(name).dup
288
+ end
289
+ exports
290
+ end
291
+ end
292
+
293
+ private
294
+
295
+ # Assemble facts from gathered data
296
+ # @return [self]
297
+ # @api private
298
+ def assemble
299
+ { }.tap do |data|
300
+ data.merge!(facts.merge(Hash[fqdn_components]))
301
+
302
+ # expand subtypes
303
+ @components.each do |name, value|
304
+ # components are converted explicitly during expansino, and solely
305
+ # based on their value
306
+ conversion = @conversions[name]
307
+
308
+ if value.is_a? Hash
309
+ subdata = data[name].scan(Regexp.new(value.values.join)).flatten
310
+ value.keys.zip(subdata).each do |subkey, subvalue|
311
+ conversion = @conversions[name].try(:[], subkey)
312
+ data[:"#{name}_#{subkey}"] = convert_value(subvalue, conversion)
313
+ end
314
+ else
315
+ data[name] = convert_value(data[name], conversion)
316
+ end
317
+ end
318
+
319
+ # handle any remaining runtime generated facts
320
+ data.each do |fact, value|
321
+ case value
322
+ when Proc then value = convert_value(data, value)
323
+ when Symbol then value
324
+ else next
325
+ end
326
+ data[fact] = value.is_a?(Symbol) ? value.to_s : value
327
+ end
328
+
329
+ data.reject! { |fact, value| value.empty? }
330
+ end
331
+ end
332
+
333
+ def convert_value(value, converter)
334
+ case converter
335
+ when Proc then
336
+ bind_data = value.is_a?(Hash) ? value.dup : { value: value }
337
+ bind_data.merge!({
338
+ fqdn: fqdn,
339
+ components: Hash[fqdn_components],
340
+ priority: priority,
341
+ handler_class: self.class.name
342
+ })
343
+
344
+ value = if converter.arity == 1
345
+ converter.call_with_vars(bind_data, value)
346
+ else
347
+ converter.call_with_vars(bind_data)
348
+ end
349
+ value.is_a?(Symbol) ? value.to_s : value
350
+
351
+ when :symbol then value.to_sym
352
+ when :integer, Integer then value.to_i
353
+ when :float, Float then value.to_f
354
+ when :string, String then value.to_s
355
+ when :array, Array then Array(value)
356
+ else value
357
+ end
358
+ end
359
+
360
+ # @api private
361
+ def fqdn_data
362
+ fqdn.split('.', @order.size)
363
+ end
364
+
365
+ # @api private
366
+ def fqdn_components
367
+ @order.zip(fqdn_data)
368
+ end
369
+
370
+ # @api private
371
+ def debug(message)
372
+ if ENV.keys.collect(&:downcase).include? 'debug'
373
+ STDERR.puts message
374
+ end
375
+ end
376
+ end
377
+ end
@@ -18,5 +18,5 @@
18
18
 
19
19
  module FqdnFacts
20
20
  # The current FqdnFacts version
21
- VERSION = "0.0.1"
21
+ VERSION = "0.2.0"
22
22
  end
data/lib/fqdn_facts.rb CHANGED
@@ -26,7 +26,7 @@ require 'fqdn_facts/core_ext'
26
26
  module FqdnFacts
27
27
  @registry = {}
28
28
 
29
- autoload :BaseHandler, 'fqdn_facts/base_handler'
29
+ autoload :Handler, 'fqdn_facts/handler'
30
30
  autoload :Error, 'fqdn_facts/errors'
31
31
 
32
32
  class << self
@@ -34,25 +34,29 @@ module FqdnFacts
34
34
 
35
35
  # Registers a FQDN Fact Handler
36
36
  #
37
- # @param klass Symbol name of handler to register
38
- # @param options Hash options to pass
39
- # @param block Block block of DSL code
37
+ # @param klass <Symbol> name of handler to register
38
+ # @param options [Hash] options to pass
39
+ # @option options [Symbol] :copy existing handler to copy definition from
40
+ # @param block [Block] block of DSL code
40
41
  #
41
- # @return FqdnFacts::BaseHandler derived class
42
- #
43
- def register(klass, options={}, &block)
42
+ # @raise [Error::HandlerNotFound] if the requested copy handler doesn't exist
43
+ # @return <Handler>
44
+ def register(klass, options = {}, &block)
45
+ parent_name = (v = options.delete(:copy)).nil? ? :handler : v.to_sym
46
+ parent_class = parent_name == :handler ? Handler : parent_name.constantize(Handler)
47
+
44
48
  klass_const = klass.to_s.camelize
45
- unless BaseHandler.const_defined? klass_const
46
- BaseHandler.const_set(klass_const, Class.new(BaseHandler))
49
+ unless Handler.const_defined? klass_const
50
+ Handler.const_set(klass_const, Class.new(parent_class))
47
51
  end
48
52
 
49
- if other = options.delete(:copy)
50
- unless other = @registry[other.to_sym]
53
+ if parent_name != :handler
54
+ unless parent = @registry[parent_name]
51
55
  fail Error::HandlerNotFound, other
52
56
  end
53
- @registry[klass.to_sym] = BaseHandler.const_get(klass_const).copy_from(other)
57
+ @registry[klass.to_sym] = Handler.const_get(klass_const).copy_from(parent)
54
58
  else
55
- @registry[klass.to_sym] = BaseHandler.const_get(klass_const).new
59
+ @registry[klass.to_sym] = Handler.const_get(klass_const).new
56
60
  end
57
61
 
58
62
 
@@ -68,9 +72,10 @@ module FqdnFacts
68
72
  # Retrieves a handler that is capable of generating facts
69
73
  # for the given FQDN
70
74
  #
71
- # @param fqdn String Fully qualified domain name to resolve into a handler
75
+ # @param fqdn <String> Fully qualified domain name to resolve into a handler
72
76
  #
73
- # @return FqdnFacts::BaseHandler derived class
77
+ # @raise [Error::UnresolveableHandler] if unable to find a handler for the given fqdn
78
+ # @return <Handler>
74
79
  def handler(fqdn)
75
80
  if (handlers = @registry.values.select { |klass| klass.match?(fqdn) }).empty?
76
81
  fail Error::UnresolveableHandler, "unable to find a handler for FQDN:#{fqdn}"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fqdn_facts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl P. Corliss
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-19 00:00:00.000000000 Z
11
+ date: 2015-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -70,9 +70,9 @@ files:
70
70
  - Rakefile
71
71
  - fqdn_facts.gemspec
72
72
  - lib/fqdn_facts.rb
73
- - lib/fqdn_facts/base_handler.rb
74
73
  - lib/fqdn_facts/core_ext.rb
75
74
  - lib/fqdn_facts/errors.rb
75
+ - lib/fqdn_facts/handler.rb
76
76
  - lib/fqdn_facts/version.rb
77
77
  - spec/fqdn_facts_spec.rb
78
78
  - spec/spec_helper.rb
@@ -1,279 +0,0 @@
1
- =begin
2
- Copyright (C) 2015 Carl P. Corliss <rabbitt@gmail.com>
3
-
4
- This program is free software; you can redistribute it and/or modify
5
- it under the terms of the GNU General Public License as published by
6
- the Free Software Foundation; either version 2 of the License, or
7
- (at your option) any later version.
8
-
9
- This program is distributed in the hope that it will be useful,
10
- but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- GNU General Public License for more details.
13
-
14
- You should have received a copy of the GNU General Public License along
15
- with this program; if not, write to the Free Software Foundation, Inc.,
16
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
- =end
18
-
19
- require_relative 'core_ext'
20
- require_relative 'errors'
21
-
22
- module FqdnFacts
23
- #
24
- # base class for all handlers.
25
- #
26
- class BaseHandler
27
- class << self
28
- # Creates a new instance of this object using the internal state
29
- # of another handler object
30
- def copy_from(other)
31
- new(other.export)
32
- end
33
- end
34
-
35
- attr_accessor :priority, :fqdn
36
-
37
- # default validation hash of FQDN components
38
- DEFAULT_COMPONENTS = {
39
- host: %r'(.+)',
40
- sub: %r:(.+):,
41
- tld: %r'(.+)'
42
- } unless defined? DEFAULT_COMPONENTS
43
- private_constant :DEFAULT_COMPONENTS
44
-
45
- # initilalizer
46
- def initialize(data = {})
47
- @priority = data.delete(:priority) || 1
48
- @conversions = data.delete(:conversions) || {}
49
- @components = data.delete(:components) || DEFAULT_COMPONENTS.dup
50
- @component_order = data.delete(:component_order) || DEFAULT_COMPONENTS.keys
51
- @facts = data.delete(:facts) || {}
52
- @fqdn = ''
53
-
54
- add_fact :fqdn, ->() { @fqdn }
55
- add_fact :handler_name, self.class.to_s.split('::').last.underscore
56
- end
57
-
58
- ## DSL Methods
59
- # @!group DSL
60
-
61
- # The priority of the registered Fqdn Fact handler
62
- #
63
- # Lower priority fqdn fact handlers win out over
64
- # higer priority handlers.
65
- #
66
- # @return Integer
67
- def priority(value = nil)
68
- return @priority if value.nil?
69
- @priority = value.to_i
70
- end
71
-
72
- # Defines the order of the defined components
73
- # that make up the FQDN.
74
- #
75
- # This defaults to: host, :sub, :tld
76
- def components(*args)
77
- @component_order = args
78
- end
79
-
80
- # Define a validation rule for a component.
81
- #
82
- # Validation rules can be one of the following
83
- # * :any (the same as /.+/), Hash
84
- # * A Hash of sub_component/regexs
85
- # * An array of valid values
86
- # * a scalar for exact matching
87
- #
88
- # @param component Symbol the name of the component to set validation for
89
- # @param validate Hash{Symbol=>Symbol,Hash,Array,Scalar}
90
- def component(component, validate=:any)
91
- v = case validate
92
- when :any then %r:(.+):
93
- when Hash then
94
- if @components[component.to_sym].is_a?(Hash)
95
- basis = @components[component.to_sym]
96
- else
97
- basis = {}
98
- end
99
-
100
- basis.merge(
101
- Hash[validate.keys.zip(
102
- validate.values.collect {|v|
103
- case v
104
- when :any then %r:(.+):
105
- when Regexp then v
106
- else Regexp.new(v)
107
- end
108
- }
109
- )]
110
- )
111
- when Array then
112
- %r:(#{validate.join('|')}):
113
- else validate
114
- end
115
-
116
- if @components[component.to_sym]
117
- # if their not the same class, then remove any conversions
118
- unless @components[component.to_sym].is_a?(v.class)
119
- @conversions.delete(component.to_sym)
120
- end
121
- end
122
-
123
- @components[component.to_sym] = v
124
- end
125
-
126
- # Defines a conversion rule for a given component.
127
- # The conversion must be a Proc/Lambda
128
- #
129
- # @param component Symbol the name of the component to set validation for
130
- # @param conversion Proc the conversion proc/lambda
131
- def convert(component, conversion)
132
- conversion = case conversion
133
- when Hash then
134
- @conversions[component.to_sym] ||= {}
135
- @conversions[component.to_sym].merge(conversion)
136
- else conversion
137
- end
138
- @conversions[component.to_sym] = conversion
139
- end
140
-
141
- # Returns the value of a fact
142
- #
143
- # @param name String, Symbol name of the fact to retrieve
144
- def get_fact(name)
145
- retrieve_facts[name.to_sym]
146
- end
147
-
148
- # Adds a fact, either using a static value, or a Proc/Lambda
149
- # for runtime determination of the value.
150
- #
151
- # @param name String, Symbol name of the fact to add
152
- # @param value
153
- def add_fact(name, value)
154
- @facts[name.to_sym] = value
155
- end
156
-
157
- # Removes a fact from the list of facts.
158
- def remove_fact(name)
159
- @facts.delete(name.to_sym)
160
- end
161
-
162
- ### End of DSL Methods
163
- # @!endgroup
164
-
165
- # retreives all facts, optionally prefixing the name of each
166
- # with a string value.
167
- def retrieve_facts(prefix = '')
168
- facts = @facts.merge(Hash[fqdn_components])
169
-
170
- # build facts from components
171
- @components.each do |name, value|
172
- case value
173
- when Hash then
174
- data = facts[name].scan(Regexp.new(value.values.join)).flatten
175
- value.keys.zip(data).each do |key, v|
176
- if @conversions[name] && @conversions[name][key]
177
- facts["#{name}_#{key}".to_sym] = @conversions[name][key].call(v)
178
- else
179
- facts["#{name}_#{key}".to_sym] = v
180
- end
181
- end
182
- else
183
- if @conversions[name]
184
- facts[name] = @conversions[name].call(facts[name])
185
- end
186
- @facts.merge!(name.to_sym => value)
187
- end
188
- end
189
-
190
- # handle conversions
191
- facts.each do |key, value|
192
- if value.is_a?(Proc)
193
- value = value.arity == 1 ? value.call(facts) : value.call()
194
- end
195
- facts[key] = value.is_a?(Symbol) ? value.to_s : value
196
- end
197
-
198
- # add prefix
199
- facts.replace(
200
- facts.inject({}) { |h,(k,v)| h["#{prefix}_#{k}"] = v ; h }
201
- ) unless prefix.empty?
202
-
203
- facts.reject { |k,v| v.empty? }
204
- end
205
-
206
- # Checks to see if the fqdn matches this particular FQDN Fact Handler
207
- def match?(fqdn)
208
- parts = fqdn.split('.', @component_order.size)
209
- return unless parts.size == @component_order.size
210
-
211
- debug "Validating #{self.class}:"
212
-
213
- test_data = @component_order.zip(parts)
214
- debug " test data -> #{test_data.inspect}"
215
-
216
- test_data.all? do |name, value|
217
- debug " validating component '#{name}'"
218
- validation = case @components[name]
219
- when Hash then Regexp.new(@components[name].values.join)
220
- when nil then %r:.+:
221
- else @components[name]
222
- end
223
-
224
- case validation
225
- when Regexp then
226
- (value =~ validation).tap { |r|
227
- debug " Regexp -> #{value.inspect} =~ #{validation.inspect} == #{r.inspect}"
228
- }
229
- when Array then
230
- validation.include?(value).tap { |r|
231
- debug " Array -> #{validation.inspect}.include?(#{value.inspect}) == #{r.inspect}"
232
- }
233
- else
234
- (value == validation).tap { |r|
235
- debug " #{validation.class} -> #{value.inspect} == #{validation.inspect} == #{r.inspect}"
236
- }
237
- end
238
- end.tap { |r| debug " ---> validation #{r ? 'successful' : 'failed'} for #{self.class}"}
239
- end
240
-
241
- # Compares the priority of this handler to another handler
242
- def <=>(other)
243
- self.priority <=> other.priority
244
- end
245
-
246
- # Exports the internal state as a hash
247
- def export
248
- instance_variables.inject({}) do |exports, name|
249
- varname = name.to_s.tr('@', '').to_sym
250
- exports[varname] = begin
251
- Marshal.load(Marshal.dump(instance_variable_get(name)))
252
- rescue TypeError
253
- instance_variable_get(name).dup
254
- end
255
- exports
256
- end
257
- end
258
- alias_method :to_h, :export
259
-
260
- private
261
-
262
- # @api private
263
- def fqdn_data
264
- @fqdn.split('.', @component_order.size)
265
- end
266
-
267
- # @api private
268
- def fqdn_components
269
- @component_order.zip(fqdn_data)
270
- end
271
-
272
- # @api private
273
- def debug(message)
274
- if ENV.keys.collect(&:downcase).include? 'debug'
275
- STDERR.puts message
276
- end
277
- end
278
- end
279
- end