Custom facts overview
You can add custom facts by writing snippets of Ruby code on the primary Puppet server. Puppet then uses plug-ins in modules to distribute the facts to the client.
For information on how to add custom facts to modules, see Module plug-in types.
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 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 Facter. These additional facts can then be distributed to Puppet clients and are available for use in manifests and templates, just like any other fact is.
lib/facter/util
fails
with an error.Structured and flat facts
A typical fact extracts a piece of information about a system and returns it as either as a simple value (“flat” fact) or data organized as a hash or array (“structured” fact). There are several types of facts classified by how they collect information, including:
-
Core facts, which are built into Facter and are common to almost all systems.
-
Custom facts, which run Ruby code to produce a value.
-
External facts, which return values from pre-defined static data, or the result of an executable script or program.
All fact types can produce flat or structured values.
Loading custom facts
Facter offers multiple methods of loading facts.
These include:
-
$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
$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:
#~/lib/ruby
└── facter
├── rackspace.rb
├── system_load.rb
└── users.rb
Facter loads facter/system_load.rb
, facter/users.rb
, and facter/rackspace.rb
.
Using the --custom-dir
command line option
--custom-dir
options on the command line that specifies a single
directory to search for custom facts. Facter attempts to
load all Ruby files in the specified directories. This
allows you to do something like this:
$ ls my_facts
system_load.rb
$ ls 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
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:$ 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
-p
with puppet facts show
. The puppet facts show
command is the default action for Puppet facts. Facter also
accepts puppet facts
.Two parts of every fact
Most facts have at least two elements.
A call to
Facter.add('fact_name')
, which determines the name of the fact.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 facts
Puppet gets information about a system from Facter, and the most common way for Facter to get that information is by executing 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 thesetcode do ... end
block. Whatever thesetcode
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.
|
) 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
uname --hardware-platform
to single
out a specific type of workstation, you create a custom fact. -
Start by giving the fact a name, in this case,
hardware_platform
. -
Create the fact in a file called
hardware_platform.rb
on the primary Puppet server:# hardware_platform.rb Facter.add('hardware_platform') do setcode do Facter::Core::Execution.execute('/bin/uname --hardware-platform') end end
-
Use the instructions in the Plug-ins in modules docs to copy the new fact to a module and distribute it. During your next Puppet run, the value of the new fact is available to use in your manifests and templates.
Using other facts
You can write a fact that uses other facts by accessing Facter.value('somefact')
. If the fact fails to resolve or is not present, Facter returns nil
.
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
Facter.value
or Facter.fact
, for example,
Facter.value('OS')
and Facter.value('os')
return the same
value. Facter also downcases custom or external facts with uppercase or mixed-case
names.Configuring facts
Facts have properties that you can use to customize how they are evaluated.
Confining facts
One of the more commonly used properties is the confine
statement, which restricts the fact to run only on systems that match another given
fact.
Facter.add('powerstates') do
confine kernel: 'Linux'
setcode do
Facter::Core::Execution.execute('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. Because this is available only 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.['os']['family']
, you can use
Facter.value
: You can also use a Ruby
block: confine 'os' do |os|
os['family'] == 'RedHat'
end
Fact precedence
A single fact can have multiple resolutions, each of which is a different way of
determining the value of the fact. It’s common to have different resolutions for different
operating systems, for example. To add a new resolution to a fact, you add the fact again
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 Facter decides the issue of resolution precedence is the weight
property. After Facter 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
, Facter moves on to the next resolution (by
descending weight) until it gets a value for the fact.
confine
statements it has, so that more specific resolutions take priority over
less specific resolutions. External facts have a weight of 1000 — to override them, set a
weight above 1000.
# 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
Execution timeouts
Facter 4 supports timeouts on resolutions. If the timeout is exceeded, Facter prints an error message.
Facter.add('foo', {timeout: 0.2}) do
setcode do
Facter::Core::Execution.execute("sleep 1")
end
End
Facter::Core::Execution#execute
:.Facter.add('sleep') do
setcode do
begin
Facter::Core::Execution.execute('sleep 10', options = {:timeout => 5})
'did not timeout!'
rescue Facter::Core::Execution::ExecutionFailure
Facter.warn("Sleep fact timed out!")
end
end
end
When Facter runs as standalone, using
Facter.warn
ensures that the message is printed to
STDERR
. When Facter is called as part of a
catalog application, using Facter.warn
prints the message to Puppet’s log. If an exception is not caught, Facter automatically logs it as an error.Structured 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 section of the Custom facts overview.
Facter 4 introduced a new way to aggregate facts using dot notation. For more information, see Writing aggregate resolutions.
Aggregate resolutions
If your fact combines the output of multiple commands, 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.
:type => :aggregate
parameter: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:
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:
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, Facter merges all of your data into one array or hash and
uses that as the fact’s value.For more examples of aggregate resolutions, visit Writing facts with aggregate resolutions.
Viewing fact values
If your Puppet primary servers are configured to use PuppetDB, you can view and search all of the facts for any node, including custom facts.
See the PuppetDB docs for more info.
Environment facts
Environment facts allow you to override core facts and add custom facts.
To access a fact set with environment variables, you can use the CLI or the Ruby API
(Facter.value
or Facter.fact
methods). Use the
environment variable prefixed with FACTER_
, for example:
$ facter virtual
physical
$ FACTER_virtual=virtualbox facter virtual
virtualbox
Note that environment facts are downcased before they are added to the fact collection,
for example, FACTER_EXAMPLE
and FACTER_example
resolve
to a single fact named example
.