I have written a custom Puppet fact that requires the biosdevname tool to be installed. I'm not sure how to set things up correctly such that this tool will be installed before facter tries to instantiate the custom fact.

Facts are loaded early on in the process, so I can't simply put a package { biosdevname: ensure => installed } in the manifest, since by the time Puppet gets this far the custom fact has already failed.

I was curious if I could resolve this through Puppet's run stages. I tried:

stage { pre: before => Stage[main] }
class { biosdevname: stage => pre }


class biosdevname {
  package { biosdevname: ensure => installed }

But this doesn't work...Puppet loads facts before entering the pre stage:

info: Loading facts in physical_network_config
./physical_network_config.rb:33: command not found: biosdevname -i eth0
info: Applying configuration version '1320248045'
notice: /Stage[pre]/Biosdevname/Package[biosdevname]/ensure: created

Etc. Is there any way to make this work?

EDIT: I should make it clear that I understand, given a suitable package declaration, that the fact will run correctly on subsequent runs. The difficulty here is that this is part of our initial configuration process. We're running Puppet out of kickstart and want the network configuration to be in place before the first reboot.

It sounds like the only workable solution is to simply run Puppet twice during the initial system configuration, which will ensure that the necessary packages are in place.

Also, for Zoredache:

# This produces a fact called physical_network_config that describes
# the number of NICs available on the motherboard, on PCI bus 1, and on
# PCI bus 2.  The fact value is of the form <x>-<y>-<z>, where <x>
# is the number of embedded interfaces, <y> is the number of interfaces
# on PCI bus 1, and <z> is the number of interfaces on PCI bus 2.

em = 0
pci1 = 0
pci2 = 0

Dir['/sys/class/net/*'].each {
    |file| devname=File.basename(file)
    biosname=%x[biosdevname -i #{devname}]
    when biosname.match('^pci1')
        pci1 += 1
    when biosname.match('^pci2')
        pci2 += 1
    when biosname.match('^em[0-9]')
        em += 1

Facter.add(:physical_network_config) do
    setcode do
  • Can you post the source for your fact? – Zoredache Nov 2 '11 at 16:33
  • Let me also note that I don't actually know Ruby so it's possible this is ugly code. – larsks Nov 2 '11 at 17:08
  • Also: we call this the "railroad class" of a system, because it's similar to the notation used for classifying steam engines by the wheel configuration. – larsks Nov 2 '11 at 17:09

As far as I know, you can't. Either let it fail, or detect and fail gracefully. I have a number of plugins that only work on Debian that fail on Red Hat without consequence.

Also, please note that it is IMPOSSIBLE to have a fact evaluated AFTER some configuration went in. The architecture just doesn't support it:

Client                               Server
Compute facts
Ask for catalog passing facts =>     Receive Catalog request
                                     Compute catalog using facts
                              <=     Return Catalog
Based on the dependency tree,
  For each configuration with satisfied dependencies
    Apply configuration
    Mark (or not) dependency as satisfied
Send report, if configured so

So, you see, the configuration is only applied long, long after the facts were processed, and there's no way of going back. What may happen is that the next run will now be able to generate that fact.

See also the trick used by the Common module to handle the lack of lsbrelease on Debian without producing fatal errors.

  • This fact already fails without consequence...that is, upon failure, other than a bunch of errors on stderr, it produces a perfectly good value. Mostly I was just looking to see if there was a way to avoid running Puppet twice for this to work as intended. – larsks Nov 2 '11 at 17:06

Add a check into the fact code itself. Check if biosdevname exists before trying to run it. If it doesn't exist, set the value to undef. Use if statements within your manifests that require that fact to have a certain value.

  • Thanks. This isn't actually the problem. In the absence of the biosdevname tool the fact produces a suitable value that we can treat as "undef". Our manifest is already using a case statement to match the various values output by this fact. – larsks Nov 2 '11 at 17:04

You can add conditionals in your plugins to check for various things before adding facts, and additionally Facter has a "confine" method that will only run Facter discovery based on conditions in other facts (e.g., Windows support for Facter is heavily dependent on Confine)

Example of a conditional fact:

if File.exists?("/usr/bin/mysql")
  Facter.add(:mysql_version) do
    %x[#{mysqlcmd} "SELECT VERSION()"].to_s.strip

Example of a Confine:

# Packaging on OpenBSD.  Doesn't work anywhere else that I know of.
Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Package do
  desc "OpenBSD's form of `pkg_add` support."
  commands :pkginfo => "pkg_info", :pkgadd => "pkg_add", :pkgdelete => "pkg_delete"
  defaultfor :operatingsystem => :openbsd
  confine :operatingsystem => :openbsd

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.