diff --git a/_config.yml b/_config.yml index 49dfa31c3..ca3c8c674 100644 --- a/_config.yml +++ b/_config.yml @@ -61,6 +61,9 @@ collections: openfact_5x: output: true permalink: '/openfact/5.x/:path:output_ext' + openfact_6x: + output: true + permalink: '/openfact/6.x/:path:output_ext' openvox_latest: output: true @@ -122,6 +125,11 @@ defaults: type: openfact_5x values: nav: openfact_5x + - scope: + path: '' + type: openfact_6x + values: + nav: openfact_6x - scope: path: '' diff --git a/_data/nav/openfact_6x.yaml b/_data/nav/openfact_6x.yaml new file mode 100644 index 000000000..691f9caa2 --- /dev/null +++ b/_data/nav/openfact_6x.yaml @@ -0,0 +1,17 @@ +--- +- text: OpenFact + items: + - text: Overview + link: index.html + - text: Release notes + link: release_notes.html + - text: 'CLI' + link: cli.html + - text: Core Facts + link: core_facts.html + - text: Custom facts walkthrough + link: custom_facts.html + - text: Overview of custom facts with examples + link: fact_overview.html + - text: Configuring OpenFact with facter.conf + link: configuring_openfact.html diff --git a/_data/nav_map.yml b/_data/nav_map.yml index e45cde4b8..890a5a717 100644 --- a/_data/nav_map.yml +++ b/_data/nav_map.yml @@ -6,6 +6,10 @@ collections: openfact_latest|openfact_5x base: /openfact/latest/ +- nav_key: openfact_6x + collections: openfact_6x + base: /openfact/6.x/ + - nav_key: openvox_8x collections: openvox_latest|openvox_8x base: /openvox/latest/ diff --git a/_data/navigation.yml b/_data/navigation.yml index 6876fa529..cf5b12dad 100644 --- a/_data/navigation.yml +++ b/_data/navigation.yml @@ -1,6 +1,6 @@ - title: OpenFact url: /openfact/latest/ - collections: [openfact_latest, openfact_5x] + collections: [openfact_latest, openfact_5x, openfact_6x] - title: OpenVox url: /openvox/latest/ collections: [openvox_latest, openvox_8x, openvox_9x] diff --git a/_data/products.yml b/_data/products.yml index eeaac79ff..c6be64ef2 100644 --- a/_data/products.yml +++ b/_data/products.yml @@ -52,6 +52,11 @@ openfact: latest: 5x references: references:openfact versions: + - id: 6x + label: "6.x" + collection: _openfact_6x + base: /openfact/6.x/ + ref: "6.0.0" - id: 5x label: "5.x" collection: _openfact_5x diff --git a/docs/_openfact_6x/.gitignore b/docs/_openfact_6x/.gitignore new file mode 100644 index 000000000..95b275c91 --- /dev/null +++ b/docs/_openfact_6x/.gitignore @@ -0,0 +1,3 @@ +# Generated reference pages — copied here by CI (bundle exec rake references:openfact) +core_facts.md +cli.md diff --git a/docs/_openfact_6x/configuring_openfact.md b/docs/_openfact_6x/configuring_openfact.md new file mode 100644 index 000000000..e917dd340 --- /dev/null +++ b/docs/_openfact_6x/configuring_openfact.md @@ -0,0 +1,142 @@ +--- +layout: default +toc_levels: 1 +title: "Configuring OpenFact with facter.conf" +--- + +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`. +All sections are optional and can be listed in any order within the file. + +When you run OpenFact from the Ruby API, only the `facts` section and limited `global` settings are loaded. + +Example facter.conf file: + +~~~hocon +facts : { + blocklist : [ "file system", "EC2" ], + ttls : [ + { "timezone" : 30 days }, + ] +} +global : { + external-dir : [ "path1", "path2" ], + custom-dir : [ "custom/path" ], + no-exernal-facts : false, + no-custom-facts : false, + no-ruby : false +} + +cli : { + debug : false, + trace : true, + verbose : false, + log-level : "warn" +} +fact-groups : { + custom-group-name : ["os.name", "networking.ip"], +} +~~~ + +## Location + +OpenFact does not create the `facter.conf` file automatically, so you must create it manually, or use a module to manage it. +OpenFact loads the file by default from `/etc/puppetlabs/facter/facter.conf` on *nix systems and `C:\ProgramData\PuppetLabs\facter\etc\facter.conf` on Windows. +Or, you can specify a different default with the `--config` command line option: + +`facter --config path/to/my/config/file/facter.conf` + +### `facts` + +This section of `facter.conf` contains settings that affect fact groups. A fact group is a set of individual facts that are resolved together because they all rely on the same underlying system information. +When you add a group name to the config file as a part of either of these `facts` settings, all facts in that group will be affected. Currently only built-in facts can be cached or blocked. + +Settings: + +* `blocklist` --- Prevents all facts within the listed groups from being resolved when OpenFact runs. + Use the `--list-block-group` command line option to list valid groups. + +* `ttls` --- Caches the key-value pairs of groups and their duration to be cached. + Use the `--list-cache-group` command line option to list valid groups. + + * Cached facts are stored as JSON in `/opt/puppetlabs/facter/cache/cached_facts` on *nix and `C:\ProgramData\PuppetLabs\facter\cache\cached_facts` on Windows. + +Caching and blocking facts is useful when OpenFact is taking a long time and slowing down your code. When a system has a lot of something - for example, mount points or disks - OpenFact can take a long time +to collect the facts from each one. +When this is a problem, you can speed up OpenFact’s collection by either blocking facts you’re uninterested in (`blocklist`), or caching ones you don’t need retrieved frequently (`ttls`). + +#### Example + +To see a list of valid group names, from the command line, run `facter --list-block-groups` or `facter --list-cache-groups`. +The output shows the fact group at the top level, with all facts in that group nested below. + +~~~text +$ facter --list-block-groups +EC2 + - ec2_metadata + - ec2_userdata +file system + - mountpoints + - filesystems + - partitions +~~~ + +If you want to block any of these groups, add the group name to the `facts` section of `facter.conf`, with the `blocklist` setting. + +~~~hocon +facts : { + blocklist : [ "file system" ], +} +~~~ + +Here, the "file system" group has been added, so the `mountpoints`, `filesystems`, and `partitions` facts will all be prevented from loading. + +Within the `blocklist` and `ttls` settings, one can specify fact names: + +~~~hocon +facts : { + blocklist : [ "disks", "memory.swap" ], +} +~~~ + +~~~hocon +ttls : [ + { "mountpoints" : 30 days }, + { "memory.swap" : 6 hours }, +] +~~~~ + +### `global` + +The `global` section of `facter.conf` contains settings to control how OpenFact interacts with its external elements on your system. + +| Setting | Effect | Default | +| ------- | ------ | ------- | +| `external-dir` | A list of directories to search for external facts. | | +| `custom-dir` | A list of directories to search for custom facts. | | +| `no-external`* | If true, prevents OpenFact from searching for external facts. | `false` | +| `no-custom`* | If true, prevents OpenFact from searching for custom facts. | `false` | +| `no-ruby`* | If true, prevents OpenFact from loading its Ruby functionality. | `false` | + +\*Not available when you run OpenFact from the Ruby API. + +### `cli` + +The `cli` section of `facter.conf` contains settings that affect OpenFact’s command line output. All of these settings are ignored when you run OpenFact from the Ruby API. + +| Setting | Effect | Default | +| ------- | ------ | ------- | +| `debug` | If true, OpenFact outputs debug messages. | `false` | +| `trace` | If true, OpenFact prints stacktraces from errors arising in your custom facts. | `false` | +| `verbose` | If true, OpenFact outputs its most detailed messages. | `false` | +| `log-level` | Sets the minimum level of message severity that gets logged. Valid options: “none”, “fatal”, “error”, “warn”, “info”, “debug”, “trace”. | “warn” | + +### `facts-groups` + +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. +Please note that you can not specify external facts here. Structured facts can be provided using the dot notation. + +~~~hocon +fact-groups : { + custom-group-name : ["os.name", "networking.ip"], +} +~~~ diff --git a/docs/_openfact_6x/custom_facts.md b/docs/_openfact_6x/custom_facts.md new file mode 100644 index 000000000..e4a57ecdf --- /dev/null +++ b/docs/_openfact_6x/custom_facts.md @@ -0,0 +1,643 @@ +--- +layout: default +title: "Custom facts walkthrough" +--- + +[Plugins in Modules]: /openvox/latest/plugins_in_modules.html +[Adding plug-ins to a module]: /openvox/latest/plugins_in_modules.html +[Facts overview]: ./fact_overview.html +[openvoxdb]: /openvoxdb/latest +[Win32 API]: https://docs.microsoft.com/en-us/windows/win32/api/ +[Fiddle]: https://github.com/ruby/fiddle + +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. + +For information on how to add custom facts to modules, see [Adding plug-ins to a module][]. + +## Adding custom facts to OpenFact + +Sometimes you need to be able to write conditional expressions based on site-specific data that just isn't available via OpenFact, or perhaps you'd like to include it in a template. + +Because you can't include arbitrary Ruby code in your manifests, the best solution is to add a new fact to OpenFact. +These additional facts can then be distributed to OpenVox agent and are available for use in manifests and templates, just like any other fact is. + +> **Note:** OpenFact provides a [custom facts API](https://github.com/openvoxproject/openfact/blob/master/Extensibility.md#custom-facts-compatibility). + +## Loading custom facts + +OpenFact offers multiple methods of loading facts: + +- `$LOAD_PATH`, or the Ruby library load path +- The `--custom-dir` command line option +- The environment variable 'FACTERLIB' + +You can use these methods to do things like test files locally before distributing them, or you can arrange to have a specific set of facts available on certain machines. + +### Using the Ruby load path + +OpenFact searches all directories in the Ruby `$LOAD_PATH` variable for +subdirectories named `facter`, and loads all Ruby files in those directories. +If you had a directory in your `$LOAD_PATH` like `~/lib/ruby`, set up like +this: + +```text + #~/lib/ruby + └── facter + ├── system_load.rb + └── users.rb +``` + +OpenFact loads `facter/system_load.rb` and `facter/users.rb`. + +### Using the `--custom-dir` command line option + +OpenFact can take multiple `--custom-dir` options on the command line that specifies a single directory +to search for custom facts. OpenFact attempts to load all Ruby files in the specified directories. +This allows you to do something like this: + +```text + #~/my_facts + └── system_load.rb + #~/my_other_facts + └── users.rb + + $ facter --custom-dir=./my_facts --custom-dir=./my_other_facts system_load users + system_load => 0.25 + users => thomas,pat +``` + +### Using the `FACTERLIB` environment variable + +OpenFact also checks the environment variable `FACTERLIB` for a delimited (semicolon for Windows and colon for all +other platforms) set of directories, and tries to load all Ruby files in those directories. +This allows you to do something like this: + +```text + #~/my_facts + └── system_load.rb + #~/my_other_facts + └── users.rb + + $ export FACTERLIB="./my_facts:./my_other_facts" + $ facter system_load users + system_load => 0.25 + users => thomas,pat +``` + +## Two parts of every custom fact + +Most facts have at least two elements: + +1. A call to `Facter.add('fact_name')`, which determines the name of the fact. +2. A `setcode` statement for simple resolutions, which is evaluated to determine the fact's value. + +Facts *can* get a lot more complicated than that, but those two together are the most common implementation of a custom fact. + +## Executing shell commands in custom facts + +OpenVox gets information about a system from OpenFact, and the most simple way for OpenFact to +get that information is by executing shell commands. You can then parse and manipulate the +output from those commands using standard Ruby code. + +> **Note:** However, it is important to also check if any of the OpenVox agent bundled Ruby Libraries can be used. + +The OpenFact API gives you a few ways to +execute shell commands: + +- To run a command and use the output verbatim, as your fact's value, you can pass the command into `setcode` directly. For example: `setcode 'uname --hardware-platform'` +- If your fact is more complicated than that, you can call `Facter::Core::Execution.execute('uname --hardware-platform')` from within the `setcode do`...`end` block. + Whatever the `setcode` statement returns is used as the fact's value. +- Your shell command is also a Ruby string, so you need to escape special characters if you want to pass them through. +- When you pass a bare command name rather than a full path, OpenFact looks for it in `$PATH` + and then in `/sbin`, `/usr/sbin`, and, starting in OpenFact 6.0, `/opt/puppetlabs/bin`. The + last entry means facts that call `puppet`, `puppetserver`, or `puppetdb` resolve even when the + agent runs as a service without the `/etc/profile.d/puppet-agent.sh` `PATH` additions. + +> **Note:** Not everything that works in the terminal works in a fact. You can use the pipe (`|`) and similar operators as you normally would. +> but Bash-specific syntax like `if` statements do not work. The best way to handle this limitation is to write your conditional logic in Ruby. + +### Example + +To get the output of `uname --hardware-platform` to single out a specific type of workstation, you create a new custom fact. + +1. Start by giving the fact a name, in this case, `hardware_platform`. + +2. Create your new fact in a file, `hardware_platform.rb` on an OpenVox agent system: + + ```ruby + #~/my_facts/hardware_platform.rb + + Facter.add('hardware_platform') do + setcode do + Facter::Core::Execution.execute('/bin/uname --hardware-platform') + end + end + ``` + +3. Use the instructions in the [Plugins in Modules][] page to copy the new fact to a module and distribute it. During your next OpenVox run, the value of the new fact is available to use in your manifests and templates. + +## Accessing other facts + +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']`. +If the fact fails to resolve or is not present, OpenFact returns `nil`. +Please take care to not build loops! + +For example: + +```ruby +Facter.add(:my_osfamily) do + setcode do + distid = Facter.value(:os)['family'] + case distid + when /RedHatEnterprise|CentOS|Fedora/ + 'redhat' + when 'ubuntu' + 'debian' + else + distid + end + end +end +``` + +## Configuring custom facts + +Facts have a few properties that you can use to customize how they are evaluated. + +### Confining custom facts + +One of the more commonly used properties is the `confine` statement, which +restricts the fact to only run on systems that matches another given fact. +However, this should be done cautiously to avoid introducing cyclic dependencies between facts. + +An example of the confine statement would be something like the following: + +```ruby +Facter.add(:powerstates) do + confine :kernel => 'Linux' + setcode do + # Put your fact code here. + Facter::Core::Execution.execute('cat /sys/power/states') + end +end +``` + +Note that everything you put inside the `setcode` block is confined. Everything outside of the `setcode` block is not confined. + +This fact uses sysfs on linux to get a list of the power states that are +available on the given system. Since this is only available on Linux systems, +we use the `confine` statement to ensure that this fact isn't needlessly run on +systems that don't support this type of enumeration. + +In our next example the application has different config paths for different OS. + +RedHat - /etc/sysconfig/application +Debian - /etc/default/application + +Example with access to another fact: + +```ruby +Facter.add(:application_version) do + confine do + app_cfg_file = case Facter.value('os')['family'] + when 'RedHat' + '/etc/sysconfig/application' + when 'Debian' + '/etc/default/application' + else + 'notexisting' + end + + File.exist?(app_cfg_file) + end + + setcode do + Facter::Core::Execution.execute('/opt/app/bin/app config') + end +end +``` + +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. + +> **Note:** More information on other ways to confine can be found in the [Facts overview] + +### Custom facts precedence + +A single fact can have multiple **resolutions**, each of which is a different way +of ascertaining what the value of the fact should be. It's very common to have +different resolutions for different operating systems, for example. It's easy to +confuse facts and resolutions because they are superficially identical --- to add +a new resolution to a fact, you simply add the fact again, only with a different +`setcode` statement. + +When a fact has more than one resolution, the first resolution that returns a value other +than `nil` sets the fact's value. The way that OpenFact decides the issue of resolution precedence is the +weight property. Once OpenFact rules out any resolutions that are excluded because of `confine` statements, +the resolution with the highest weight is evaluated first. If that resolution returns `nil`, +OpenFact moves on to the next resolution (by descending weight) until it gets a value for the fact. + +By default, the weight of a fact is the number of confines for that resolution, so +that more specific resolutions take priority over less specific resolutions. + +```ruby +# Check to see if this server has been marked as a postgres server +Facter.add(:role) do + has_weight 100 + setcode do + if File.exist? '/etc/postgres_server' + 'postgres_server' + end + end +end + +# Guess if this is a server by the presence of the pg_create binary +Facter.add(:role) do + has_weight 50 + setcode do + if File.exist? '/usr/sbin/pg_create' + 'postgres_server' + end + end +end + +# If this server doesn't look like a server, it must be a desktop +Facter.add(:role) do + setcode do + 'desktop' + end +end +``` + +> **Hint:** A weight above 1000 overwrites external facts + +### Custom facts execution timeouts + +Although this version of OpenFact does not support overall timeouts on resolutions, you can pass a timeout +to `Facter::Core::Execution#execute` or specify the timeout per fact. + +Timeout for the whole fact: + +```ruby +Facter.add('', {timeout: 5}) do + ... +end +``` + +Timeout per execute method + +```ruby +Facter::Core::Execution::execute('', options = {:timeout => 5}) +``` + +> **Note:** The `time_limit` and `limit` option keys are compatibility aliases for `timeout`. +> Starting in OpenFact 6.0 they emit a deprecation warning; use `timeout`. + +```ruby +Facter.add(:sleep) do + setcode do + begin + Facter::Core::Execution.execute('sleep 10', options = {:timeout => 5}) + 'did not timeout!' + rescue Facter::Core::Execution::ExecutionFailure + 'timeout!' + end + end +end +``` + +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: + +```ruby +Facter.add(:application_config) do + confine do + File.exist?('/opt/app/bin/app') + end + + setcode do + Facter::Core::Execution.execute('/opt/app/bin/app config', {:timeout => 5}) + end +end +``` + +> Please note that `Facter::Core::Execution.exec` has been deprecated in favor of `Facter::Core::Execution.execute`, as have `Facter::Util::Resolution.exec` and `Facter::Util::Resolution.which`. Starting in OpenFact 6.0 these methods emit a runtime deprecation warning and will be removed in a future major release. This is important when migrating from older versions of Facter. + +### Logging + +It's often useful to include logging within custom facts to help with troubleshooting and development. You can log messages using OpenVox's built-in logging mechanism: + +```ruby +Facter.add(:application_version) do + setcode do + Facter.debug("Custom fact 'application_version' running") + ... + end +end +``` + +Logging levels include `debug`, `info`, `warn`, `error`, and `fatal`. + +## Structured custom facts + +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. + +You can see some relevant examples in the [writing structured facts](./fact_overview.html#writing-structured-facts) section of the [Facts overview]. + +## Aggregate resolutions + +If your fact combines the output of multiple commands, it may make sense to use aggregate resolutions. An aggregate resolution is split into "chunks", each one responsible for resolving one piece of the fact. +After all of the chunks have been resolved separately, they're combined into a single flat or structured fact and returned. + +Aggregate resolutions have several key differences compared to simple resolutions, beginning with the fact declaration. To introduce an aggregate resolution, add the `:type => :aggregate` parameter: + +```ruby +Facter.add(:fact_name, :type => :aggregate) do + #chunks go here + #aggregate block goes here +end +``` + +Each step in the resolution then gets its own named `chunk` statement: + +```ruby +chunk(:one) do + 'Chunk one returns this. ' +end + +chunk(:two) do + 'Chunk two returns this.' +end +``` + +Aggregate resolutions *never* have a `setcode` statement. Instead, they have an optional `aggregate` block that combines the chunks. +Whatever value the `aggregate` block returns is the fact's value. Here's an example that just combines the strings from the two chunks above: + +```ruby +aggregate do |chunks| + result = '' + + chunks.each_value do |str| + result += str + end + + # Result: "Chunk one returns this. Chunk two returns this." + result +end +``` + +If the `chunk` blocks all return arrays or hashes, you can omit the `aggregate` block. If you do, OpenFact automatically merges all of your data into one array or hash and uses that as the fact's value. + +For more examples of aggregate resolutions, see the [aggregate resolutions](./fact_overview.html#writing-facts-with-aggregate-resolutions) section of the [Fact overview] page. + +## Windows Facts + +Windows-based systems support custom facts just like Linux or other Unix-like systems. +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. + +Within the Facter::Core::Execution.execute usually powershell commands are used. + +Here's an example of a Windows custom fact that retrieves the version of Internet Explorer: + +```ruby +Facter.add(:internet_explorer_version) do + confine kernel: 'windows' + + setcode do + Facter::Core::Execution.execute('reg query "HKLM\Software\Microsoft\Internet Explorer" /v svcVersion') + end +end +``` + +In this example, we use the `reg query` command to check the Internet Explorer version in the Windows registry. + +Please note that the [Win32 API] calls are deprecated since ruby 1.9. +Please consider using [Fiddle] or other Ruby-based libraries for interacting with the system. + +## Viewing fact values + +If your OpenVox servers are configured to use [OpenVoxDB][OpenVoxDB], you can view and search all of the facts for any node, including custom facts. See [the OpenVoxDB docs][openvoxdb] for more info. + +## External facts + +### What are external facts? + +External facts provide a way to use arbitrary executables or scripts as facts, or set facts statically with structured data. +If you've ever wanted to write a custom fact in Perl, C, or a one-line text file, this is how. + +### Executable external facts --- Unix + +Executable facts on Unix work by dropping an executable file into the standard +external fact path. A shebang (`#!`) is always required for executable facts on Unix. If the shebang is missing, the execution of the fact fails. + +An example external fact written in Python: + +```python +#!/usr/bin/env python +data = {"key1" : "value1", "key2" : "value2" } + +for k in data: + print "%s=%s" % (k,data[k]) +``` + +You must ensure that the script has its execute bit set: + +```shell + chmod +x /etc/facter/facts.d/my_fact_script.py +``` + +For OpenFact to parse the output, the script must return key/value pairs on +STDOUT in the format: + +```text + key1=value1 + key2=value2 + key3=value3 +``` + +Using this format, a single script can return multiple facts. + +### Executable external facts --- Windows + +Executable facts on Windows work by dropping an executable file into the external fact path. +Unlike with Unix, the external facts interface expects Windows scripts to end with a known extension. Line endings can be either `LF` or `CRLF`. The following extensions are currently supported: + +- `.com` and `.exe`: binary executables +- `.bat` and `.cmd`: batch scripts +- `.ps1`: PowerShell scripts + +As with Unix facts, each script must return key/value pairs on STDOUT in the format: + +```text + key1=value1 + key2=value2 + key3=value3 +``` + +Using this format, a single script can return multiple facts in one return. + +#### Executable external fact locations + +The best way to distribute external executable facts is with pluginsync, To add external executable facts to your OpenVox modules, just place them in `//facts.d/`. + +If you're not using pluginsync, then external facts must go in a standard directory. The location of this directory varies depending on your operating system and whether you are running as root/Administrator. +When calling OpenFact from the command line, you can specify the external facts directory with the `--external-dir` option. + +> **Note:** These directories don't necessarily exist by default; you may need to create them. If you create the directory, make sure to restrict access so that only Administrators can write to the directory. + +In a module (recommended): + +```text + //facts.d/ +``` + +On Unix/Linux/OS X, there are three directories: + +```text + /opt/puppetlabs/facter/facts.d/ + /etc/puppetlabs/facter/facts.d/ + /etc/facter/facts.d/ +``` + +On Windows: + +```text + C:\ProgramData\PuppetLabs\facter\facts.d\ +``` + +When running as a non-root / non-Administrator user: + +```text + /.facter/facts.d/ +``` + +> **Note:** You can only use custom facts as a non-root user if you have previously run OpenVox agent as that same user. + +#### Windows batch scripts + +The file encoding for `.bat/.cmd` files must be `ANSI` or `UTF8 without BOM` (Byte Order Mark), otherwise you may get strange output. + +Here is a sample batch script which outputs facts using the required format: + +```text + @echo off + echo key1=val1 + echo key2=val2 + echo key3=val3 + REM Invalid - echo 'key4=val4' + REM Invalid - echo "key5=val5" +``` + +#### PowerShell scripts + +The encoding that should be used with `.ps1` files is pretty open. PowerShell determines the encoding of the file at run time. + +Here is a sample PowerShell script which outputs facts using the required format: + +```powershell + Write-Host "key1=val1" + Write-Host 'key2=val2' + Write-Host key3=val3 +``` + +You should be able to save and execute this PowerShell script on the command line. + +### Structured data based external facts + +OpenFact can parse structured data files stored in the external facts directory and set facts based on their contents. + +Structured data files must use one of the supported data types and must have the correct file extension. OpenFact supports the following extensions and data types: + +`.yaml`: YAML data, in the following format: + +```yaml +--- +key1: val1 +key2: val2 +key3: val3 +``` + +`.json`: JSON data, in the following format: + +```json +{ + "key1": "val1", + "key2": "val2", + "key3": "val3" +} +``` + +`.txt`: Key value pairs, of the `String` data type, in the following format: + +```text +key1=value1 +key2=value2 +key3=value3 +``` + +As with executable facts, structured data files can set multiple facts at once. + +```json +{ + "datacenter": + { + "location": "bfs", + "workload": "Web Development Pipeline", + "contact": "Blackbird" + }, + "provision": + { + "birth": "2017-01-01 14:23:34", + "user": "alex" + } +} +``` + +#### Structured data based external facts on Windows + +All of the above types are supported on Windows with the following caveats: + +- The line endings can be either `LF` or `CRLF`. +- The file encoding must be either `ANSI` or `UTF8 without BOM` (Byte Order Mark). + +### Troubleshooting external facts + +If your external fact is not appearing in OpenFact's output, running +OpenFact in debug mode should give you a meaningful reason and tell you which file is causing the problem: + +```shell + # puppet facts --debug +``` + +One example of when this can happen is in cases where a fact returns invalid characters. +For example if you used a hyphen instead of an equals sign in your script `test.sh`: + +```shell + #!/bin/bash + + echo "key1-value1" +``` + +Running `puppet facts --debug` yields a useful message: + +```text + ... + Debug: Facter: resolving facts from executable file "/tmp/test.sh". + Debug: Facter: executing command: /tmp/test.sh + Debug: Facter: key1-value1 + Debug: Facter: ignoring line in output: key1-value1 + Debug: Facter: process exited with status code 0. + Debug: Facter: completed resolving facts from executable file "/tmp/test.sh". + ... +``` + +#### External facts and `stdlib` + +If you find that an external fact does not match what you have configured in your `facts.d` +directory, make sure you have not defined the same fact using the external facts capabilities +found in the `stdlib` module. + +### Drawbacks + +While external facts provide a mostly-equal way to create variables for OpenVox, they have a few drawbacks: + +- An external fact cannot internally reference another fact. However, due to parse order, you can reference an external fact from a Ruby fact. +- External executable facts are forked instead of executed within the same process. diff --git a/docs/_openfact_6x/fact_overview.md b/docs/_openfact_6x/fact_overview.md new file mode 100644 index 000000000..1d015b178 --- /dev/null +++ b/docs/_openfact_6x/fact_overview.md @@ -0,0 +1,275 @@ +--- +layout: default +title: "Overview of custom facts with examples" +--- + +A typical fact in OpenFact is a fairly simple assemblage of just a few different elements. +This page is an example-driven tour of those elements, and is intended as a quick primer or reference +for authors of custom facts. You need some familiarity with Ruby to understand most of these examples. +For a gentler introduction, check out the [Custom Facts Walkthrough](./custom_facts.html). + +It's important to distinguish between **facts** and **resolutions**. A fact is a piece of information about a given node, +while a resolution is a way of obtaining that information from the system. That means every fact needs to have **at least one** +resolution, and facts that can run on different operating systems may need to have different resolutions for each one. + +Even though facts and resolutions are conceptually very different, the line can get a bit blurry at times. That's because declaring a second +(or more) resolution for a fact looks just like declaring a completely new fact, only with the same name as an existing fact. + +## Writing facts with simple resolutions + +Most facts are resolved all at once, without any need to merge data from different sources. In that case, the resolution is simple. +Both flat and structured facts can have simple resolutions. + +### Example: Minimal fact that relies on a single shell command + +``` ruby +Facter.add(:rubypath) do + setcode 'which ruby' +end +``` + +### Example: Different resolutions for different operating systems + +``` ruby +Facter.add(:rubypath) do + setcode 'which ruby' +end + +Facter.add(:rubypath) do + confine :os['family'] => "Windows" + # Windows uses 'where' instead of 'which' + setcode 'where ruby' +end +``` + +### Example: Slightly more complex fact, confined to Linux with a block + +``` ruby +Facter.add(:jruby_installed) do + confine :kernel do |value| + value == "Linux" + end + + setcode do + # If jruby is present, return true. Otherwise, return false. + Facter::Core::Execution.which('jruby') != nil + end +end +``` + +### Main components of simple resolutions + +Simple facts are typically made up of the following parts: + +1. A call to `Facter.add(:fact_name)`: + * This introduces a new fact *or* a new resolution for an existing fact with the same name. + * The name can be either a symbol or a string. + * The rest of the fact is wrapped in the `add` call's `do ... end` block. +2. Zero or more `confine` statements: + * Determine whether the resolution is suitable (and therefore is evaluated). + * Can either match against the value of another fact or evaluate a Ruby block. + * If given a symbol or string representing a fact name, a block is required and the block receives the fact's value as an argument. + * If given a hash, the keys are expected to be fact names. The values of the hash are either the expected fact values or an array of values to compare against. + * If given a block, the confine is suitable if the block returns a value other than `nil` or `false`. +3. An optional `has_weight` statement: + * When multiple resolutions are available for a fact, resolutions are evaluated from highest weight value to lowest. + * Must be an integer greater than 0. + * Defaults to the number of `confine` statements for the resolution. +4. A `setcode` statement that determines the value of the fact: + * Can take either a string or a block. + * 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. + * 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. + * Can execute shell commands within a `setcode` block, using the `Facter::Core::Execution.execute` function. + * If multiple `setcode` statements are evaluated for a single resolution, only the last `setcode` block is used. + +## Writing structured facts + +Structured facts can take the form of hashes or arrays. You don't have to do anything special to mark the fact as structured --- if your fact returns a hash or array, OpenFact recognizes it as a structured fact. +Structured facts can have [simple](#main-components-of-simple-resolutions) or [aggregate resolutions](#main-components-of-aggregate-resolutions). + +### Example: Returning an array of network interfaces + +``` ruby +Facter.add(:interfaces_array) do + setcode do + interfaces = Facter.value(:networking)['interfaces'].keys + # the 'networking' fact provdes a list of interface hashes with the interface name as key. + # using the keys functon on the hash provoides an array of interface names. + end +end +``` + +### Example: Returning a hash of network interfaces to IP addresses + +``` ruby +Facter.add(:interfaces_hash) do + setcode do + interfaces_hash = {} + + Facter.value(:interfaces_array).each do |interface| + ipaddress = Facter.value(:networking)['interfaces'][interface]['ip'] + if ipaddress + interfaces_hash[interface] = ipaddress + end + end + + interfaces_hash + end +end +``` + +### Example: Provide OpenVox Agent certificate extensions as hash + +```ruby +Facter.add(:cert_extension) do + setcode do + require 'openssl' + require 'puppet' + require 'puppet/ssl/oids' + + # set variables + extension_hash = {} + certdir = Puppet.settings[:certdir] + certname = Puppet.settings[:certname] + certificate_file = "#{certdir}/#{certname}.pem" + + # get puppet ssl oids + oids = {} + Puppet::SSL::Oids::PUPPET_OIDS.each do |o| + oids[o[0]] = o[1] + end + + # read the certificate + cert = OpenSSL::X509::Certificate.new File.read certificate_file + + # cert extensions differs if we run via agent (numeric) or via facter (names) + # in either way we want to remove pp_preshared_key from the list + cert.extensions.each do |extension| + case extension.oid.to_s + when %r{^1\.3\.6\.1\.4\.1\.34380\.1\.1} + short_name = oids[extension.oid] + value = extension.value[2..-1] + extension_hash[short_name] = value unless short_name == 'pp_preshared_key' + when %r{^pp_} + short_name = extension.oid + value = extension.value[2..-1] + extension_hash[short_name] = value unless short_name == 'pp_preshared_key' + end + end + + extension_hash + end +end +``` + +## Writing facts with aggregate resolutions + +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). +But you can also aggregate the chunks into a flat fact using concatenation, addition, or any other function that you can express in Ruby code. + +### Main components of aggregate resolutions + +Aggregate resolutions have two key differences compared to simple resolutions: the presence of `chunk` statements and the lack of a `setcode` statement. +The `aggregate` block is optional, and without it OpenFact merges hashes with hashes or arrays with arrays. + +1. A call to `Facter.add(:fact_name, :type => :aggregate)`: + * Introduces a new fact *or* a new resolution for an existing fact with the same name. + * The name can be either a symbol or a string. + * The `:type => :aggregate` parameter is required for aggregate resolutions. + * The rest of the fact is wrapped in the `add` call's `do ... end` block. +2. Zero or more `confine` statements: + * Determine whether the resolution is suitable and (therefore is evaluated). + * They can either match against the value of another fact or evaluate a Ruby block. + * If given a symbol or string representing a fact name, a block is required and the block receives the fact's value as an argument. + * If given a hash, the keys are expected to be fact names. The values of the hash are either the expected fact values or an array of values to compare against. + * If given a block, the confine is suitable if the block returns a value other than `nil` or `false`. +3. An optional `has_weight` statement: + * Evaluates multiple resolutions for a fact from highest weight value to lowest. + * Must be an integer greater than 0. + * Defaults to the number of `confine` statements for the resolution. +4. One or more calls to `chunk`, each containing: + * A name (as the argument to `chunk`). + * A block of code, which is responsible for resolving the chunk to a value. The block's return value is the value of the chunk; it can be any type, but is typically a hash or array. +5. An optional `aggregate` block: + * If absent, OpenFact automatically merges hashes with hashes or arrays with arrays. + * To merge the chunks in any other way, you need to make a call to `aggregate`, which takes a block of code. + * The block is passed one argument (`chunks`, in the example), which is a hash of chunk name to chunk value for all the chunks in the resolution. + +### Example: Building a structured fact progressively + +This example builds a new fact, `networking_primary_sha`, by progressively merging two chunks. +One chunk encodes each networking interface's MAC address as an encoded base64 value, and the other determines if each interface is the system's primary interface. + +``` ruby +require 'digest' +require 'base64' + +Facter.add(:networking_primary_sha, :type => :aggregate) do + + chunk(:sha256) do + interfaces = {} + + Facter.value(:networking)['interfaces'].each do |interface, values| + if values['mac'] + hash = Digest::SHA256.digest(values['mac']) + encoded = Base64.encode64(hash) + interfaces[interface] = {:mac_sha256 => encoded.strip} + end + end + + interfaces + end + + chunk(:primary?) do + interfaces = {} + + Facter.value(:networking)['interfaces'].each do |interface, values| + interfaces[interface] = {:primary? => (interface == Facter.value(:networking)['primary'])} + end + + interfaces + end + # OpenFact merges the return values for the two chunks + # automatically, so there's no aggregate statement. +end +``` + +The fact's output is organized by network interface into hashes, each containing the two chunks: + +``` ruby +{ + bridge0 => { + mac_sha256 => "bfgEFV7m1V04HYU6UqzoNoVmnPIEKWRSUOU650j0Wkk=", + primary? => false + }, + en0 => { + mac_sha256 => "6Fd3Ws2z+aIl8vNmClCbzxiO2TddyFBChMlIU+QB28c=", + primary? => true + }, + ... +} +``` + +### Example: Building a flat fact progressively with addition + +``` ruby +Facter.add(:total_free_memory_mb, :type => :aggregate) do + chunk(:physical_memory) do + Facter.value(:memory)['system']['available_bytes'] + end + + chunk(:virtual_memory) do + Facter.value(:memory)['swap']['available_bytes'] + end + + aggregate do |chunks| + # The return value for this block determines the value of the fact. + sum = 0 + chunks.each_value do |i| + sum += i + end + + sum + end +end +``` diff --git a/docs/_openfact_6x/index.md b/docs/_openfact_6x/index.md new file mode 100644 index 000000000..b71a124d3 --- /dev/null +++ b/docs/_openfact_6x/index.md @@ -0,0 +1,49 @@ +--- +layout: default +title: "OpenFact 6" +--- + +OpenFact is a cross-platform system profiling library that discovers and reports per-node facts — +structured data about a node's operating system, hardware, networking, and more. Facts are available +in OpenVox manifests as top-scope variables (e.g. `$facts['os']['family']`) and are used by the +catalog compiler to make node-specific decisions. + +OpenFact is the community-maintained continuation of Puppet's Facter, originally adopted under +[Vox Pupuli](https://voxpupuli.org/) stewardship alongside OpenVox. It is downstream-compatible +with Facter — existing custom facts and external facts work unchanged. + +## How it works + +OpenFact runs on each managed node and resolves facts by querying the system directly: reading +`/proc` entries, running shell commands, calling OS APIs, and so on. Results are returned as a +structured hash. When a fact can be resolved in multiple ways (for example, differently on Linux +versus Windows), OpenFact runs the highest-weighted applicable resolution. + +**In an agent/server deployment**, OpenFact runs automatically at the start of each agent run. +The collected facts are sent to OpenVox Server and made available to the catalog compiler. +Facts are not persisted without OpenVoxDB — they are used during catalog compilation and then +discarded. The agent run produces a report, which OpenVox Server stores on local disk by default; +without periodic cleanup, report storage will grow unbounded. OpenVoxDB is an optional component +that provides durable storage for facts, catalogs, and reports. + +**Standalone**, the `facter` CLI lets you query facts directly on any node — useful for +ad-hoc inspection, debugging, or scripting without triggering a full agent run. + +## Included in openvox-agent + +OpenFact ships inside the `openvox-agent` package and does not need to be installed separately. +OpenFact 6 is the series bundled with `openvox-agent` 9.x; the 8.x agent bundles OpenFact 5. +The version bundled with a given agent release is listed in +[Component versions in recent releases](/openvox/9.x/component_versions.html). + +OpenFact 6 requires Ruby 3.0 or later (the 9.x agent bundles Ruby 4.0). It drops support for +Ruby 2.5 through 2.7. See the [release notes](./release_notes.html) for the other changes in 6.0. + +## Getting started + +- [Core facts reference](./core_facts.html) — every built-in fact that ships with OpenFact (auto-generated) +- [Custom facts walkthrough](./custom_facts.html) — step-by-step guide to writing and distributing your own facts +- [Custom facts reference](./fact_overview.html) — example-driven quick reference for fact authors +- [Configuring OpenFact](./configuring_openfact.html) — `facter.conf` options +- [CLI reference](./cli.html) — command-line flags and options (auto-generated) +- [Release notes](./release_notes.html) — OpenFact version history diff --git a/docs/_openfact_6x/release_notes.md b/docs/_openfact_6x/release_notes.md new file mode 100644 index 000000000..2c2a58a49 --- /dev/null +++ b/docs/_openfact_6x/release_notes.md @@ -0,0 +1,30 @@ +--- +layout: default +title: "OpenFact release notes" +--- + +This page documents the history of the OpenFact 6 series. OpenFact 6 is bundled with +`openvox-agent` 9.x; for the series bundled with the 8.x agent, see the +[OpenFact 5 release notes](/openfact/5.x/release_notes.html). + +## OpenFact 6.0.0 + +Released on July 31, 2026 + +Please check the [GitHub OpenFact release page](https://github.com/OpenVoxProject/openfact/releases/tag/6.0.0) for details on new features or bug fixes. + +Breaking changes in this release: + +- Ruby 3.0 or later is required. Support for Ruby 2.5, 2.6, and 2.7 is dropped. +- The deprecated `ldapname` fact option and accessor are removed. +- `Facter::Core::Execution.execute` warns when called with the `time_limit` or `limit` + option keys; use `timeout` instead. +- `Facter::Core::Execution.exec`, `Facter::Util::Resolution.exec`, and + `Facter::Util::Resolution.which` emit runtime deprecation warnings ahead of removal in a + future major release. +- The `Resolvable#limit` compatibility bridge for `timeout` is deprecated. +- When resolving a bare command name, OpenFact now searches `/opt/puppetlabs/bin` after + `$PATH`, `/sbin`, and `/usr/sbin`, so facts that call the OpenVox tools work when the agent + runs as a service without the profile.d `PATH` additions. + +Other changes include Ruby 4 support and a fix for a missing `.El` macro in the `facter` man page.