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
data/lib/rfacter/node.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require 'rfacter'
|
6
|
+
require_relative 'config'
|
7
|
+
|
8
|
+
require 'train'
|
9
|
+
require 'concurrent'
|
10
|
+
|
11
|
+
# Interface to a local or remote host
|
12
|
+
#
|
13
|
+
# @note This class should be refacter to provide an abstracted interface to
|
14
|
+
# different transport backends like Train, Vagrant, Chloride, etc.
|
15
|
+
#
|
16
|
+
# @since 0.1.0
|
17
|
+
class RFacter::Node
|
18
|
+
extend Forwardable
|
19
|
+
|
20
|
+
instance_delegate([:logger] => :@config)
|
21
|
+
|
22
|
+
# @return [URI]
|
23
|
+
attr_reader :uri
|
24
|
+
|
25
|
+
# @return [String]
|
26
|
+
attr_reader :hostname
|
27
|
+
# @return [String]
|
28
|
+
attr_reader :scheme
|
29
|
+
# @return [Integer, nil]
|
30
|
+
attr_reader :port
|
31
|
+
# @return [String, nil]
|
32
|
+
attr_reader :user
|
33
|
+
# @return [String, nil]
|
34
|
+
attr_reader :password
|
35
|
+
# @return [Hash]
|
36
|
+
attr_reader :options
|
37
|
+
|
38
|
+
attr_reader :transport
|
39
|
+
|
40
|
+
def initialize(uri, config: RFacter::Config.config, **opts)
|
41
|
+
@config = config
|
42
|
+
|
43
|
+
@uri = unless uri.is_a?(URI)
|
44
|
+
URI.parse(uri.to_s)
|
45
|
+
else
|
46
|
+
uri
|
47
|
+
end
|
48
|
+
|
49
|
+
@hostname = @uri.hostname || @uri.path
|
50
|
+
@scheme = if @uri.scheme.nil? && (@hostname == 'localhost')
|
51
|
+
'local'
|
52
|
+
elsif @uri.scheme.nil?
|
53
|
+
'ssh'
|
54
|
+
else
|
55
|
+
@uri.scheme
|
56
|
+
end
|
57
|
+
|
58
|
+
case @scheme
|
59
|
+
when 'ssh'
|
60
|
+
@port = @uri.port || 22
|
61
|
+
@user = @uri.user || 'root'
|
62
|
+
when 'winrm'
|
63
|
+
@user = @uri.user || 'Administrator'
|
64
|
+
end
|
65
|
+
|
66
|
+
@password = CGI.unescape(@uri.password) unless @uri.password.nil?
|
67
|
+
@options = @uri.query.nil? ? Hash.new : CGI.parse(@uri.query)
|
68
|
+
@options.update(opts)
|
69
|
+
|
70
|
+
# TODO: This should be abstracted.
|
71
|
+
@transport = Train.create(@scheme,
|
72
|
+
host: @hostname,
|
73
|
+
user: @user,
|
74
|
+
password: @password,
|
75
|
+
port: @port,
|
76
|
+
logger: logger, **@options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# FIXME: For some reason, Train's connection re-use logic isn't working, so a
|
80
|
+
# new connection is being negotiated for each command. File a bug.
|
81
|
+
#
|
82
|
+
# TODO: Ensure connection use is thread-safe.
|
83
|
+
def connection
|
84
|
+
@connection ||= @transport.connection
|
85
|
+
end
|
86
|
+
|
87
|
+
# Execute a command on the node asynchronously
|
88
|
+
#
|
89
|
+
# This method initiates the execution of a command line and returns an
|
90
|
+
# object representing the result.
|
91
|
+
#
|
92
|
+
# @param command [String] The command string to execute.
|
93
|
+
#
|
94
|
+
# @return [Train::Extras::CommandResult] The result of the command including
|
95
|
+
# stdout, stderr and exit code.
|
96
|
+
#
|
97
|
+
# @todo Add support for setting user accounts and environment variables.
|
98
|
+
def execute(command)
|
99
|
+
connection.run_command(command)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Determine if an executable exists and return the path
|
103
|
+
#
|
104
|
+
# @param executable [String] The executable to locate.
|
105
|
+
#
|
106
|
+
# @return [String] The path to the executable if it exists.
|
107
|
+
#
|
108
|
+
# @return [nil] Returned when no matching executable can be located.
|
109
|
+
#
|
110
|
+
# @todo Add support for setting user accounts and environment variables.
|
111
|
+
def which(executable)
|
112
|
+
# TODO: Abstract away from the Train "os" implementation.
|
113
|
+
result = if connection.os.windows?
|
114
|
+
connection.run_command("(Get-Command -TotalCount 1 #{executable}).Path")
|
115
|
+
else
|
116
|
+
connection.run_command("which #{executable}")
|
117
|
+
end
|
118
|
+
|
119
|
+
if (result.exit_status != 0) || (result.stdout.chomp.empty?)
|
120
|
+
nil
|
121
|
+
else
|
122
|
+
result.stdout.chomp
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Interact with remote files in a read-only manner
|
127
|
+
#
|
128
|
+
# This method returns an object that can povide read only access to the stats
|
129
|
+
# and content of a particular file path.
|
130
|
+
#
|
131
|
+
# @param path [String] The file path to interact with.
|
132
|
+
#
|
133
|
+
# @return [Train::Extras::FileCommon] An object representing the remote file.
|
134
|
+
def file(path)
|
135
|
+
connection.file(path)
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'rfacter'
|
4
|
+
require_relative '../config'
|
5
|
+
require_relative '../dsl'
|
6
|
+
require_relative 'loader'
|
7
|
+
require_relative 'fact'
|
8
|
+
|
9
|
+
# Manage which facts exist and how we access them. Largely just a wrapper
|
10
|
+
# around a hash of facts.
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
class RFacter::Util::Collection
|
14
|
+
# Ensures unqualified namespaces like `Facter` and `Facter::Util` get
|
15
|
+
# re-directed to RFacter shims when the loader calls `instance_eval`
|
16
|
+
include RFacter::DSL
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
instance_delegate([:logger] => :@config)
|
20
|
+
|
21
|
+
def initialize(config: RFacter::Config.config, **opts)
|
22
|
+
@config = config
|
23
|
+
@facts = Hash.new
|
24
|
+
@internal_loader = RFacter::Util::Loader.new
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return a fact object by name.
|
28
|
+
def [](name, node)
|
29
|
+
value(name, node)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Define a new fact or extend an existing fact.
|
33
|
+
#
|
34
|
+
# @param name [Symbol] The name of the fact to define
|
35
|
+
# @param options [Hash] A hash of options to set on the fact
|
36
|
+
#
|
37
|
+
# @return [Facter::Util::Fact] The fact that was defined
|
38
|
+
def define_fact(name, options = {}, &block)
|
39
|
+
fact = create_or_return_fact(name, options)
|
40
|
+
|
41
|
+
if block_given?
|
42
|
+
fact.instance_eval(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
fact
|
46
|
+
rescue => e
|
47
|
+
logger.log_exception(e, "Unable to add fact #{name}: #{e}")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a resolution mechanism for a named fact. This does not distinguish
|
51
|
+
# between adding a new fact and adding a new way to resolve a fact.
|
52
|
+
#
|
53
|
+
# @param name [Symbol] The name of the fact to define
|
54
|
+
# @param options [Hash] A hash of options to set on the fact and resolution
|
55
|
+
#
|
56
|
+
# @return [Facter::Util::Fact] The fact that was defined
|
57
|
+
def add(name, options = {}, &block)
|
58
|
+
fact = create_or_return_fact(name, options)
|
59
|
+
|
60
|
+
fact.add(options, &block)
|
61
|
+
|
62
|
+
return fact
|
63
|
+
end
|
64
|
+
|
65
|
+
include Enumerable
|
66
|
+
|
67
|
+
# Iterate across all of the facts.
|
68
|
+
def each(node)
|
69
|
+
load_all
|
70
|
+
|
71
|
+
RFacter::DSL::COLLECTION.bind(self) do
|
72
|
+
RFacter::DSL::NODE.bind(node) do
|
73
|
+
@facts.each do |name, fact|
|
74
|
+
value = fact.value
|
75
|
+
unless value.nil?
|
76
|
+
yield name.to_s, value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return a fact by name.
|
84
|
+
def fact(name)
|
85
|
+
name = canonicalize(name)
|
86
|
+
|
87
|
+
# Try to load the fact if necessary
|
88
|
+
load(name) unless @facts[name]
|
89
|
+
|
90
|
+
# Try HARDER
|
91
|
+
load_all unless @facts[name]
|
92
|
+
|
93
|
+
if @facts.empty?
|
94
|
+
logger.warnonce("No facts loaded from #{@internal_loader.search_path.join(File::PATH_SEPARATOR)}")
|
95
|
+
end
|
96
|
+
|
97
|
+
@facts[name]
|
98
|
+
end
|
99
|
+
|
100
|
+
# Flush all cached values.
|
101
|
+
def flush
|
102
|
+
@facts.each { |name, fact| fact.flush }
|
103
|
+
end
|
104
|
+
|
105
|
+
# Return a list of all of the facts.
|
106
|
+
def list
|
107
|
+
load_all
|
108
|
+
return @facts.keys
|
109
|
+
end
|
110
|
+
|
111
|
+
def load(name)
|
112
|
+
@internal_loader.load(name, self)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Load all known facts.
|
116
|
+
def load_all
|
117
|
+
@internal_loader.load_all(self)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Return a hash of all of our facts.
|
121
|
+
def to_hash(node)
|
122
|
+
@facts.inject({}) do |h, ary|
|
123
|
+
resolved_value = RFacter::DSL::COLLECTION.bind(self) do
|
124
|
+
RFacter::DSL::NODE.bind(node) do
|
125
|
+
ary[1].value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# For backwards compatibility, convert the fact name to a string.
|
130
|
+
h[ary[0].to_s] = resolved_value unless resolved_value.nil?
|
131
|
+
|
132
|
+
h
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def value(name, node)
|
137
|
+
RFacter::DSL::COLLECTION.bind(self) do
|
138
|
+
RFacter::DSL::NODE.bind(node) do
|
139
|
+
if fact = fact(name)
|
140
|
+
fact.value
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def create_or_return_fact(name, options)
|
149
|
+
name = canonicalize(name)
|
150
|
+
|
151
|
+
fact = @facts[name]
|
152
|
+
|
153
|
+
if fact.nil?
|
154
|
+
fact = RFacter::Util::Fact.new(name, options)
|
155
|
+
@facts[name] = fact
|
156
|
+
else
|
157
|
+
fact.extract_ldapname_option!(options)
|
158
|
+
end
|
159
|
+
|
160
|
+
fact
|
161
|
+
end
|
162
|
+
|
163
|
+
def canonicalize(name)
|
164
|
+
name.to_s.downcase.to_sym
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'rfacter'
|
4
|
+
require_relative '../config'
|
5
|
+
require_relative '../dsl'
|
6
|
+
require_relative 'values'
|
7
|
+
|
8
|
+
# A restricting tag for fact resolution mechanisms. The tag must be true
|
9
|
+
# for the resolution mechanism to be suitable.
|
10
|
+
class RFacter::Util::Confine
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
instance_delegate([:logger] => :@config)
|
14
|
+
|
15
|
+
attr_accessor :fact, :values
|
16
|
+
|
17
|
+
include RFacter::Util::Values
|
18
|
+
|
19
|
+
# Add the restriction. Requires the fact name, an operator, and the value
|
20
|
+
# we're comparing to.
|
21
|
+
#
|
22
|
+
# @param fact [Symbol] Name of the fact
|
23
|
+
# @param values [Array] One or more values to match against.
|
24
|
+
# They can be any type that provides a === method.
|
25
|
+
# @param block [Proc] Alternatively a block can be supplied as a check. The fact
|
26
|
+
# value will be passed as the argument to the block. If the block returns
|
27
|
+
# true then the fact will be enabled, otherwise it will be disabled.
|
28
|
+
def initialize(fact = nil, *values, config: RFacter::Config.config, **options, &block)
|
29
|
+
raise ArgumentError, "The fact name must be provided" unless fact or block_given?
|
30
|
+
if values.empty? and not block_given?
|
31
|
+
raise ArgumentError, "One or more values or a block must be provided"
|
32
|
+
end
|
33
|
+
@fact = fact
|
34
|
+
@values = values
|
35
|
+
@config = config
|
36
|
+
@block = block
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
return @block.to_s if @block
|
41
|
+
return "'%s' '%s'" % [@fact, @values.join(",")]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Evaluate the fact, returning true or false.
|
45
|
+
# if we have a block paramter then we only evaluate that instead
|
46
|
+
def true?
|
47
|
+
if @block and not @fact then
|
48
|
+
begin
|
49
|
+
return !! @block.call
|
50
|
+
rescue StandardError => error
|
51
|
+
logger.debug "Confine raised #{error.class} #{error}"
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
unless fact = RFacter::DSL::Facter[@fact]
|
57
|
+
logger.debug "No fact for %s" % @fact
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
value = convert(fact.value)
|
61
|
+
|
62
|
+
return false if value.nil?
|
63
|
+
|
64
|
+
if @block then
|
65
|
+
begin
|
66
|
+
return !! @block.call(value)
|
67
|
+
rescue StandardError => error
|
68
|
+
logger.debug "Confine raised #{error.class} #{error}"
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
return @values.any? do |v| convert(v) === value end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'rfacter'
|
4
|
+
require_relative '../config'
|
5
|
+
|
6
|
+
# This class represents a fact. Each fact has a name and multiple
|
7
|
+
# {Facter::Util::Resolution resolutions}.
|
8
|
+
#
|
9
|
+
# Create facts using {Facter.add}
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
class RFacter::Util::Fact
|
13
|
+
require_relative '../core/aggregate'
|
14
|
+
require_relative 'resolution'
|
15
|
+
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
instance_delegate([:logger] => :@config)
|
19
|
+
|
20
|
+
# The name of the fact
|
21
|
+
# @return [String]
|
22
|
+
attr_accessor :name
|
23
|
+
|
24
|
+
# @return [String]
|
25
|
+
# @deprecated
|
26
|
+
attr_accessor :ldapname
|
27
|
+
|
28
|
+
# Creates a new fact, with no resolution mechanisms. See {Facter.add}
|
29
|
+
# for the public API for creating facts.
|
30
|
+
# @param name [String] the fact name
|
31
|
+
# @param options [Hash] optional parameters
|
32
|
+
# @option options [String] :ldapname set the ldapname property on the fact
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
def initialize(name, config: RFacter::Config.config, **options)
|
36
|
+
@name = name.to_s.downcase.intern
|
37
|
+
@config = config
|
38
|
+
|
39
|
+
extract_ldapname_option!(options)
|
40
|
+
|
41
|
+
@ldapname ||= @name.to_s
|
42
|
+
|
43
|
+
@resolves = []
|
44
|
+
@searching = false
|
45
|
+
|
46
|
+
@value = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Adds a new {Facter::Util::Resolution resolution}. This requires a
|
50
|
+
# block, which will then be evaluated in the context of the new
|
51
|
+
# resolution.
|
52
|
+
#
|
53
|
+
# @param options [Hash] A hash of options to set on the resolution
|
54
|
+
#
|
55
|
+
# @return [Facter::Util::Resolution]
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
def add(options = {}, &block)
|
59
|
+
define_resolution(nil, options, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Define a new named resolution or return an existing resolution with
|
63
|
+
# the given name.
|
64
|
+
#
|
65
|
+
# @param resolution_name [String] The name of the resolve to define or look up
|
66
|
+
# @param options [Hash] A hash of options to set on the resolution
|
67
|
+
# @return [Facter::Util::Resolution]
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def define_resolution(resolution_name, options = {}, &block)
|
71
|
+
|
72
|
+
resolution_type = options.delete(:type) || :simple
|
73
|
+
|
74
|
+
resolve = create_or_return_resolution(resolution_name, resolution_type)
|
75
|
+
|
76
|
+
resolve.set_options(options) unless options.empty?
|
77
|
+
resolve.evaluate(&block) if block
|
78
|
+
|
79
|
+
resolve
|
80
|
+
rescue => e
|
81
|
+
logger.log_exception(e, "Unable to add resolve #{resolution_name.inspect} for fact #{@name}: #{e.message}")
|
82
|
+
end
|
83
|
+
|
84
|
+
# Retrieve an existing resolution by name
|
85
|
+
#
|
86
|
+
# @param name [String]
|
87
|
+
#
|
88
|
+
# @return [Facter::Util::Resolution, nil] The resolution if exists, nil if
|
89
|
+
# it doesn't exist or name is nil
|
90
|
+
def resolution(name)
|
91
|
+
return nil if name.nil?
|
92
|
+
|
93
|
+
@resolves.find { |resolve| resolve.name == name }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Flushes any cached values.
|
97
|
+
#
|
98
|
+
# @return [void]
|
99
|
+
#
|
100
|
+
# @api private
|
101
|
+
def flush
|
102
|
+
@resolves.each { |r| r.flush }
|
103
|
+
@value = nil
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the value for this fact. This searches all resolutions by
|
107
|
+
# suitability and weight (see {Facter::Util::Resolution}). If no
|
108
|
+
# suitable resolution is found, it returns nil.
|
109
|
+
#
|
110
|
+
# @api public
|
111
|
+
def value
|
112
|
+
return @value if @value
|
113
|
+
|
114
|
+
if @resolves.empty?
|
115
|
+
logger.debug("No resolves for #{@name}")
|
116
|
+
return nil
|
117
|
+
end
|
118
|
+
|
119
|
+
searching do
|
120
|
+
|
121
|
+
suitable_resolutions = sort_by_weight(find_suitable_resolutions(@resolves))
|
122
|
+
@value = find_first_real_value(suitable_resolutions)
|
123
|
+
|
124
|
+
announce_when_no_suitable_resolution(suitable_resolutions)
|
125
|
+
announce_when_no_value_found(@value)
|
126
|
+
|
127
|
+
@value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# @api private
|
132
|
+
# @deprecated
|
133
|
+
def extract_ldapname_option!(options)
|
134
|
+
if options[:ldapname]
|
135
|
+
logger.warnonce("ldapname is deprecated and will be removed in a future version")
|
136
|
+
self.ldapname = options.delete(:ldapname)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
# Are we in the midst of a search?
|
143
|
+
def searching?
|
144
|
+
@searching
|
145
|
+
end
|
146
|
+
|
147
|
+
# Lock our searching process, so we never ge stuck in recursion.
|
148
|
+
def searching
|
149
|
+
raise RuntimeError, "Caught recursion on #{@name}" if searching?
|
150
|
+
|
151
|
+
# If we've gotten this far, we're not already searching, so go ahead and do so.
|
152
|
+
@searching = true
|
153
|
+
begin
|
154
|
+
yield
|
155
|
+
ensure
|
156
|
+
@searching = false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def find_suitable_resolutions(resolutions)
|
161
|
+
resolutions.find_all{ |resolve| resolve.suitable? }
|
162
|
+
end
|
163
|
+
|
164
|
+
def sort_by_weight(resolutions)
|
165
|
+
resolutions.sort { |a, b| b.weight <=> a.weight }
|
166
|
+
end
|
167
|
+
|
168
|
+
def find_first_real_value(resolutions)
|
169
|
+
resolutions.each do |resolve|
|
170
|
+
value = resolve.value
|
171
|
+
if not value.nil?
|
172
|
+
return value
|
173
|
+
end
|
174
|
+
end
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
|
178
|
+
def announce_when_no_suitable_resolution(resolutions)
|
179
|
+
if resolutions.empty?
|
180
|
+
logger.debug("Found no suitable resolves of #{@resolves.length} for #{@name}")
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def announce_when_no_value_found(value)
|
185
|
+
if value.nil?
|
186
|
+
logger.debug("value for #{name} is still nil")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def create_or_return_resolution(resolution_name, resolution_type)
|
191
|
+
resolve = self.resolution(resolution_name)
|
192
|
+
|
193
|
+
if resolve
|
194
|
+
if resolution_type != resolve.resolution_type
|
195
|
+
raise ArgumentError, "Cannot return resolution #{resolution_name} with type" +
|
196
|
+
" #{resolution_type}; already defined as #{resolve.resolution_type}"
|
197
|
+
end
|
198
|
+
else
|
199
|
+
case resolution_type
|
200
|
+
when :simple
|
201
|
+
resolve = RFacter::Util::Resolution.new(resolution_name, self)
|
202
|
+
when :aggregate
|
203
|
+
resolve = RFacter::Core::Aggregate.new(resolution_name, self)
|
204
|
+
else
|
205
|
+
raise ArgumentError, "Expected resolution type to be one of (:simple, :aggregate) but was #{resolution_type}"
|
206
|
+
end
|
207
|
+
|
208
|
+
@resolves << resolve
|
209
|
+
end
|
210
|
+
|
211
|
+
resolve
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
require 'rfacter'
|
5
|
+
require_relative '../config'
|
6
|
+
require_relative '../dsl'
|
7
|
+
|
8
|
+
# Load facts on demand.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class RFacter::Util::Loader
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
instance_delegate([:logger] => :@config)
|
15
|
+
|
16
|
+
def initialize(config: RFacter::Config.config, **opts)
|
17
|
+
@config = config
|
18
|
+
@loaded = []
|
19
|
+
end
|
20
|
+
|
21
|
+
# Load all resolutions for a single fact.
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
# @param fact [Symbol]
|
25
|
+
def load(fact, collection)
|
26
|
+
# Now load from the search path
|
27
|
+
shortname = fact.to_s.downcase
|
28
|
+
|
29
|
+
filename = shortname + ".rb"
|
30
|
+
|
31
|
+
paths = search_path
|
32
|
+
unless paths.nil?
|
33
|
+
paths.each do |dir|
|
34
|
+
# Load individual files
|
35
|
+
file = File.join(dir, filename)
|
36
|
+
|
37
|
+
load_file(file, collection) if File.file?(file)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Load all facts from all directories.
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def load_all(collection)
|
46
|
+
return if defined?(@loaded_all)
|
47
|
+
|
48
|
+
paths = search_path
|
49
|
+
unless paths.nil?
|
50
|
+
paths.each do |dir|
|
51
|
+
# dir is already an absolute path
|
52
|
+
Dir.glob(File.join(dir, '*.rb')).each do |path|
|
53
|
+
# exclude dirs that end with .rb
|
54
|
+
load_file(path, collection) if File.file?(path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@loaded_all = true
|
60
|
+
end
|
61
|
+
|
62
|
+
# List directories to search for fact files.
|
63
|
+
#
|
64
|
+
# Search paths are gathered from the following sources:
|
65
|
+
#
|
66
|
+
# 1. A core set of facts from the rfacter/facts directory
|
67
|
+
# 2. ENV['RFACTERLIB'] is split and used verbatim
|
68
|
+
#
|
69
|
+
# A warning will be generated for paths that are not
|
70
|
+
# absolute directories.
|
71
|
+
#
|
72
|
+
# @api public
|
73
|
+
# @return [Array<String>]
|
74
|
+
def search_path
|
75
|
+
search_paths = [File.expand_path('../../facts', __FILE__)]
|
76
|
+
|
77
|
+
if ENV.include?("RFACTERLIB")
|
78
|
+
search_paths += ENV["RFACTERLIB"].split(File::PATH_SEPARATOR)
|
79
|
+
end
|
80
|
+
|
81
|
+
search_paths.delete_if { |path| ! valid_search_path?(path) }
|
82
|
+
|
83
|
+
search_paths.uniq
|
84
|
+
end
|
85
|
+
|
86
|
+
# Validate that the given path is valid, ie it is an absolute path.
|
87
|
+
#
|
88
|
+
# @param path [String]
|
89
|
+
# @return [Boolean]
|
90
|
+
def valid_search_path?(path)
|
91
|
+
Pathname.new(path).absolute? && File.directory?(path)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Load a file and record is paths to prevent duplicate loads.
|
95
|
+
#
|
96
|
+
# @param file [String] The *absolute path* to the file to load
|
97
|
+
def load_file(file, collection)
|
98
|
+
return if @loaded.include? file
|
99
|
+
|
100
|
+
# We have to specify Kernel.load, because we have a load method.
|
101
|
+
begin
|
102
|
+
# Store the file path so we don't try to reload it
|
103
|
+
@loaded << file
|
104
|
+
|
105
|
+
RFacter::DSL::COLLECTION.bind(collection) do
|
106
|
+
collection.instance_eval(File.read(file), file)
|
107
|
+
end
|
108
|
+
rescue Exception => detail
|
109
|
+
# Don't store the path if the file can't be loaded
|
110
|
+
# in case it's loadable later on.
|
111
|
+
@loaded.delete(file)
|
112
|
+
logger.log_exception(detail, "Error loading fact #{file}: #{detail.message}")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|