homeblogfacter part 1 facter 101

Facter Part 1: Facter 101

With the release of facter 1.5.8, it is a great opportunity to revisit this utility and review how it works in conjunction with Puppet. This is a multi-part blog post that will progressively dive deeper into facter and writing custom facts for Puppet. Facter is a standalone tool based on Ruby that provides system information in "key => value" pairs:
# facter
architecture => i386
ipaddress =>
is_virtual => true
kernel => Linux
kernelmajversion => 2.6
operatingsystem => CentOS
operatingsystemrelease => 5.5
physicalprocessorcount => 0
processor0 => Intel(R) Core(TM)2 Duo CPU     P8800  @ 2.66GHz
processorcount => 1
productname => VMware Virtual Platform
Facter can be used independently from Puppet to gather information about a system. Whether it's parsing the /proc/xen directory on Linux or running prtdiag command on Solaris, the tool does a great job abstracting the specific operating system commands used to determine the collection of facts. When used in conjunction with Puppet, facts gather through the system allows the puppet master to make intelligent decisions during manifest compilation. Within your puppet manifest, you can reference any key value pairs provided by facter by prefixing the hash key with "$", since facter variables are set at top scope I prefer "$::" to make it explicit:
case $::operatingsystem {
  'CentOS': { include centos }
  'MacOS':  { include mac }
If the default set of facts are not sufficient, there are two ways to extend Facter to provide additional fact. First we can create custom facts using ruby. This sounds far more intimidating, but as you can see in the example below it's actually rather straightforward.
require 'facter'
Facter.add(:system_role) do
  setcode "cat /etc/system_role"
Let's break down the example line by line, require 'facter' loads the facter library, we add a new fact called system_role via Facter.add(:system_role). For users new to ruby, :system_role is a symbol for "system_role", do/end indicate the beginning and end of a code block ({} also works). Last, setcode method executes the string as an OS command to obtain the value of system_role fact. Obviously not all facts is are a one line command, in that case, provide a do/end block with setcode method and the last line will be the return value for the custom fact. After a quick rewrite the previous example is now:
require 'facter'
Facter.add(:system_role) do
  setcode do
    Facter::Util::Resolution.exec("cat /etc/system_role")
If you read an earlier version of this blog post, originally OS commands was executed via %x{}, and that has been replaced by Facter::Util::Resolution.exec, because it provides several benefits such as searching the command $PATH, chomp the output string to remove newline, and handle the difference between Windows and Unix. Another way to extend facter is by using environment variables prefixed with FACTER_. So matching the ruby example above would simply be:
export FACTER_system_role=`cat /etc/system_role`; facter
One thing to be aware is environment variables will override custom ruby facts, but not facts generated by facter. FACTER_operatingsystem will not override the value CentOS in the example above, but FACTER_system_role will take precedence over the custom fact written in system_role.rb. It makes sense to name custom facts either with a sensible prefix, which avoids this problem as well as makes finding custom facts easier (there's also suggestions to use qualified names along the line of com.puppetlabs.system_role). Though you could write a wrapper to update facter environment variables on each puppet execution, it make more sense to use them when the facts are static, and write ruby custom facts if the value is dynamic. To end this post, a quick summary regarding facter:
  • When using facts in puppet manifest, preface with $:: to explicitly specify top scope.
  • Facter can be extended using ruby custom facts, or environment variables starting with FACTER_.
  • Facts provided through FACTER_ environment variables have precedence over ruby custom facts.
  • Custom facts should be named appropriately to avoid potential collisions.
We will dive into further details on how to test/distribute custom facts in part 2.