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 +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
|