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