puppet

Puppet resource collection as a workaround of the defined() function's limitation created by order of catalog compilation


Puppet version 8 manages a laptop that includes profiles such as

include profile::common
...
include profile::git 

In the common class, I wish to update users' ~/.bashrc to show the git branch name on the command prompt. I can't assume that git will be installed on the laptop, so I only want to modify ~/.bashrc if the git package is defined. The order of the above include determines the order of Puppet's catalog compilation, so defined(Package['git']) is false, even though git will be installed on the laptop.

One workaround is to swap the lines above so that the git package is processed first. But I don't want to do that as there are many profiles on many roles.

I attempted to use Puppet's resource collector to force the git package to be installed before the user is created, such as in this snippet:

...
    Package <| title == 'git' |> {
      before => Accounts::User[$user_account],
    }

    if defined(Package['git']) {
      notify{"${user_account}: Git is defined":}
    } else {
      notify{"${user_account}: Git is not defined":}
    }
...

This approach fails and returns git is not defined. Why doesn't this work?


Solution

  • The order of the above include determines the order of Puppet's catalog compilation, so defined(Package['git']) is false, even though git will be installed on the laptop.

    Never use defined(). You're right that it depends on catalog compilation order, but you seem to underestimate how difficult it is to predict that. If you can predict it well enough to use defined() safely then defined() probably doesn't tell you anything you didn't already know anyway.

    Additionally, defined() doesn't necessarily even tell you what you actually want to know. For example, after you declare package { 'git': ensure => 'absent' }, evaluating defined('Package[git]') will yield true. It speaks to whether that resource is being managed, not to its target state.

    I attempted to use Puppet's resource collector to force the git package to be installed before the user is created, such as in this snippet:

    [...]

    Why doesn't this work?

    You already know this. You explained yourself why the other use of defined() does not work: defined() recognizes only resources whose declarations have already been processed by the catalog builder at the point in catalog compilation when the defined() call itself is processed. The declaration of the Package[git] resource has not yet been processed at the point when that defined() is evaluated. Resource collectors do not change catalog compilation order. If you give it a think, I expect you'll recognize that there's no way they could. Resource application order is a different consideration.

    One workaround is to swap the lines above so that the git package is processed first. But I don't want to do that as there are many profiles on many roles.

    The sooner you stop thinking in terms of defined(), the better off you will be. At a high level, the alternative is to make the decision about ~/.bashrc configuration at a point where it is already known whether git is to be installed. The most natural place for that would be in profile::git, but there are other alternatives. For example, if the decision is driven by site data, then making both decisions based on the same data would get you consistency.