Selecting targets for plans in Puppet Enterprise
Tags: puppet, bolt, plans, targetspec
Published: 2023-03-13 17:41:14

This article was previously published on the Puppet blog by Steve Axthelm.

Do you author plans for Puppet Enterprise? Looking for ways to improve them? Read on!

The Puppet Plan language allows a variety of methods to pick targets. In this article we will explore two of these methods (TargetSpec parameters and PuppetDB queries) and how plan authors can employ the latter to:

- Improve the user experience of running the plans.
- Reduce input errors.
- Restrict the scope of nodes that the plan can execute against.

Note: the following examples assume that you are running against targets that have the Puppet agent installed. You can achieve similar results for targets that are not running the Puppet agent by gathering facts with the facts plan.

## TargetSpec parameters
The most basic target input a plan can have is a parameter that is a [TargetSpec](https://puppet.com/docs/bolt/latest/writing_plans.html#targetspec):

```
plan test::targets(
  TargetSpec $nodes_to_target,
){
  run_command("whoami", $nodes_to_target)
}
```

Users running the plan on the command line can then supply the targets through an inventory file, a list of FQDNs (fully qualified domain names), etc., and Puppet Enterprise Console users will be presented with a plain text input where they can enter FQDNs.

![Run a plan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q71kw1rmu4oxdr9ozb98.png)

This is very flexible, but when authoring plans that other users will run, this much flexibility is often neither required nor desired, and also introduces the possibility for entry error. General user experience guidelines include error prevention when possible, so let's take a look at an alternative method for target input.

## Using Enum Parameters and PuppetDB Queries

Using Enum type parameters is a great way to decrease the chances of user input errors. We could hard-code the targets into the plan and allow users to choose a set of targets. This would solve for user entry errors, but it would be fragile. We can specify targets in a more robust manner by using PuppetDB queries. Perhaps we have an application monitoring team that watches over web servers, load balancers and database servers. We can provide them with a quick way to select the types of hosts on which they are interested in running a task. [PuppetDB queries](https://puppet.com/docs/puppetdb/latest/api/query/v4/pql.html) are very flexible and can query for hosts using a variety of methods.

This plan takes a server_type parameter, queries all targets that fit the `server_type` profile through a PuppetDB query that checks if they have `Apache`, `Haproxy`, or `Postgresql::Server` classes applied to the hosts, and returns the last boot time for each host.

```
plan server_team::check_boot_time(
  Enum['Webservers', 'Balancers', 'Database'] $server_type,
){
  $server_type_query = case $server_type {
    'Webservers': { 'resources[certname] { type = "Class" and title = "Apache" }' }
    'Balancers': { 'resources[certname] { type = "Class" and title = "Haproxy" }' }
    'Database': { 'resources[certname] { type = "Class" and title = "Postgresql::Server" }' }
  }

  $server_type_results = puppetdb_query($server_type_query)
  $server_type_targets = $server_type_results.map |$r| { $r["certname"] }

  run_task("reboot::last_boot_time", $server_type_targets)
}
```
Command line interface (CLI) users can then run the plan with `puppet plan run server_team::check_boot_time server_type=<value>` and Puppet Enterprise Console users are presented with a select form control where they can choose the type:


![PE Plan Parameters](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/88d7gc6x0jt73dze9a3h.png)

This is a big improvement in user experience for the users who will run the plan and it also bakes in limited scope with respect to which nodes the plan can be run against. [Trusted facts](https://puppet.com/docs/puppet/7/lang_facts_builtin_variables.html#lang_facts_builtin_variables-trusted-facts) can also be employed in the queries to make the scope limiting more secure. This plan takes two parameters, `rhel_major_version`, and `trusted_domain`, constructs a PuppetDB query from those using facts and trusted facts, and then simply echoes out the `trusted_domain` for each host returned from the query.

```
plan server_team::echo_trusted_domain(
  Enum['6', '7', '8'] $rhel_major_version,
  Enum['vm', 'com', 'net'] $trusted_domain,
){
  $target_query = "trusted.domain = '$trusted_domain' and facts.os.family = 'RedHat' and facts.os.release.major = '$rhel_major_version'"

  $target_query_result = puppetdb_query("inventory[certname] { $target_query }")
  $command_targets = $target_query_result.map |$r| { $r["certname"] }

  run_command("echo $trusted_domain", $command_targets)
}
```
CLI users can run the plan with:

```
puppet plan run server_team::echo_trusted_domain rhel_major_version=<value> trusted_domain=<value>
```
And Puppet Enterprise Console users will be presented with select form controls for the parameters:

![Puppet parameter values 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2f5ez0384th9drc7y5b4.png)

If the user supplies a value for either of the parameters that are not one of the enumerated options, an error will be thrown:


`puppet plan run server_team::echo_trusted_domain rhel_major_version=7 trusted_domain=info`

```
{
  "kind": "bolt/pal-error",
  "msg": "test::targets: parameter 'trusted_domain' expects a match for Enum['com', 'net', 'vm'], got 'info'",
  "details": {
  }
}
```
This ensures that users who run your plans will be able to supply only the parameter values you expect.

While the plans presented are simple in nature, hopefully they will spark some ideas for how you can improve the experience of the users who run plans that you author.

## Learn More
[Learn more](https://puppet.com/docs/bolt/latest/plans.html) about orchestrating workflows with Bolt plans.
[Learn more](https://puppet.com/docs/pe/latest/plans-in-pe.html) about Bolt plans in Puppet Enterprise.