rfacter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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