Using Puppet Bolt to apply roles to nodes, part 1

October 17, 2020 

NOTE: this article is somewhat outdated. Please refer to Serverless Puppet with control repo, Hiera, roles and profiles and Puppet Bolt instead.

Puppet Bolt is a designed to be an orchestration tool, but it can be used for configuration management as well. For example you may have a small environment of handful of nodes where having a dedicated Puppet server would be an overkill, or you have some nodes in the infrastructure where you can't or don't want to install a Puppet Agent.

The reason you'd want to use Bolt instead of, say, Ansible to manage this is simple: Bolt allows you to use your control repository as-is, without having to build your own puppet apply-based solution - something I've had to do several times in the past before Bolt came along.

Below I assume you have Bolt installed and configured to use your control repository as described in this article. You should have hiera-eyaml installed and configured on your Bolt controller (e.g. your laptop). You should be able to run commands on the target nodes with commands such as

bolt command run "date" -t node.example.org

Once that works all you're almost good to go. For the sake of example suppose you have a role like this:

class role::test {
  notify { 'foobar': }
}

All you need is to add a thin wrapper plan around it:

plan role::test
(
  TargetSpec $nodes,
)
{
  $nodes.apply_prep

  $results = apply($nodes) {
    include ::role::test
  }
}

Then you can simply apply the role using "bolt plan run":

bolt plan run --verbose role::test nodes=localhost
Starting: plan role::test
Starting: install puppet and gather facts on localhost
Finished: install puppet and gather facts with 0 failures in 10.07 sec
Starting: apply catalog on localhost
Started on localhost...
Finished on localhost:
  Notice: /Stage[main]/Role::Test/Notify[foobar]/message: defined 'message' as 'foobar'
  changed: 1, failed: 0, unchanged: 0 skipped: 0, noop: 0
Finished: apply catalog with 0 failures in 18.43 sec
Finished: plan role::test in 30.09 sec
Plan completed successfully with no result

In practice you'd use --run-as root and --sudo-password-prompt to apply real roles.

Now, the above is all good, but the problem is that you need one plan for each role. Plus you need to target the correct nodes with each plan or you could break things. Fortunately it is fairly easy to create a generic plan that will automatically apply the correct classes on each targeted node. The key is to use the contents of the Hiera classes array in the plan:

plan role::test
(
  TargetSpec $nodes,
)
{
  $nodes.apply_prep

  $nodes.split(',').each |$node| {
    $results = apply($node) {
      $classes = lookup('classes', Optional[Array[String]], 'first', undef)
      if $classes {
        $classes.each |$c| {
          include $c
        }
      }
    }
  }
}

This plan loops through each target and includes any classes found from the Hiera classes array. Suppose the node's yaml file (data/nodes/mynode.example.org) looks like this:

classes:
  - role::first
  - role::second

role::first::param: "This is the first role"
role::second::param: "This is the second role"

Of course normally you would not include several roles on a node, but this is just for the sake of demonstration. Now, the two roles (site/role/manifests/first.pp and second.pp) are essentially identical:


class role::first {
  $param = lookup('role::first::param', String)
  notify { $param: }
}

class role::second {
  $param = lookup('role::second::param', String)
  notify { $param: }
}

Now, when you run Bolt you get the output you'd expect:

bolt plan run --verbose role::test nodes=mynode.example.org
Starting: plan role::test
Starting: install puppet and gather facts on mynode.example.org
Finished: install puppet and gather facts with 0 failures in 11.24 sec
Starting: apply catalog on mynode.example.org
Started on mynode.example.org...
Finished on mynode.example.org:
  Notice: /Stage[main]/Role::First/Notify[This is the first role]/message: defined 'message' as 'This is the first role'
  Notice: /Stage[main]/Role::Second/Notify[This is the second role]/message: defined 'message' as 'This is the second role'
  changed: 2, failed: 0, unchanged: 0 skipped: 0, noop: 0
Finished: apply catalog with 0 failures in 9.69 sec

This is approach is still not perfect because of two reasons:

  • Puppet apply runs in serial, because the lookup() only works within the apply block
  • Splitting the TargetSpec parameter into individual targets is simplistic and will fail if anything except plain hostnames are given.
  • The target name has to match the yaml file name in Hiera

Some of these improvements will follow in a separate blog post. Stay tuned!

Samuli Seppänen
Samuli Seppänen
Author archive
menucross-circle