Custom Facts
Extend facter by writing your own custom facts to provide information to Puppet.
Ruby Facts
Adding Custom Facts to Facter
Sometimes you need to be able to write conditional expressions based on site-specific data that just isn’t available via Facter (or use a variable in a template that isn’t there). A solution can be achieved by adding a new fact to Facter. These additional facts can then be distributed to Puppet clients and are available for use in manifests.
The Concept
You can add new facts by writing a snippet of Ruby code on the Puppet master. We then use Plugins In Modules to distribute our facts to the client.
An Example
Let’s say we need to get the output of uname -i to single out a
specific type of workstation. To do these we create a fact. We
start by giving the fact a name, in this case, hardware_platform
,
and create our new fact in a file, hardware_platform.rb
, on the
Puppet master server:
# hardware_platform.rb
Facter.add("hardware_platform") do
setcode do
Facter::Util::Resolution.exec('/bin/uname -i')
end
end
Note: Prior to Facter 1.5.8, values returned by
Facter::Util::Resolution.exec
often had trailing newlines. If your custom fact will also be used by older versions of Facter, you may need to callchomp
on these values. (In the example above, this would look likeFacter::Util::Resolution.exec('/bin/uname -i').chomp
.)
We then use the instructions in Plugins In Modules page to copy our new fact to a module and distribute it. During your next Puppet run the value of our new fact will be available to use in your manifests.
The best place to get ideas about how to write your own custom facts is to look at the existing Facter fact code. You will find lots of examples of how to interpret different types of system data and return useful facts.
Using other facts
You can write a fact which uses other facts by accessing Facter.value(“somefact”) or simply Facter.somefact. The former will return nil for unknown facts, the latter will raise an exception. An example:
Facter.add("osfamily") do
setcode do
distid = Facter.value('lsbdistid')
case distid
when /RedHatEnterprise|CentOS|Fedora/
"redhat"
when "ubuntu"
"debian"
else
distid
end
end
end
Loading Custom Facts
Facter offers a few methods of loading facts:
- $LOAD_PATH, or the ruby library load path
- The environment variable ‘FACTERLIB’
- Facts distributed using pluginsync
You can use these methods of loading facts do to things like test files locally before distributing them, or have a specific set of facts available on certain machines.
Facter will search all directories in the ruby $LOAD_PATH variable for subdirectories named ‘facter’, and will load all ruby files in those directories. If you had some directory in your $LOAD_PATH like ~/lib/ruby, set up like this:
{~/lib/ruby}
└── facter
├── rackspace.rb
├── system_load.rb
└── users.rb
Facter would try to load ‘facter/system_load.rb’, ‘facter/users.rb’, and ‘facter/rackspace.rb’.
Facter also will check the environment variable FACTERLIB
for a colon delimited
set of directories, and will try to load all ruby files in those directories.
This allows you to do something like this:
$ ls my_facts
system_load.rb
$ ls my_other_facts
users.rb
$ export FACTERLIB="./my_facts:./my_other_facts"
$ facter system_load users
system_load => 0.25
users => thomas,pat
Facter can also easily load fact files distributed using pluginsync. Running
facter -p
will load all the facts that have been distributed via pluginsync,
so if you’re using a lot of custom facts inside puppet, you can easily use
these facts with standalone facter.
Custom facts can be distributed to clients using the Plugins In Modules method.
Configuring Facts
Facts have a few properties that you can use to customize how facts are evaluated.
Confining 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.
An example of the confine statement would be something like the following:
Facter.add(:powerstates) do
confine :kernel => "Linux"
setcode do
Facter::Util::Resolution.exec('cat /sys/power/states')
end
end
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.
Fact precedence
Another property of facts is the weight
property. Facts with a higher weight
are run earlier, which allows you to either override or provide fallbacks to
existing facts, or ensure that facts are evaluated in a specific order.
By default, the weight of a fact is the number of confines for that fact, so
that more specific facts are evaluated first.
# 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
Timing out
If you have facts that are unreliable and may not finish running, you can use
the timeout
property. If a fact is defined with a timeout and the evaluation
of the setcode block exceeds the timeout, Facter will halt the resolution of
that fact and move on.
# Sleep
Facter.add(:sleep, :timeout => 10) do
setcode do
sleep 999999
end
end
Viewing Fact Values
[puppetdb]: If your puppet master(s) are configured to use [PuppetDB][] and/or the inventory service, you can view and search all of the facts for any node, including custom facts. See the PuppetDB or inventory service docs for more info.
Legacy Fact Distribution
For Puppet versions prior to 0.24.0:
On older versions of Puppet, prior to 0.24.0, a different method
called factsync was used for custom fact distribution. Puppet would
look for custom facts on
puppet://$server/facts by default and
you needed to run puppetd with --factsync
option (or add factsync =
true
to puppetd.conf). This would enable the syncing of these files
to the local file system and loading them within puppetd.
Facts were synced to a local directory ($vardir/facts, by default)
before facter was run, so they would be available the first time.
If $factsource was unset, the --factsync
option is equivalent to:
file { $factdir: source => "puppet://puppet/facts", recurse => true }
After the facts were downloaded, they were loaded (or reloaded) into memory.
Some additional options were available to configure this legacy method:
The following command line or config file options are available (default options shown):
- factpath ($vardir/facts): Where Puppet should look for facts. Multiple directories should be colon-separated, like normal PATH variables. By default, this is set to the same value as factdest, but you can have multiple fact locations (e.g., you could have one or more on NFS).
- factdest ($vardir/facts): Where Puppet should store facts that it pulls down from the central server.
- factsource (puppet://$server/facts): From where to retrieve facts. The standard Puppet file type is used for retrieval, so anything that is a valid file source can be used here.
- factsync (false): Whether facts should be synced with the central server.
- factsignore (.svn CVS): What files to ignore when pulling down facts.
Remember the approach described above for factsync
is now deprecated and replaced by the plugin approach described in the Plugins In Modules page.
External Facts
External facts are available only in Facter 1.7 and later.
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.
Fact Locations
On Unix/Linux:
/etc/facter/facts.d/ # Puppet Open Source
/etc/puppetlab/facter/facts.d/ # Puppet Enterprise
On Windows 2003:
C:\Documents and Settings\All Users\Application Data\Puppetlabs\facter\facts.d\
On Windows 2008:
C:\ProgramData\Puppetlabs\facter\facts.d\
Executable facts — Unix
Executable facts on Unix work by dropping an executable file into the standard external fact path above.
You must ensure that the script has its execute bit set:
chmod +x /etc/facter/facts.d/my_fact_script.rb
For Facter to parse the output, the script must return key/value pairs on STDOUT in the format:
key1=value1
key2=value2
key3=value3
Using this format, a single script can return multiple facts.
Executable facts — Windows
Executable facts on Windows work by dropping an executable file into the external fact path for your version of Windows. Unlike with Unix, the external facts interface expects Windows scripts to end with a known extension. At the moment the following extensions are supported:
.com
andexe
: binary executables.bat
: batch scripts.ps1
: PowerShell scripts
As with Unix facts, each script must return key/value pairs on STDOUT in the format:
key1=value1
key2=value2
key3=value3
Using this format, a single script can return multiple facts in one return.
Enabling PowerShell Scripts
For PowerShell scripts (scripts with a ps1 extension) to work, you need to make sure you have the correct execution policy set.
See this Microsoft TechNet article for more detail about the impact of changing execution policy. We recommend understanding any security implications before making a global change to execution policy.
The simplest and safest mechanism we have found is to change the execution policy so that only remotely downloaded scripts need to be signed. You can set this policy with:
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
Here is a sample PowerShell script which outputs facts using the required format:
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 after changing the execution policy.
Structured Data Facts
Facter 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. At the moment, Facter supports the following extensions and data types:
-
.yaml
: YAML data, in the following format:--- key1: val1 key2: val2 key3: val3
-
.json
: JSON data, in the following format:{ "key1": "val1", "key2": "val2", "key3": "val3" }
-
.txt
: Key value pairs, in the following format:key1=value1 key2=value2 key3=value3
As with executable facts, structured data files can set multiple facts at once.
Troubleshooting
If your external fact is not appearing in Facter’s output, running Facter in debug mode should give you a meaningful reason and tell you which file is causing the problem:
# facter --debug
An example would be in cases where a fact returns invalid characters.
Let say you used a hyphen instead of an equals sign in your script test.sh
:
#!/bin/bash
echo "key1-value1"
Running facter --debug
should yield a useful error message:
...
Fact file /etc/facter/facter.d/sample.txt was parsed but returned an empty data set
...
If you are interested in finding out where any bottlenecks are, you can run Facter in timing mode and it will reflect how long it takes to parse your external facts:
facter --timing
The output should look similar to the timing for Ruby facts, but will name external facts with their full paths. For example:
$ facter --timing
kernel: 14.81ms
/usr/lib/facter/ext/abc.sh: 48.72ms
/usr/lib/facter/ext/foo.sh: 32.69ms
/usr/lib/facter/ext/full.json: 104.71ms
/usr/lib/facter/ext/sample.txt: 0.65ms
....
External Facts and stdlib
If you find that an external fact does not match what you have configured in your facter.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 Puppet, 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.
- Although we plan to allow distribution of external facts through Puppet’s pluginsync capability, this is not yet supported.