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 +4 -4
- data/README.md +4 -0
- data/lib/fqdn_facts/core_ext.rb +59 -1
- data/lib/fqdn_facts/handler.rb +377 -0
- data/lib/fqdn_facts/version.rb +1 -1
- data/lib/fqdn_facts.rb +20 -15
- metadata +3 -3
- data/lib/fqdn_facts/base_handler.rb +0 -279
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37772163234e2641a79f0961962983973d326c04
|
4
|
+
data.tar.gz: 2c232820195afc81d2b6635d38b681389c30822f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
data/lib/fqdn_facts/core_ext.rb
CHANGED
@@ -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
|
data/lib/fqdn_facts/version.rb
CHANGED
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 :
|
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
|
38
|
-
# @param options
|
39
|
-
# @
|
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
|
-
# @
|
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
|
46
|
-
|
49
|
+
unless Handler.const_defined? klass_const
|
50
|
+
Handler.const_set(klass_const, Class.new(parent_class))
|
47
51
|
end
|
48
52
|
|
49
|
-
if
|
50
|
-
unless
|
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] =
|
57
|
+
@registry[klass.to_sym] = Handler.const_get(klass_const).copy_from(parent)
|
54
58
|
else
|
55
|
-
@registry[klass.to_sym] =
|
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
|
-
# @
|
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
|
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-
|
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
|