Skip to content

Commit 0f4b47c

Browse files
committed
Update OpenFact
- add missing facts-group in facter config - switch examples from legacy to structured facts - add information on writing facts on windows - timeout der fact - hint on Facter::Execution.exec deprecation - add information on logging Signed-off-by: Martin Alfke <ma@betadots.de>
1 parent 75e0dbb commit 0f4b47c

4 files changed

Lines changed: 191 additions & 18 deletions

File tree

docs/_openfact_5x/configuring_openfact.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ toc_levels: 1
44
title: "Configuring OpenFact with facter.conf"
55
---
66

7-
The `facter.conf` file is a configuration file that allows you to cache and block fact groups, and manage how OpenFact interacts with your system. There are three sections: `facts`, `global` and `cli`.
7+
The `facter.conf` file is a configuration file that allows you to cache and block fact groups, and manage how OpenFact interacts with your system. There are three sections: `facts`, `global`, `cli` and `facts-group`.
88
All sections are optional and can be listed in any order within the file.
99

1010
When you run OpenFact from the Ruby API, only the `facts` section and limited `global` settings are loaded.
1111

1212
Example facter.conf file:
1313

14-
~~~yaml
14+
~~~hocon
1515
facts : {
1616
blocklist : [ "file system", "EC2" ],
1717
ttls : [
@@ -32,6 +32,9 @@ cli : {
3232
verbose : false,
3333
log-level : "warn"
3434
}
35+
fact-groups : {
36+
custom-group-name : ["os.name", "networking.ip"],
37+
}
3538
~~~
3639

3740
## Location
@@ -79,14 +82,29 @@ file system
7982

8083
If you want to block any of these groups, add the group name to the `facts` section of `facter.conf`, with the `blocklist` setting.
8184

82-
~~~yaml
85+
~~~hocon
8386
facts : {
8487
blocklist : [ "file system" ],
8588
}
8689
~~~
8790

8891
Here, the "file system" group has been added, so the `mountpoints`, `filesystems`, and `partitions` facts will all be prevented from loading.
8992

93+
Within the `blocklist` and `ttls` settings, one can specify fact names:
94+
95+
~~~hocon
96+
facts : {
97+
blocklist : [ "disks", "memory.swap" ],
98+
}
99+
~~~
100+
101+
~~~hocon
102+
ttls : [
103+
{ "mountpoints" : 30 days },
104+
{ "memory.swap" : 6 hours },
105+
]
106+
~~~~
107+
90108
### `global`
91109
92110
The `global` section of `facter.conf` contains settings to control how OpenFact interacts with its external elements on your system.
@@ -111,3 +129,14 @@ The `cli` section of `facter.conf` contains settings that affect OpenFact’s co
111129
| `trace` | If true, OpenFact prints stacktraces from errors arising in your custom facts. | `false` |
112130
| `verbose` | If true, OpenFact outputs its most detailed messages. | `false` |
113131
| `log-level` | Sets the minimum level of message severity that gets logged. Valid options: “none”, “fatal”, “error”, “warn”, “info”, “debug”, “trace”. | “warn” |
132+
133+
### `facts-groups`
134+
135+
If you have a need to define your own group of facts beacuse you want to manage a larger set of facts within `blocklist` or `ttls` you can make use of the `facts-groups` setting.
136+
Please note that you can not specify external facts here. Structured facts can be provided using the dot notation.
137+
138+
~~~hocon
139+
fact-groups : {
140+
custom-group-name : ["os.name", "networking.ip"],
141+
}
142+
~~~

docs/_openfact_5x/custom_facts.md

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ title: "Custom facts walkthrough"
77
[Adding plug-ins to a module]: /openvox/latest/plugins_in_modules.html
88
[Facts overview]: ./fact_overview.html
99
[openvoxdb]: /openvoxdb/latest
10+
[Win32 API]: https://docs.microsoft.com/en-us/windows/win32/api/
11+
[Fiddle]: https://github.com/ruby/fiddle
1012

1113
You can add custom facts by adding snippets of Ruby code to the OpenVox server. OpenVox then uses [Plugins in Modules][] to distribute the facts to all agents.
1214

@@ -99,7 +101,7 @@ OpenVox gets information about a system from OpenFact, and the most simple way f
99101
get that information is by executing shell commands. You can then parse and manipulate the
100102
output from those commands using standard Ruby code.
101103

102-
> **Note:** However, it is important to also ceck if any of the OpenVox agent bundled Ruby Libraries can be used.
104+
> **Note:** However, it is important to also check if any of the OpenVox agent bundled Ruby Libraries can be used.
103105
104106
The OpenFact API gives you a few ways to
105107
execute shell commands:
@@ -134,13 +136,13 @@ To get the output of `uname --hardware-platform` to single out a specific type o
134136

135137
## Accessing other facts
136138

137-
You can write a custom fact that uses other facts by accessing `Facter.value(:somefact)`.
139+
You can write a custom fact that uses other facts by accessing `Facter.value(:somefact)`. When accessing parts of a structured fact, one must append the key name: `Facter.value(:os)['family']`.
138140
If the fact fails to resolve or is not present, OpenFact returns `nil`.
139141

140142
For example:
141143

142144
``` ruby
143-
Facter.add(:osfamily) do
145+
Facter.add(:my_osfamily) do
144146
setcode do
145147
distid = Facter.value(:os)['family']
146148
case distid
@@ -163,6 +165,7 @@ Facts have a few properties that you can use to customize how they are evaluated
163165

164166
One of the more commonly used properties is the `confine` statement, which
165167
restricts the fact to only run on systems that matches another given fact.
168+
However, this should be done cautiously to avoid introducing cyclic dependencies between facts.
166169

167170
An example of the confine statement would be something like the following:
168171

@@ -183,7 +186,37 @@ available on the given system. Since this is only available on Linux systems,
183186
we use the `confine` statement to ensure that this fact isn't needlessly run on
184187
systems that don't support this type of enumeration.
185188

186-
> **Note:** More information on other ways to confine. e.g. files present can be found a the [Facts overview]
189+
In our next example the application has different config paths for different OS.
190+
191+
RedHat - /etc/sysconfig/application
192+
Debian - /etc/default/application
193+
194+
Example with access to another fact:
195+
196+
```ruby
197+
Facter.add(:betadots_application_version) do
198+
confine do
199+
app_cfg_file = case Facter.value('os')['family']
200+
when 'RedHat'
201+
'/etc/sysconfig/application'
202+
when 'Debian'
203+
'/etc/default/application'
204+
else
205+
'notexisting'
206+
end
207+
208+
File.exist?(app_cfg_file)
209+
end
210+
211+
setcode do
212+
Facter::Core::Execution.execute('/opt/app/bin/app config')
213+
end
214+
end
215+
```
216+
217+
The example retrieves the os fact and uses the family key to compare the OS, then applies logic to determine which application config path to check for.
218+
219+
> **Note:** More information on other ways to confine can be found in the [Facts overview]
187220

188221
### Custom facts precedence
189222

@@ -237,7 +270,21 @@ end
237270
### Custom facts execution timeouts
238271

239272
Although this version of OpenFact does not support overall timeouts on resolutions, you can pass a timeout
240-
to `Facter::Core::Execution#execute`:
273+
to `Facter::Core::Execution#execute` or specify the timeout per fact.
274+
275+
Timeout for the whole fact:
276+
277+
```ruby
278+
Facter.add('<name>', {timeout: 5}) do
279+
...
280+
end
281+
```
282+
283+
Timeout per execute method
284+
285+
```ruby
286+
Facter::Core::Execution::execute('<cmd>', options = {:timeout => 5})
287+
```
241288

242289
``` ruby
243290
Facter.add(:sleep) do
@@ -252,6 +299,37 @@ Facter.add(:sleep) do
252299
end
253300
```
254301

302+
For example, if an application returns its configuration via /opt/app/bin/app config and this commands is sometimes running very long, you can set a timeout:
303+
304+
```ruby
305+
Facter.add(:application_config) do
306+
confine do
307+
File.exist?('/opt/app/bin/app')
308+
end
309+
310+
setcode do
311+
Facter::Core::Execution.execute('/opt/app/bin/app config', {:timeout => 5})
312+
end
313+
end
314+
```
315+
316+
> Please note that Facter::Core::Execution::exec has been deprecated in favor of Facter::Core::Execution::execute. This is important when migrating from older versions of Facter.
317+
318+
### Logging
319+
320+
It's often useful to include logging within custom facts to help with troubleshooting and development. You can log messages using Puppet's built-in logging mechanism:
321+
322+
```ruby
323+
Facter.add(:application_version) do
324+
setcode do
325+
Facter.debug("Custom fact 'application_version' running")
326+
...
327+
end
328+
end
329+
```
330+
331+
Logging levels include `debug`, `info`, `warn`, `error`, and `fatal`.
332+
255333
## Structured custom facts
256334

257335
Structured facts take the form of either a hash or an array. To create a structured fact, return a hash or an array from the `setcode` statement.
@@ -304,6 +382,30 @@ If the `chunk` blocks all return arrays or hashes, you can omit the `aggregate`
304382

305383
For more examples of aggregate resolutions, see the [aggregate resolutions](./fact_overview.html#writing-facts-with-aggregate-resolutions) section of the [Fact overview] page.
306384

385+
## Windows Facts
386+
387+
Windows-based systems support custom facts just like Linux or other Unix-like systems.
388+
The main difference is that many Windows-specific tasks (such as checking installed software or reading from the registry) may require platform-specific Ruby code.
389+
390+
Within the Facter::Core::Execution.execute usually powershell commands are used.
391+
392+
Here's an example of a Windows custom fact that retrieves the version of Internet Explorer:
393+
394+
```ruby
395+
Facter.add(:internet_explorer_version) do
396+
confine kernel: 'windows'
397+
398+
setcode do
399+
Facter::Core::Execution.execute('reg query "HKLM\Software\Microsoft\Internet Explorer" /v svcVersion')
400+
end
401+
end
402+
```
403+
404+
In this example, we use the `reg query` command to check the Internet Explorer version in the Windows registry.
405+
406+
Please note that the [Win32 API] calls are deprecated since ruby 1.9.
407+
Please consider using [Fiddle] or other Ruby-based libraries for interacting with the system.
408+
307409
## Viewing fact values
308410

309411
[OpenVox-DB]: /openvoxdb/latest

docs/_openfact_5x/fact_overview.md

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Simple facts are typically made up of the following parts:
7979
* Can take either a string or a block.
8080
* If given a string, OpenFact executes it as a shell command. If the command succeeds, the output of the command is the value of the fact. If the command fails, the next suitable resolution is evaluated.
8181
* If given a block, the block's return value is the value of the fact unless the block returns `nil`. If `nil` is returned, the next suitable resolution is evalutated.
82-
* Can execute shell commands within a `setcode` block, using the `Facter::Core::Execution.exec` function.
82+
* Can execute shell commands within a `setcode` block, using the `Facter::Core::Execution.execute` function.
8383
* If multiple `setcode` statements are evaluated for a single resolution, only the last `setcode` block is used.
8484

8585
## Writing structured facts
@@ -92,10 +92,9 @@ Structured facts can have [simple](#main-components-of-simple-resolutions) or [a
9292
``` ruby
9393
Facter.add(:interfaces_array) do
9494
setcode do
95-
interfaces = Facter.value(:interfaces)
96-
# the 'interfaces' fact returns a single comma-delimited string, e.g., "lo0,eth0,eth1"
97-
# this splits the value into an array of interface names
98-
interfaces.split(',')
95+
interfaces = Facter.value(:networking)['interfaces'].keys
96+
# the 'networking' fact provdes a list of interface hashes with the interface name as key.
97+
# using the keys functon on the hash provoides an array of interface names.
9998
end
10099
end
101100
```
@@ -108,7 +107,7 @@ Facter.add(:interfaces_hash) do
108107
interfaces_hash = {}
109108

110109
Facter.value(:interfaces_array).each do |interface|
111-
ipaddress = Facter.value("ipaddress_#{interface}")
110+
ipaddress = Facter.value(:networking)['interfaces'][interface]['ip']
112111
if ipaddress
113112
interfaces_hash[interface] = ipaddress
114113
end
@@ -119,6 +118,50 @@ Facter.add(:interfaces_hash) do
119118
end
120119
```
121120

121+
### Example: Provide OpenVox Agent certificate extensions as hash
122+
123+
```ruby
124+
Facter.add(:cert_extension) do
125+
setcode do
126+
require 'openssl'
127+
require 'puppet'
128+
require 'puppet/ssl/oids'
129+
130+
# set variables
131+
extension_hash = {}
132+
certdir = Puppet.settings[:certdir]
133+
certname = Puppet.settings[:certname]
134+
certificate_file = "#{certdir}/#{certname}.pem"
135+
136+
# get puppet ssl oids
137+
oids = {}
138+
Puppet::SSL::Oids::PUPPET_OIDS.each do |o|
139+
oids[o[0]] = o[1]
140+
end
141+
142+
# read the certificate
143+
cert = OpenSSL::X509::Certificate.new File.read certificate_file
144+
145+
# cert extensions differs if we run via agent (numeric) or via facter (names)
146+
# in either way we want to remove pp_preshared_key from the list
147+
cert.extensions.each do |extension|
148+
case extension.oid.to_s
149+
when %r{^1\.3\.6\.1\.4\.1\.34380\.1\.1}
150+
short_name = oids[extension.oid]
151+
value = extension.value[2..-1]
152+
extension_hash[short_name] = value unless short_name == 'pp_preshared_key'
153+
when %r{^pp_}
154+
short_name = extension.oid
155+
value = extension.value[2..-1]
156+
extension_hash[short_name] = value unless short_name == 'pp_preshared_key'
157+
end
158+
end
159+
160+
extension_hash
161+
end
162+
end
163+
```
164+
122165
## Writing facts with aggregate resolutions
123166

124167
Aggregate resolutions allow you to split up the resolution of a fact into separate chunks.By default, OpenFact merges hashes with hashes or arrays with arrays, resulting in a [structured fact](#writing-structured-facts).
@@ -212,11 +255,11 @@ The fact's output is organized by network interface into hashes, each containing
212255
``` ruby
213256
Facter.add(:total_free_memory_mb, :type => :aggregate) do
214257
chunk(:physical_memory) do
215-
Facter.value(:memoryfree_mb)
258+
Facter.value(:memory)['system']['available_bytes']
216259
end
217260

218261
chunk(:virtual_memory) do
219-
Facter.value(:swapfree_mb)
262+
Facter.value(:memory)['swap']['available_bytes']
220263
end
221264

222265
aggregate do |chunks|

docs/_openfact_5x/release_notes.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,4 @@ Please check the [GitHub OpenFact release page](https://github.com/OpenVoxProjec
1616

1717
Released February 20, 2026
1818

19-
Please check the [GitHub OpenFact release page](https://github.com/OpenVoxProject/openfact/releases/tag/5.6.0) for details on new features or bug fixes
20-
.
19+
Please check the [GitHub OpenFact release page](https://github.com/OpenVoxProject/openfact/releases/tag/5.6.0) for details on new features or bug fixes.

0 commit comments

Comments
 (0)