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
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
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::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.
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, see the Aggregate resolutions section of the Custom facts overview page.
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.