Search code examples
puppet

How can you specify anchoring to only happen within certain classes/scopes in puppet


I saw this question which is a user having the same issue, but I am not asking the same question(s).

I am having the same cyclic dependency issue in puppet as the other OP. The cause for this error is that we are using an upstream module that has built in anchoring that makes sense, but is incompatible with the way we anchor the components of the upstream apt module we are using in manifests/site.pp to ensure apt update runs when a new source is added.

The puppet code in manifests/site.pp which ensures any apt source runs before apt update, which happens before any package is installed, is:

Apt::Source <| |> -> Class['apt::update'] -> Package <| |>

The upstream docker module is incompatible with this, specifically, this line of the repo.pp puppet manifest is anchoring a package before an apt source like so:

Package['apt-transport-https'] -> Apt::Source <||>

So a legitimate cyclical dependency cycle emerges.

Obviously I could fix this by either updating the upstream module to be compatible (which has been opened by someone else as an issue here, so apparently is a problem for others too), or updating our anchoring in manifests/site.pp to remove it and add it each appropriate module/package (yuck).

The solution I am more interested in, both to solve this specific problem and also to get a better general understanding of puppet manifests, is if there is a way to set manifests/site.pp so the specific anchoring pattern of apt source, apt update, and package (referenced above) only occurs within certain scopes.

Similar to the if logic used to switch on things like hostname:

if $hostname !~ /^myhostname/ {}

Is there a way to reference the class/scope in puppet instead of the facts/variables about the node? This code doesn't work, but I am looking for something theoretically like:

if scope != 'docker' {
  Apt::Source <| |> -> Class['apt::update'] -> Package <| |>
}

Alternatively, if there isn't a way to do this, would I be correct in stating that puppet manifests contain logic about things that apply to the entire node (e.g. hostname, memory, number of CPUs) and the resources included apply to the entire compiled manifest, whereas the ruby functions can contain more specific logic about the puppet execution scope?


Solution

  • [Is there] a way to set manifests/site.pp so the specific anchoring pattern of apt source, apt update, and package (referenced above) only occurs within certain scopes[?]

    In the first place, you should not be declaring relationships between Apt::Source resources and Class['apt::update'], at least not if these are from the puppetlabs-apt module. It should be enough that that class is not a public class of the module -- code outside the module ought therefore never to touch it. In addition, however, that module already takes care to manage relationships among the classes and resource types it provides, and you should not muck with that. In other words, you're trying too hard.

    In fact, that's basically what's gotten you into trouble. The chain expression you inserted into your site.pp is too broad, covering resources that belong to third-party modules in addition to the ones you want it to cover.

    If you're asking how to be more specific, then you should consider using a filter predicate in your collector expressions. For example:

    Exec['apt-update'] -> Package<| tag == 'docker' |>
    

    (Note that the puppetlabs-apt module documents the presence and availability of Exec['apt-update'], which makes it fairly safe to use.)

    That sometimes plays nicely with tags, as demonstrated above, but you may find that Puppet's automatic tagging covers more resources than is suitable for your purposes, and having to manually tag your resources defeats the purpose.

    Alternatively, if there isn't a way to do this, would I be correct in stating that puppet manifests contain logic about things that apply to the entire node (e.g. hostname, memory, number of CPUs) and the resources included apply to the entire compiled manifest, whereas the ruby functions can contain more specific logic about the puppet execution scope?

    That sounds too broad and too vague.

    All class and resource declarations have global visibility and effect. Similarly, collectors collect resources regardless of the scope in which the declaration appears or the relative order in which those declarations are evaluated during catalog building.

    Manifest code is evaluated during catalog building. It can rely on node facts, on external data available to the catalog builder for customizing the catalog to be built for the target node. It can also rely on data hardcoded into your manifests, but in itself that can hardly be viewed as "customization".

    Puppet supports Ruby plug-ins of various kinds, with various scopes, roles, and capabilities. Technically, they can access Puppet internals because they run in Puppet's overall Ruby context, but they should not do so. Generally speaking, Ruby plugins should restrict themselves to essentially the same information available to manifest code. Or less.