Writing facts with simple resolutions
Most facts are resolved all at the same time, without any need to merge data from different sources. In that case, the resolution is simple. Both flat and structured facts can have simple resolutions.
Main components of simple resolutions
Simple facts are typically made up of the following parts:
A call to
Facter.add(:fact_name)
:- This introduces a new fact or a new resolution for an existing fact with the same name.
The name can be either a symbol or a string.
The rest of the fact is wrapped in the
add
call’sdo ... end
block.
Zero or more
confine
statements:Determine whether the resolution is suitable (and therefore is evaluated).
Can either match against the value of another fact or evaluate a Ruby block.
If given a symbol or string representing a fact name, a block is required and the block receives the fact’s value as an argument.
If given a hash, the keys are expected to be fact names. The values of the hash are either the expected fact values or an array of values to compare against.
If given a block, the confine is suitable if the block returns a value other than
nil
orfalse
.
An optional
has_weight
statement:When multiple resolutions are available for a fact, resolutions are evaluated from highest weight value to lowest.
Must be an integer greater than 0.
Defaults to the number of
confine
statements for the resolution.
A
setcode
statement that determines the value of the fact:Can take either a string or a block.
If given a string, Facter executes it as a shell command. If the command succeeds, the output of the command is the value of the fact. If the command fails, the next suitable resolution is evaluated.
If given a block, the block’s return value is the value of the fact unless the block returns
nil
. Ifnil
is returned, the next suitable resolution is evaluated.Can execute shell commands within a
setcode
block, using theFacter::Core::Execution.exec
function.If multiple
setcode
statements are evaluated for a single resolution, only the lastsetcode
block is used.
Set all code inside the sections outlined above — there must not be any code outsidesetcode
andconfine
blocks other than an optionalhas_weight
statement in a custom fact.
How to format facts
The format of a fact is important because of the way that Facter evaluates them — by
reading all the fact definitions. If formatted incorrectly, Facter can execute code
too early. You need to use the setcode
correctly. Below is a good
example and a bad example of a fact, showing you where to place the
setcode
.
Good:
Facter.add('phi') do confine owner: "BTO" confine :kernel do |value| value == "Linux" end setcode do bar=Facter.value('theta') bar + 1 end end
In this example, the bar=Facter.value('theta')
call is guarded by
setcode
, which means it is not executed unless or until it is appropriate
to do so. Facter loads all Facter.add
blocks first, use any OS or
confine/weight information to decide which facts to evaluate, and once it chooses, it
selectively executes setcode
blocks for each fact that it needs.
Bad:
Facter.add('phi') do confine owner: "BTO" confine :kernel do |value| value == "Linux" end bar = Facter.value('theta') setcode do bar + 1 end end
In this example, the Facter.value('theta')
call is outside of the guarded
setcode
block and in the unguarded part of the
Facter.add
block. This means that the statement always executes, on every
system, regardless of confine, weight, or which resolution of phi
is
appropriate. Any code with possible side-effects, or code pertaining to figuring out the
value of a fact, must be kept inside the setcode
block. The
only code left outside setcode
is code that helps Facter choose which
resolution of a fact to use.
Examples
The following example shows a minimal fact that relies on a single shell command:
Facter.add(:rubypath) do setcode 'which ruby' end
The following example shows different resolutions for different operating systems:
Facter.add(:rubypath) do setcode 'which ruby' end Facter.add(:rubypath) do confine 'os' do |os_fact| os_fact['family'] == "Windows" end # Windows uses 'where' instead of 'which' setcode 'where ruby' end
The following example shows a more complex fact, confined to Linux with a block:
Facter.add(:jruby_installed) do confine :kernel do |value| value == "Linux" end setcode do # If jruby is present, return true. Otherwise, return false. Facter::Core::Execution.which('jruby') != nil end end