fqdn_facts 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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