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.
@@ -0,0 +1,330 @@
1
+ require 'concurrent'
2
+
3
+ require 'rfacter'
4
+ require_relative 'config'
5
+ require_relative 'util/non_nullable'
6
+
7
+ # Facter compatibility layer
8
+ #
9
+ # This module exists to provide compatibility shims for Facter DSL methods as
10
+ # exposed by the Facter 3.0 Ruby API. Any fact source code that is executed
11
+ # within the {RFacter::Util} namespace via `instance_eval` should pick up on
12
+ # these shims. The methods in this module should never be called directly.
13
+ #
14
+ # However, lexical scope is a tricky thing, so "should" is the operative word
15
+ # here.
16
+ #
17
+ # @see https://github.com/puppetlabs/facter/blob/master/Extensibility.md
18
+ #
19
+ # @api public
20
+ # @since 0.1.0
21
+ module RFacter::DSL
22
+ # FIXME: Add i18n for the following.
23
+ COLLECTION = RFacter::Util::NonNullable.new(err_message: <<-EOS)
24
+ A Facter DSL method that manipulates a fact collection was called without the
25
+ collection being set. This usually happens if the DSL method is called directly
26
+ instead of via an instance of RFacter::Util::Collection.
27
+ EOS
28
+
29
+ NODE = RFacter::Util::NonNullable.new(err_message: <<-EOS)
30
+ A Facter DSL method that executes shell commands was called without a
31
+ node to execute on being set. This usually happens if the DSL method is called
32
+ directly instead of via an instance of RFacter::Util::Collection.
33
+ EOS
34
+
35
+ # DSL for top-level Facter methods
36
+ #
37
+ # @todo Implement `reset`
38
+ # @todo Implement `search`
39
+ module Facter
40
+ # Returns a fact object by name.
41
+ #
42
+ # If you use this, you still have to call
43
+ # {RFacter::Util::Fact#value `value`} on it to retrieve the actual value.
44
+ #
45
+ # @param name [String, Symbol] the name of the fact
46
+ #
47
+ # @return [RFacter::Util::Fact, nil] The fact object, or nil if no fact
48
+ # is found.
49
+ def self.[](name)
50
+ COLLECTION.value.fact(name)
51
+ end
52
+
53
+ # Adds a {RFacter::Util::Resolution resolution} mechanism for a named
54
+ # fact. This does not distinguish between adding a new fact and adding
55
+ # a new way to resolve a fact.
56
+ #
57
+ # @overload add(name, options = {}, { || ... })
58
+ # @param name [String] the fact name
59
+ # @param options [Hash] optional parameters for the fact - attributes
60
+ # of {RFacter::Util::Fact} and {Facter::Util::Resolution} can be
61
+ # supplied here
62
+ # @option options [Integer] :timeout set the
63
+ # {RFacter::Util::Resolution#timeout timeout} for this resolution
64
+ # @param block [Proc] a block defining a fact resolution
65
+ #
66
+ # @return [RFacter::Util::Fact] the fact object, which includes any previously
67
+ # defined resolutions
68
+ def self.add(name, options = {}, &block)
69
+ COLLECTION.value.add(name, options, &block)
70
+ end
71
+
72
+ # Clears all cached values and removes all facts from memory.
73
+ #
74
+ # @return [void]
75
+ def self.clear
76
+ self.flush
77
+ self.reset
78
+ end
79
+
80
+ # Prints a debug message if debugging is turned on
81
+ #
82
+ # @param msg [String] the debug message
83
+ #
84
+ # @return [void]
85
+ def self.debug(msg)
86
+ ::RFacter::Config.config.logger.debug(msg)
87
+ end
88
+
89
+ # Prints a debug message only once.
90
+ #
91
+ # @note Uniqueness is based on the string, not the specific location
92
+ # of the method call.
93
+ #
94
+ # @param msg [String] the debug message
95
+ # @return [void]
96
+ def self.debugonce(msg)
97
+ ::RFacter::Config.config.logger.debugonce(msg)
98
+ end
99
+
100
+ # Define a new fact or extend an existing fact.
101
+ #
102
+ # @param name [Symbol] The name of the fact to define
103
+ # @param options [Hash] A hash of options to set on the fact
104
+ #
105
+ # @return [RFacter::Util::Fact] The fact that was defined
106
+ #
107
+ # @see {RFacter::Util::Collection#define_fact}
108
+ def self.define_fact(name, options = {}, &block)
109
+ COLLECTION.value.define_fact(name, options, &block)
110
+ end
111
+
112
+ # Iterates over fact names and values
113
+ #
114
+ # @yieldparam [String] name the fact name
115
+ # @yieldparam [String] value the current value of the fact
116
+ #
117
+ # @return [void]
118
+ def self.each
119
+ COLLECTION.value.each(NODE.value) do |*args|
120
+ yield(*args)
121
+ end
122
+ end
123
+
124
+ # (see [])
125
+ def self.fact(name)
126
+ COLLECTION.value.fact(name)
127
+ end
128
+
129
+ # Flushes cached values for all facts. This does not cause code to be
130
+ # reloaded; it only clears the cached results.
131
+ #
132
+ # @return [void]
133
+ def self.flush
134
+ COLLECTION.value.flush
135
+ end
136
+
137
+ # Lists all fact names
138
+ #
139
+ # @return [Array<String>] array of fact names
140
+ def self.list
141
+ COLLECTION.value.list
142
+ end
143
+
144
+ # Loads all facts.
145
+ #
146
+ # @return [void]
147
+ def self.loadfacts
148
+ COLLECTION.value.load_all
149
+ end
150
+
151
+ def self.log_exception(exception, message = nil)
152
+ ::RFacter::Config.config.logger.log_exception(exception, messge)
153
+ end
154
+
155
+ # Removes all facts from memory. Use this when the fact code has
156
+ # changed on disk and needs to be reloaded.
157
+ #
158
+ # @note This is currently a no-op for RFacter pending changes to how
159
+ # collections are handled.
160
+ #
161
+ # @return [void]
162
+ def self.reset
163
+ end
164
+
165
+ # Register directories to be searched for facts. The registered directories
166
+ # must be absolute paths or they will be ignored.
167
+ #
168
+ # @param dirs [String] directories to search
169
+ #
170
+ # @note This is currently a no-op for RFacter pending changes to how
171
+ # collections are handled.
172
+ #
173
+ # @return [void]
174
+ def self.search(*dirs)
175
+ end
176
+
177
+ # Returns the registered search directories.
178
+ #
179
+ # @return [Array<String>] An array of the directories searched
180
+ def self.search_path
181
+ COLLECTION.value.search_path
182
+ end
183
+
184
+ # Gets a hash mapping fact names to their values.
185
+ #
186
+ # @return [Hash{String => Object}] the hash of fact names and values
187
+ def self.to_hash
188
+ COLLECTION.value.to_hash(NODE.value)
189
+ end
190
+
191
+ # Gets the value for a fact.
192
+ #
193
+ # @param name [String, Symbol] the fact name
194
+ #
195
+ # @return [Object, nil] the value of the fact, or nil if no fact is
196
+ # found
197
+ def self.value(name)
198
+ COLLECTION.value.value(name, NODE.value)
199
+ end
200
+
201
+ # Returns the current RFacter version
202
+ #
203
+ # @return [String]
204
+ def self.version
205
+ RFacter::VERSION
206
+ end
207
+
208
+ # Prints a warning message. The message is only printed if debugging
209
+ # is enabled.
210
+ #
211
+ # @param msg [String] the warning message to be printed
212
+ #
213
+ # @return [void]
214
+ def self.warn(msg)
215
+ ::RFacter::Config.config.logger.warn(msg)
216
+ end
217
+
218
+
219
+ # Prints a warning message only once per process. Each unique string
220
+ # is printed once.
221
+ #
222
+ # @note Unlike {warn} the message will be printed even if debugging is
223
+ # not turned on. This behavior is likely to change and should not be
224
+ # relied on.
225
+ #
226
+ # @param msg [String] the warning message to be printed
227
+ #
228
+ # @return [void]
229
+ def self.warnonce(msg)
230
+ ::RFacter::Config.config.logger.warnonce(msg)
231
+ end
232
+
233
+ # Facter::Core DSL methods
234
+ module Core
235
+ require_relative 'core/aggregate'
236
+ # (see RFacter::Core::Aggregate)
237
+ Aggregate = ::RFacter::Core::Aggregate
238
+
239
+ # Shims for Facter::Core::Exection methods
240
+ #
241
+ # @todo Implement execution options
242
+ module Execution
243
+ # Error raised when :on_fail is set
244
+ class ExecutionFailure < StandardError; end
245
+
246
+ # Try to execute a command and return the output.
247
+ #
248
+ # @param command [String] the program to run
249
+ #
250
+ # @return [String] the output of the program, or nil if the command
251
+ # does not exist or could not be executed.
252
+ #
253
+ # @deprecated Use #{execute} instead
254
+ def self.exec(command)
255
+ execute(command, on_fail: nil)
256
+ end
257
+
258
+ # Execute a command and return the output of that program.
259
+ #
260
+ # @param command [String] the program to run
261
+ # @param options [Hash]
262
+ #
263
+ # @option options [Object] :on_fail How to behave when the command
264
+ # could not be run. Specifying `:raise` will raise an error, anything
265
+ # else will return that object on failure. Default is `:raise`.
266
+ #
267
+ # @raise [RFacter::Util::DSL::Facter::Core::Execution::ExecutionFailure]
268
+ # If the command does not exist or could not be executed.
269
+ #
270
+ # @return [String] The stdout of the program.
271
+ #
272
+ # @return [Object] The value of `:on_fail` if command execution failed
273
+ # and `:on_fail` was specified.
274
+ def self.execute(command, on_fail: :raise, **options)
275
+ begin
276
+ output = NODE.value.execute(command).stdout.chomp
277
+ rescue => detail
278
+ if on_fail == :raise
279
+ raise ::RFacter::DSL::Facter::Core::Execution::ExecutionFailure.new,
280
+ "Failed while executing '#{command}': #{detail.message}"
281
+ else
282
+ return on_fail
283
+ end
284
+ end
285
+
286
+ output
287
+ end
288
+
289
+ # Determines the full path to a binary.
290
+ #
291
+ # Returns nil if no matching executable can be found otherwise returns
292
+ # the expanded pathname.
293
+ #
294
+ # @param bin [String] the executable to locate
295
+ #
296
+ # @return [String,nil] the full path to the executable or nil if not
297
+ # found
298
+ def self.which(bin)
299
+ NODE.value.which(bin)
300
+ end
301
+ end
302
+ end
303
+
304
+ # Facter::Util DSL methods
305
+ module Util
306
+ require_relative 'util/fact'
307
+ # (see RFacter::Util::Fact)
308
+ Fact = ::RFacter::Util::Fact
309
+
310
+ require_relative 'util/resolution'
311
+ # (see RFacter::Util::Resolution)
312
+ Resolution = ::RFacter::Util::Resolution
313
+
314
+ # Methods for interacting with remote files.
315
+ #
316
+ # @note The `exists?` part is uniqe to RFacter.
317
+ #
318
+ # @todo Possibly augment this with some top-level shims for File?
319
+ module FileRead
320
+ def self.read(path)
321
+ NODE.value.file(path).content
322
+ end
323
+
324
+ def self.exists?(path)
325
+ NODE.value.file(path).exist?
326
+ end
327
+ end
328
+ end
329
+ end
330
+ end
@@ -0,0 +1,26 @@
1
+ # Fact: kernel
2
+ #
3
+ # Purpose: Returns the operating system's name.
4
+ #
5
+ # Resolution:
6
+ # Uses Ruby's RbConfig to find host_os, if that is a Windows derivative, then
7
+ # returns `windows`, otherwise returns the output of `uname -s` verbatim.
8
+ #
9
+ # Caveats:
10
+ #
11
+
12
+ Facter.add(:kernel) do
13
+ setcode do
14
+ # FIXME: This is a bit naive as winrm could conceivably connect to
15
+ # PowerShell running on POSIX and ssh could connect to a Windows node due
16
+ # to recent investments by Microsoft in Open Source.
17
+ #
18
+ # This also won't work correctly for local execution on a Windows node.
19
+ case NODE.value.scheme
20
+ when 'winrm'
21
+ 'windows'
22
+ else
23
+ Facter::Core::Execution.exec("uname -s")
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # Fact: kernelmajversion
2
+ #
3
+ # Purpose: Return the operating system's release number's major value.
4
+ #
5
+ # Resolution:
6
+ # Takes the first two elements of the kernel version as delimited by periods.
7
+ # Takes the first element of the kernel version on FreeBSD.
8
+ #
9
+ # Caveats:
10
+ #
11
+
12
+ Facter.add("kernelmajversion") do
13
+ setcode do
14
+ Facter.value(:kernelversion).split('.')[0..1].join('.')
15
+ end
16
+ end
17
+
18
+ Facter.add("kernelmajversion") do
19
+ confine :kernel => [:FreeBSD, :SunOS]
20
+ setcode do
21
+ Facter.value(:kernelversion).split('.')[0]
22
+ end
23
+ end
@@ -0,0 +1,41 @@
1
+ # Fact: kernelrelease
2
+ #
3
+ # Purpose: Return the operating system's release number.
4
+ #
5
+ # Resolution:
6
+ # On AIX, returns the output from the `oslevel -s` system command.
7
+ # On Windows-based systems, uses `Get-WmiObject` to query Windows Management
8
+ # for the `Win32_OperatingSystem` value.
9
+ # Otherwise uses the output of `uname -r` system command.
10
+ #
11
+ # Caveats:
12
+ #
13
+ Facter.add(:kernelrelease) do
14
+ setcode 'uname -r'
15
+ end
16
+
17
+ Facter.add(:kernelrelease) do
18
+ confine :kernel => "aix"
19
+ setcode 'oslevel -s'
20
+ end
21
+
22
+ Facter.add("kernelrelease") do
23
+ confine :kernel => :openbsd
24
+ setcode do
25
+ version = Facter::Core::Execution.execute('sysctl -n kern.version')
26
+ version.split(' ')[1]
27
+ end
28
+ end
29
+
30
+ Facter.add(:kernelrelease) do
31
+ confine :kernel => "hp-ux"
32
+ setcode do
33
+ version = Facter::Core::Execution.execute('uname -r')
34
+ version[2..-1]
35
+ end
36
+ end
37
+
38
+ Facter.add(:kernelrelease) do
39
+ confine :kernel => "windows"
40
+ setcode '(Get-WmiObject -Class Win32_OperatingSystem -Property Version).Version'
41
+ end
@@ -0,0 +1,22 @@
1
+ # Fact: kernelversion
2
+ #
3
+ # Purpose: Return the operating system's kernel version.
4
+ #
5
+ # Resolution:
6
+ # On Solaris and SunOS based machines, returns the output of `uname -v`.
7
+ # Otherwise returns the kernerlversion fact up to the first `-`. This may be
8
+ # the entire kernelversion fact in many cases.
9
+ #
10
+ # Caveats:
11
+ #
12
+
13
+ Facter.add("kernelversion") do
14
+ setcode do
15
+ Facter['kernelrelease'].value.split('-')[0]
16
+ end
17
+ end
18
+
19
+ Facter.add("kernelversion") do
20
+ confine :kernel => :sunos
21
+ setcode 'uname -v'
22
+ end
@@ -0,0 +1,130 @@
1
+ # Fact: networking
2
+ #
3
+ # Purpose: Return the system's short hostname.
4
+ #
5
+ # Resolution:
6
+ # On all systems but Darwin, parses the output of the `hostname` system command
7
+ # to everything before the first period.
8
+ # On Darwin, uses the system configuration util to get the LocalHostName
9
+ # variable.
10
+ #
11
+ # Caveats:
12
+ #
13
+
14
+ Facter.add(:networking, :type => :aggregate) do
15
+ chunk(:hostname) do
16
+ name = Facter::Core::Execution.execute('hostname')
17
+
18
+ if name.empty?
19
+ {}
20
+ else
21
+ {'hostname' => name.split('.').first}
22
+ end
23
+ end
24
+
25
+ chunk(:domain) do
26
+ return_value = nil
27
+
28
+ hostname_command = case Facter.value(:kernel)
29
+ when /Linux/i, /FreeBSD/i, /Darwin/i
30
+ 'hostname -f 2> /dev/null'
31
+ else
32
+ 'hostname 2> /dev/null'
33
+ end
34
+
35
+ if name = Facter::Core::Execution.exec(hostname_command) \
36
+ and match = name.match(/.*?\.(.+$)/)
37
+
38
+ return_value = match.captures.first
39
+ elsif domain = Facter::Core::Execution.exec('dnsdomainname 2> /dev/null') \
40
+ and domain.match(/.+/)
41
+
42
+ return_value = domain
43
+ elsif Facter::Util::FileRead.exists?("/etc/resolv.conf")
44
+ domain = nil
45
+ search = nil
46
+ Facter::Util::FileRead.read('/etc/resolv.conf').lines.each do |line|
47
+ if (match = line.match(/^\s*domain\s+(\S+)/))
48
+ domain = match.captures.first
49
+ elsif (match = line.match(/^\s*search\s+(\S+)/))
50
+ search = match.captures.first
51
+ end
52
+ end
53
+ return_value ||= domain
54
+ return_value ||= search
55
+ end
56
+
57
+ if return_value.nil?
58
+ {}
59
+ else
60
+ {'domain' => return_value.gsub(/\.$/, '')}
61
+ end
62
+ end
63
+
64
+ chunk(:fqdn, require: [:hostname, :domain]) do |net_hostname, net_domain|
65
+ host = net_hostname['hostname']
66
+ domain = net_domain['domain']
67
+
68
+ fqdn = if host && domain
69
+ [host, domain].join(".")
70
+ elsif host
71
+ host
72
+ else
73
+ nil
74
+ end
75
+
76
+ if fqdn.nil?
77
+ {}
78
+ else
79
+ {'fqdn' => fqdn}
80
+ end
81
+ end
82
+ end
83
+
84
+ Facter.add(:hostname, :type => :aggregate) do
85
+ confine :kernel => :windows
86
+
87
+ chunk(:hostname) do
88
+ name = Facter::Core::Execution.execute('hostname')
89
+
90
+ if name.empty?
91
+ {}
92
+ else
93
+ {'hostname' => name.split('.').first}
94
+ end
95
+ end
96
+
97
+ chunk(:domain) do
98
+ return_value = nil
99
+
100
+ network_info = Facter::Core::Execution.execute(<<-PS1)
101
+ (Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Property DNSDomain -Filter 'IPEnabled = True'|
102
+ Select-Object -First 1).DNSDomain
103
+ PS1
104
+
105
+ if network_info.empty?
106
+ {}
107
+ else
108
+ {'domain' => network_info.strip}
109
+ end
110
+ end
111
+
112
+ chunk(:fqdn, require: [:hostname, :domain]) do |net_hostname, net_domain|
113
+ host = net_hostname['hostname']
114
+ domain = net_domain['domain']
115
+
116
+ fqdn = if host && domain
117
+ [host, domain].join(".")
118
+ elsif host
119
+ host
120
+ else
121
+ nil
122
+ end
123
+
124
+ if fqdn.nil?
125
+ {}
126
+ else
127
+ {'fqdn' => fqdn}
128
+ end
129
+ end
130
+ end