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