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.

For example:

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 sysfs 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.

You can confine structured facts like ['os']['family'] using dotted notation. For example:

confine 'os.family' => :redhat

You can also use a Ruby block. For example:

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.

By default, the weight of a resolution is the number of confine statements it has, so that more specific resolutions take priority over less specific resolutions. Each external fact has a weight of 10,000. To override the default value, set a weight above 10_000, because Ruby ignores underscores (_) in numbers.

The following example code checks the role of a server. The value for role is set to 10,001 during the PostgreSQL server check, prioritizing the check for that role before the check for the server and desktop role.

# Check to see if this server has been marked as a postgres server
Facter.add('role') do
  has_weight 10_001
  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

You can also pass a timeout to 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.