rfacter 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/rfacter +5 -0
- data/lib/rfacter/cli.rb +51 -0
- data/lib/rfacter/config/settings.rb +31 -0
- data/lib/rfacter/config.rb +87 -0
- data/lib/rfacter/core/aggregate.rb +228 -0
- data/lib/rfacter/core/directed_graph.rb +48 -0
- data/lib/rfacter/core/resolvable.rb +97 -0
- data/lib/rfacter/core/suitable.rb +114 -0
- data/lib/rfacter/dsl.rb +330 -0
- data/lib/rfacter/facts/kernel.rb +26 -0
- data/lib/rfacter/facts/kernelmajversion.rb +23 -0
- data/lib/rfacter/facts/kernelrelease.rb +41 -0
- data/lib/rfacter/facts/kernelversion.rb +22 -0
- data/lib/rfacter/facts/networking.rb +130 -0
- data/lib/rfacter/facts/os.rb +591 -0
- data/lib/rfacter/node.rb +137 -0
- data/lib/rfacter/util/collection.rb +166 -0
- data/lib/rfacter/util/confine.rb +75 -0
- data/lib/rfacter/util/fact.rb +213 -0
- data/lib/rfacter/util/loader.rb +115 -0
- data/lib/rfacter/util/logger.rb +42 -0
- data/lib/rfacter/util/non_nullable.rb +46 -0
- data/lib/rfacter/util/normalization.rb +96 -0
- data/lib/rfacter/util/resolution.rb +163 -0
- data/lib/rfacter/util/values.rb +110 -0
- data/lib/rfacter/version.rb +3 -0
- data/lib/rfacter.rb +6 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 57a9d18faa5cfba3610ad4e136cc0f6b67674f7e
|
4
|
+
data.tar.gz: ddc6e4a94f5cb61e2ff6133c46d1eb1bda340eed
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e2633c5de708b220c5d8cb5bb39aadb635cce530fe9d7a5d1c228860c82f3f698c54ca4d45f33850fd796914e6a53b69f96f3efaa2908583eb6b3cd2a585c20f
|
7
|
+
data.tar.gz: b6ebc55e51bfb161ef8363e3f1381e9b4ce5d37ef21bfc27eee45b101c055c2a279a747bd36049f2b0c59c5752de9fa8426d4af7931c1e7f9646576a6f198244
|
data/bin/rfacter
ADDED
data/lib/rfacter/cli.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'rfacter'
|
5
|
+
|
6
|
+
require_relative 'config'
|
7
|
+
require_relative 'node'
|
8
|
+
require_relative 'util/collection'
|
9
|
+
|
10
|
+
module RFacter::CLI
|
11
|
+
extend SingleForwardable
|
12
|
+
|
13
|
+
delegate([:logger] => :@config)
|
14
|
+
|
15
|
+
def self.run(argv)
|
16
|
+
names = RFacter::Config.configure_from_argv!(argv)
|
17
|
+
@config = RFacter::Config.config
|
18
|
+
|
19
|
+
if @config.nodes.empty?
|
20
|
+
@config.nodes['localhost'] = RFacter::Node.new('localhost')
|
21
|
+
end
|
22
|
+
|
23
|
+
logger.info('cli::run') { "Configured nodes: #{@config.nodes.values.map(&:hostname)}" }
|
24
|
+
|
25
|
+
collection = RFacter::Util::Collection.new
|
26
|
+
collection.load_all
|
27
|
+
|
28
|
+
facts = @config.nodes.values.inject(Hash.new) do |h, node|
|
29
|
+
node_facts = if names.empty?
|
30
|
+
collection.to_hash(node)
|
31
|
+
else
|
32
|
+
names.inject(Hash.new) do |n, name|
|
33
|
+
n[name] = collection.value(name, node)
|
34
|
+
n
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# TODO: Implement proper per-node Fact caching so that we don't just
|
39
|
+
# reset the colleciton on each loop.
|
40
|
+
collection.flush
|
41
|
+
|
42
|
+
h[node.hostname] = node_facts
|
43
|
+
h
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
puts JSON.pretty_generate(facts)
|
48
|
+
|
49
|
+
exit 0
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rfacter'
|
2
|
+
require_relative '../util/logger'
|
3
|
+
|
4
|
+
# Class for top-level RFacter configuration
|
5
|
+
#
|
6
|
+
# Instances of this class hold top-level configuration values and shared
|
7
|
+
# service objects such as loggers.
|
8
|
+
#
|
9
|
+
# @since 0.1.0
|
10
|
+
class RFacter::Config::Settings
|
11
|
+
# Access the logger instance
|
12
|
+
#
|
13
|
+
# The object stored here should conform to the interface prresented by
|
14
|
+
# the Ruby logger.
|
15
|
+
#
|
16
|
+
# @return [Logger]
|
17
|
+
attr_reader :logger
|
18
|
+
|
19
|
+
# A list of nodes to operate on
|
20
|
+
#
|
21
|
+
# @return [Hash{String => RFacter::Node}] A list of URIs identifying nodes along with the
|
22
|
+
# schemes to use when contacting them.
|
23
|
+
attr_reader :nodes
|
24
|
+
|
25
|
+
def initialize(**options)
|
26
|
+
@logger = RFacter::Util::Logger.new($stderr)
|
27
|
+
@logger.level = Logger::WARN
|
28
|
+
|
29
|
+
@nodes = Hash.new
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'optparse/uri'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
require 'rfacter'
|
6
|
+
|
7
|
+
require_relative 'config/settings'
|
8
|
+
require_relative 'node'
|
9
|
+
|
10
|
+
# Stores and sets global configuration
|
11
|
+
#
|
12
|
+
# This module stores a global instance of {RFacter::Config::Settings}
|
13
|
+
# and contains methods for initializing the settings instance from
|
14
|
+
# various sources.
|
15
|
+
#
|
16
|
+
# @since 0.1.0
|
17
|
+
module RFacter::Config
|
18
|
+
# Return global configuration
|
19
|
+
#
|
20
|
+
# @return [RFacter::Config::Settings]
|
21
|
+
def self.config
|
22
|
+
@settings ||= RFacter::Config::Settings.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set global configuration from an argument vector
|
26
|
+
#
|
27
|
+
# This method calls {.parse_argv} and uses the results to update the
|
28
|
+
# settings instance returned by {.config}.
|
29
|
+
#
|
30
|
+
# @param argv [Array<String>] A list of strings passed as command line
|
31
|
+
# arguments.
|
32
|
+
#
|
33
|
+
# @return [Array<string>] An array of command line arguments that were
|
34
|
+
# not consumed by the parser.
|
35
|
+
def self.configure_from_argv!(argv)
|
36
|
+
args, _ = parse_argv(argv, self.config)
|
37
|
+
|
38
|
+
args
|
39
|
+
end
|
40
|
+
|
41
|
+
# Configure a settings instance by parsing an argument vector
|
42
|
+
#
|
43
|
+
# @param argv [Array<String>] Command line arguments as an array of
|
44
|
+
# strings.
|
45
|
+
#
|
46
|
+
# @param settings [RFacter::Config::Settings, nil] A settings object to
|
47
|
+
# configure. A new object will be created if nothing is passed.
|
48
|
+
#
|
49
|
+
# @return [Array<Array<String>, RFacter::Config::Settings>>] A tuple
|
50
|
+
# containing a configured instance of {RFacter::Config::Settings}
|
51
|
+
# followed by an array of command line arguments that were not consumed
|
52
|
+
# by the parser.
|
53
|
+
def self.parse_argv(argv, settings = nil)
|
54
|
+
settings ||= RFacter::Config::Settings.new
|
55
|
+
parser = OptionParser.new
|
56
|
+
args = argv.dup
|
57
|
+
|
58
|
+
parser.separator("\nOptions\n=======")
|
59
|
+
|
60
|
+
parser.on('--version', 'Print version number and exit.') do
|
61
|
+
puts RFacter::VERSION
|
62
|
+
exit 0
|
63
|
+
end
|
64
|
+
|
65
|
+
parser.on('-h', '--help', 'Print this help message.') do
|
66
|
+
puts parser.help
|
67
|
+
exit 0
|
68
|
+
end
|
69
|
+
|
70
|
+
parser.on('-v', '--verbose', 'Raise log level to INFO.') do
|
71
|
+
settings.logger.level = Logger::INFO
|
72
|
+
end
|
73
|
+
|
74
|
+
parser.on('-d', '--debug', 'Raise log level to DEBUG.') do
|
75
|
+
settings.logger.level = Logger::DEBUG
|
76
|
+
end
|
77
|
+
|
78
|
+
parser.on('-n', '--node', '=MANDATORY', URI, 'Add a node by URI.') do |uri|
|
79
|
+
node = RFacter::Node.new(uri)
|
80
|
+
settings.nodes[node.hostname] = node
|
81
|
+
end
|
82
|
+
|
83
|
+
parser.parse!(args)
|
84
|
+
|
85
|
+
[args, settings]
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'rfacter'
|
4
|
+
require_relative '../config'
|
5
|
+
|
6
|
+
# Aggregates provide a mechanism for facts to be resolved in multiple steps.
|
7
|
+
#
|
8
|
+
# Aggregates are evaluated in two parts: generating individual chunks and then
|
9
|
+
# aggregating all chunks together. Each chunk is a block of code that generates
|
10
|
+
# a value, and may depend on other chunks when it runs. After all chunks have
|
11
|
+
# been evaluated they are passed to the aggregate block as Hash<name, result>.
|
12
|
+
# The aggregate block converts the individual chunks into a single value that is
|
13
|
+
# returned as the final value of the aggregate.
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
# @since 2.0.0
|
17
|
+
class RFacter::Core::Aggregate
|
18
|
+
require_relative 'directed_graph'
|
19
|
+
require_relative 'resolvable'
|
20
|
+
require_relative 'suitable'
|
21
|
+
require_relative '../util/values'
|
22
|
+
|
23
|
+
extend Forwardable
|
24
|
+
|
25
|
+
instance_delegate([:logger] => :@config)
|
26
|
+
|
27
|
+
include RFacter::Core::Suitable
|
28
|
+
include RFacter::Core::Resolvable
|
29
|
+
|
30
|
+
# @!attribute [r] name
|
31
|
+
# @return [Symbol] The name of the aggregate resolution
|
32
|
+
attr_reader :name
|
33
|
+
|
34
|
+
# @!attribute [r] deps
|
35
|
+
# @api private
|
36
|
+
# @return [Facter::Core::DirectedGraph]
|
37
|
+
attr_reader :deps
|
38
|
+
|
39
|
+
# @!attribute [r] confines
|
40
|
+
# @return [Array<Facter::Core::Confine>] An array of confines restricting
|
41
|
+
# this to a specific platform
|
42
|
+
# @see Facter::Core::Suitable
|
43
|
+
attr_reader :confines
|
44
|
+
|
45
|
+
# @!attribute [r] fact
|
46
|
+
# @return [Facter::Util::Fact]
|
47
|
+
# @api private
|
48
|
+
attr_reader :fact
|
49
|
+
|
50
|
+
def initialize(name, fact, config: RFacter::Config.config, **options)
|
51
|
+
@name = name
|
52
|
+
@fact = fact
|
53
|
+
@config = config
|
54
|
+
|
55
|
+
@confines = []
|
56
|
+
@chunks = {}
|
57
|
+
|
58
|
+
@aggregate = nil
|
59
|
+
@deps = RFacter::Core::DirectedGraph.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_options(options)
|
63
|
+
if options[:name]
|
64
|
+
@name = options.delete(:name)
|
65
|
+
end
|
66
|
+
|
67
|
+
if options.has_key?(:timeout)
|
68
|
+
@timeout = options.delete(:timeout)
|
69
|
+
end
|
70
|
+
|
71
|
+
if options.has_key?(:weight)
|
72
|
+
@weight = options.delete(:weight)
|
73
|
+
end
|
74
|
+
|
75
|
+
if not options.keys.empty?
|
76
|
+
raise ArgumentError, "Invalid aggregate options #{options.keys.inspect}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def evaluate(&block)
|
81
|
+
instance_eval(&block)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Define a new chunk for the given aggregate
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
#
|
88
|
+
# @example Defining a chunk with no dependencies
|
89
|
+
# aggregate.chunk(:mountpoints) do
|
90
|
+
# # generate mountpoint information
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# @example Defining an chunk to add mount options
|
94
|
+
# aggregate.chunk(:mount_options, :require => [:mountpoints]) do |mountpoints|
|
95
|
+
# # `mountpoints` is the result of the previous chunk
|
96
|
+
# # generate mount option information based on the mountpoints
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# @param name [Symbol] A name unique to this aggregate describing the chunk
|
100
|
+
# @param opts [Hash]
|
101
|
+
# @option opts [Array<Symbol>, Symbol] require One or more chunks to evaluate
|
102
|
+
# and pass to this block.
|
103
|
+
# @yield [*Object] Zero or more chunk results
|
104
|
+
#
|
105
|
+
# @return [void]
|
106
|
+
def chunk(name, opts = {}, &block)
|
107
|
+
if not block_given?
|
108
|
+
raise ArgumentError, "#{self.class.name}#chunk requires a block"
|
109
|
+
end
|
110
|
+
|
111
|
+
deps = Array(opts.delete(:require))
|
112
|
+
|
113
|
+
if not opts.empty?
|
114
|
+
raise ArgumentError, "Unexpected options passed to #{self.class.name}#chunk: #{opts.keys.inspect}"
|
115
|
+
end
|
116
|
+
|
117
|
+
@deps[name] = deps
|
118
|
+
@chunks[name] = block
|
119
|
+
end
|
120
|
+
|
121
|
+
# Define how all chunks should be combined
|
122
|
+
#
|
123
|
+
# @api public
|
124
|
+
#
|
125
|
+
# @example Merge all chunks
|
126
|
+
# aggregate.aggregate do |chunks|
|
127
|
+
# final_result = {}
|
128
|
+
# chunks.each_value do |chunk|
|
129
|
+
# final_result.deep_merge(chunk)
|
130
|
+
# end
|
131
|
+
# final_result
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# @example Sum all chunks
|
135
|
+
# aggregate.aggregate do |chunks|
|
136
|
+
# total = 0
|
137
|
+
# chunks.each_value do |chunk|
|
138
|
+
# total += chunk
|
139
|
+
# end
|
140
|
+
# total
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# @yield [Hash<Symbol, Object>] A hash containing chunk names and
|
144
|
+
# chunk values
|
145
|
+
#
|
146
|
+
# @return [void]
|
147
|
+
def aggregate(&block)
|
148
|
+
if block_given?
|
149
|
+
@aggregate = block
|
150
|
+
else
|
151
|
+
raise ArgumentError, "#{self.class.name}#aggregate requires a block"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def resolution_type
|
156
|
+
:aggregate
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# Evaluate the results of this aggregate.
|
162
|
+
#
|
163
|
+
# @see Facter::Core::Resolvable#value
|
164
|
+
# @return [Object]
|
165
|
+
def resolve_value
|
166
|
+
chunk_results = run_chunks()
|
167
|
+
aggregate_results(chunk_results)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Order all chunks based on their dependencies and evaluate each one, passing
|
171
|
+
# dependent chunks as needed.
|
172
|
+
#
|
173
|
+
# @return [Hash<Symbol, Object>] A hash containing the chunk that
|
174
|
+
# generated value and the related value.
|
175
|
+
def run_chunks
|
176
|
+
results = {}
|
177
|
+
order_chunks.each do |(name, block)|
|
178
|
+
input = @deps[name].map { |dep_name| results[dep_name] }
|
179
|
+
|
180
|
+
output = block.call(*input)
|
181
|
+
results[name] = RFacter::Util::Values.deep_freeze(output)
|
182
|
+
end
|
183
|
+
|
184
|
+
results
|
185
|
+
end
|
186
|
+
|
187
|
+
# Process the results of all chunks with the aggregate block and return the
|
188
|
+
# results. If no aggregate block has been specified, fall back to deep
|
189
|
+
# merging the given data structure
|
190
|
+
#
|
191
|
+
# @param results [Hash<Symbol, Object>] A hash of chunk names and the output
|
192
|
+
# of that chunk.
|
193
|
+
# @return [Object]
|
194
|
+
def aggregate_results(results)
|
195
|
+
if @aggregate
|
196
|
+
@aggregate.call(results)
|
197
|
+
else
|
198
|
+
default_aggregate(results)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def default_aggregate(results)
|
203
|
+
results.values.inject do |result, current|
|
204
|
+
RFacter::Util::Values.deep_merge(result, current)
|
205
|
+
end
|
206
|
+
rescue RFacter::Util::Values::DeepMergeError => e
|
207
|
+
raise ArgumentError, "Could not deep merge all chunks (Original error: " +
|
208
|
+
"#{e.message}), ensure that chunks return either an Array or Hash or " +
|
209
|
+
"override the aggregate block", e.backtrace
|
210
|
+
end
|
211
|
+
|
212
|
+
# Order chunks based on their dependencies
|
213
|
+
#
|
214
|
+
# @return [Array<Symbol, Proc>] A list of chunk names and blocks in evaluation order.
|
215
|
+
def order_chunks
|
216
|
+
if not @deps.acyclic?
|
217
|
+
raise DependencyError, "Could not order chunks; found the following dependency cycles: #{@deps.cycles.inspect}"
|
218
|
+
end
|
219
|
+
|
220
|
+
sorted_names = @deps.tsort
|
221
|
+
|
222
|
+
sorted_names.map do |name|
|
223
|
+
[name, @chunks[name]]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class DependencyError < StandardError; end
|
228
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'tsort'
|
3
|
+
|
4
|
+
require 'rfacter'
|
5
|
+
|
6
|
+
module RFacter
|
7
|
+
module Core
|
8
|
+
class DirectedGraph < Hash
|
9
|
+
include TSort
|
10
|
+
|
11
|
+
def acyclic?
|
12
|
+
cycles.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def cycles
|
16
|
+
cycles = []
|
17
|
+
each_strongly_connected_component do |component|
|
18
|
+
cycles << component if component.size > 1
|
19
|
+
end
|
20
|
+
cycles
|
21
|
+
end
|
22
|
+
|
23
|
+
alias tsort_each_node each_key
|
24
|
+
|
25
|
+
def tsort_each_child(node)
|
26
|
+
fetch(node, []).each do |child|
|
27
|
+
yield child
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def tsort
|
32
|
+
missing = Set.new(self.values.flatten) - Set.new(self.keys)
|
33
|
+
|
34
|
+
if not missing.empty?
|
35
|
+
raise MissingVertex, "Cannot sort elements; cannot depend on missing elements #{missing.to_a}"
|
36
|
+
end
|
37
|
+
|
38
|
+
super
|
39
|
+
|
40
|
+
rescue TSort::Cyclic
|
41
|
+
raise CycleError, "Cannot sort elements; found the following cycles: #{cycles.inspect}"
|
42
|
+
end
|
43
|
+
|
44
|
+
class CycleError < StandardError; end
|
45
|
+
class MissingVertex < StandardError; end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
require 'rfacter'
|
4
|
+
require_relative '../util/normalization'
|
5
|
+
|
6
|
+
# The resolvable mixin defines behavior for evaluating and returning fact
|
7
|
+
# resolutions.
|
8
|
+
#
|
9
|
+
# Classes including this mixin should implement at #name method describing
|
10
|
+
# the value being resolved and a #resolve_value that actually executes the code
|
11
|
+
# to resolve the value.
|
12
|
+
module RFacter::Core::Resolvable
|
13
|
+
|
14
|
+
# The timeout, in seconds, for evaluating this resolution.
|
15
|
+
# @return [Integer]
|
16
|
+
# @api public
|
17
|
+
attr_accessor :timeout
|
18
|
+
|
19
|
+
# Return the timeout period for resolving a value.
|
20
|
+
# (see #timeout)
|
21
|
+
# @return [Numeric]
|
22
|
+
# @comment requiring 'timeout' stdlib class causes Object#timeout to be
|
23
|
+
# defined which delegates to Timeout.timeout. This method may potentially
|
24
|
+
# overwrite the #timeout attr_reader on this class, so we define #limit to
|
25
|
+
# avoid conflicts.
|
26
|
+
def limit
|
27
|
+
@timeout || 0
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# on_flush accepts a block and executes the block when the resolution's value
|
32
|
+
# is flushed. This makes it possible to model a single, expensive system
|
33
|
+
# call inside of a Ruby object and then define multiple dynamic facts which
|
34
|
+
# resolve by sending messages to the model instance. If one of the dynamic
|
35
|
+
# facts is flushed then it can, in turn, flush the data stored in the model
|
36
|
+
# instance to keep all of the dynamic facts in sync without making multiple,
|
37
|
+
# expensive, system calls.
|
38
|
+
#
|
39
|
+
# Please see the Solaris zones fact for an example of how this feature may be
|
40
|
+
# used.
|
41
|
+
#
|
42
|
+
# @see Facter::Util::Fact#flush
|
43
|
+
# @see Facter::Util::Resolution#flush
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def on_flush(&block)
|
47
|
+
@on_flush_block = block
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# flush executes the block, if any, stored by the {on_flush} method
|
52
|
+
#
|
53
|
+
# @see Facter::Util::Fact#flush
|
54
|
+
# @see Facter::Util::Resolution#on_flush
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
def flush
|
58
|
+
@on_flush_block.call if @on_flush_block
|
59
|
+
end
|
60
|
+
|
61
|
+
def value
|
62
|
+
result = nil
|
63
|
+
|
64
|
+
with_timing do
|
65
|
+
Timeout.timeout(limit) do
|
66
|
+
result = resolve_value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
RFacter::Util::Normalization.normalize(result)
|
71
|
+
rescue Timeout::Error => detail
|
72
|
+
logger.log_exception(detail, "Timed out after #{limit} seconds while resolving #{qualified_name}")
|
73
|
+
return nil
|
74
|
+
rescue RFacter::Util::Normalization::NormalizationError => detail
|
75
|
+
logger.log_exception(detail, "Fact resolution #{qualified_name} resolved to an invalid value: #{detail.message}")
|
76
|
+
return nil
|
77
|
+
rescue => detail
|
78
|
+
logger.log_exception(detail, "Could not retrieve #{qualified_name}: #{detail.message}")
|
79
|
+
return nil
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def with_timing
|
85
|
+
starttime = Time.now.to_f
|
86
|
+
|
87
|
+
yield
|
88
|
+
|
89
|
+
finishtime = Time.now.to_f
|
90
|
+
ms = (finishtime - starttime) * 1000
|
91
|
+
#::Facter.show_time "#{qualified_name}: #{"%.2f" % ms}ms"
|
92
|
+
end
|
93
|
+
|
94
|
+
def qualified_name
|
95
|
+
"fact='#{@fact.name.to_s}', resolution='#{@name || '<anonymous>'}'"
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'rfacter'
|
2
|
+
|
3
|
+
# The Suitable mixin provides mechanisms for confining objects to run on
|
4
|
+
# certain platforms and determining the run precedence of these objects.
|
5
|
+
#
|
6
|
+
# Classes that include the Suitable mixin should define a `#confines` method
|
7
|
+
# that returns an Array of zero or more Facter::Util::Confine objects.
|
8
|
+
module RFacter::Core::Suitable
|
9
|
+
require_relative '../util/confine'
|
10
|
+
|
11
|
+
attr_writer :weight
|
12
|
+
|
13
|
+
# Sets the weight of this resolution. If multiple suitable resolutions
|
14
|
+
# are found, the one with the highest weight will be used. If weight
|
15
|
+
# is not given, the number of confines set on a resolution will be
|
16
|
+
# used as its weight (so that the most specific resolution is used).
|
17
|
+
#
|
18
|
+
# @param weight [Integer] the weight of this resolution
|
19
|
+
#
|
20
|
+
# @return [void]
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
def has_weight(weight)
|
24
|
+
@weight = weight
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sets the conditions for this resolution to be used. This method accepts
|
28
|
+
# multiple forms of arguments to determine suitability.
|
29
|
+
#
|
30
|
+
# @return [void]
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
#
|
34
|
+
# @overload confine(confines)
|
35
|
+
# Confine a fact to a specific fact value or values. This form takes a
|
36
|
+
# hash of fact names and values. Every fact must match the values given for
|
37
|
+
# that fact, otherwise this resolution will not be considered suitable. The
|
38
|
+
# values given for a fact can be an array, in which case the value of the
|
39
|
+
# fact must be in the array for it to match.
|
40
|
+
# @param [Hash{String,Symbol=>String,Array<String>}] confines set of facts identified by the hash keys whose
|
41
|
+
# fact value must match the argument value.
|
42
|
+
# @example Confining to Linux
|
43
|
+
# Facter.add(:powerstates) do
|
44
|
+
# # This resolution only makes sense on linux systems
|
45
|
+
# confine :kernel => "Linux"
|
46
|
+
# setcode do
|
47
|
+
# File.read('/sys/power/states')
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# @overload confine(confines, &block)
|
52
|
+
# Confine a fact to a block with the value of a specified fact yielded to
|
53
|
+
# the block.
|
54
|
+
# @param [String,Symbol] confines the fact name whose value should be
|
55
|
+
# yielded to the block
|
56
|
+
# @param [Proc] block determines the suitability of the fact. If the block
|
57
|
+
# evaluates to `false` or `nil` then the confined fact will not be
|
58
|
+
# evaluated.
|
59
|
+
# @yield [value] the value of the fact identified by {confines}
|
60
|
+
# @example Confine the fact to a host with an ipaddress in a specific
|
61
|
+
# subnet
|
62
|
+
# confine :ipaddress do |addr|
|
63
|
+
# require 'ipaddr'
|
64
|
+
# IPAddr.new('192.168.0.0/16').include? addr
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# @overload confine(&block)
|
68
|
+
# Confine a fact to a block. The fact will be evaluated only if the block
|
69
|
+
# evaluates to something other than `false` or `nil`.
|
70
|
+
# @param [Proc] block determines the suitability of the fact. If the block
|
71
|
+
# evaluates to `false` or `nil` then the confined fact will not be
|
72
|
+
# evaluated.
|
73
|
+
# @example Confine the fact to systems with a specific file.
|
74
|
+
# confine { File.exist? '/bin/foo' }
|
75
|
+
def confine(confines = nil, &block)
|
76
|
+
case confines
|
77
|
+
when Hash
|
78
|
+
confines.each do |fact, values|
|
79
|
+
@confines.push RFacter::Util::Confine.new(fact, *values)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
if block
|
83
|
+
if confines
|
84
|
+
@confines.push RFacter::Util::Confine.new(confines, &block)
|
85
|
+
else
|
86
|
+
@confines.push RFacter::Util::Confine.new(&block)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns the importance of this resolution. If the weight was not
|
94
|
+
# given, the number of confines is used instead (so that a more
|
95
|
+
# specific resolution wins over a less specific one).
|
96
|
+
#
|
97
|
+
# @return [Integer] the weight of this resolution
|
98
|
+
#
|
99
|
+
# @api private
|
100
|
+
def weight
|
101
|
+
if @weight
|
102
|
+
@weight
|
103
|
+
else
|
104
|
+
@confines.length
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Is this resolution mechanism suitable on the system in question?
|
109
|
+
#
|
110
|
+
# @api private
|
111
|
+
def suitable?
|
112
|
+
@confines.all? { |confine| confine.true? }
|
113
|
+
end
|
114
|
+
end
|