Creating templates using Embedded Puppet

Embedded Puppet (EPP) is a templating language based on the Puppet language. You can use EPP in Puppet 4 and higher, and with Puppet 3.5 through 3.8 with the future parser enabled. Puppet evaluates EPP templates with the epp and inline_epp functions.

EPP structure and syntax

An EPP template looks like a plain-text document interspersed with tags containing Puppet expressions. When evaluated, these tagged expressions can modify text in the template. You can use Puppet variables in an EPP template to customize its output.

The following example shows parameter tags (<% |), non-printing expression tags (<%), expression-printing tags (<%=), and comment tags (<%#). A hyphen in a tag (-) strips leading or trailing whitespace when printing the evaluated template:
<%- | Boolean $keys_enable,
      String  $keys_file,
      Array   $keys_trusted,
      String  $keys_requestkey,
      String  $keys_controlkey
| -%>
<% if $keys_enable { -%>

<%# Printing the keys file, trusted key, request key, and control key: -%>
keys <%= $keys_file %>
<% unless $keys_trusted =~ Array[Data,0,0] { -%>
trustedkey <%= $keys_trusted.join(' ') %>
<% } -%>
<% if $keys_requestkey =~ String[1] { -%>
requestkey <%= $keys_requestkey %>
<% } -%>
<% if $keys_controlkey =~ String[1] { -%>
controlkey <%= $keys_controlkey %>
<% } -%>

<% } -%>

EPP tags

Embedded Puppet (EPP) has two tags for Puppet code expressions, optional tags for parameters and comments, and a way to escape tag delimiters.

The following table provides an overview of the main tag types used with EPP. See the sections below for additional detail about each tag, including instructions on trimming whitespace and escaping special characters.
I want to ... EPP tag syntax
Insert the value of a single expression. <%= EXPRESSION %>
Execute an expression without inserting a value. <% EXPRESSION %>
Declare the template’s parameters. <% | PARAMETERS | %>
Add a comment. <%# COMMENT %>
Text outside a tag is treated as literal text, but is subject to any tagged Puppet code surrounding it. For example, text surrounded by a tagged if statement only appears in the output if the condition is true.

Expression-printing tags

An expression-printing tag inserts the value of a single Puppet expression into the output.
Opening tag <%=
Closing tag %>

Closing tag with trailing whitespace and line break trimming

-%>
Example tag:
<%= $fqdn %>
An expression-printing tag must contain any single Puppet expression that resolves to a value, including plain variables, function calls, and arithmetic expressions. If the value isn’t a string, Puppet automatically converts it to a string based on the rules for value interpolation in double-quoted strings.

All facts are available in EPP templates. For example, to insert the value of the fqdn and hostname facts in an EPP template for an Apache config file:

ServerName <%= $facts[fqdn] %>
ServerAlias <%= $facts[hostname] %>

Non-printing tags

A non-printing tag executes the code it contains, but doesn’t insert a value into the output.
Opening tag <%
Opening tag with indentation trimming <%-
Closing tag %>
Closing tag with trailing whitespace and line break trimming -%>
Non-printing tags that contain iterative and conditional expressions can affect the untagged text they surround.

For example, to insert text only if a certain variable was set, write:

<% if $broadcastclient == true { -%>
broadcastclient
<% } -%>

Expressions in non-printing tags don’t have to resolve to a value or be a complete statement, but the tag must close at a place where it would be legal to write another expression. For example, this doesn't work:

<%# Syntax error: %>
<% $servers.each -%>
# some server
<% |$server| { %> server <%= server %>
<% } -%>

You must keep |$server| { inside the first tag, because you can’t insert an arbitrary statement between a function call and its required block.

Parameter tags

A parameter tag declares which parameters the template accepts. Each parameter can be typed and can have a default value.
Opening tag with indentation trimming <%- |
Closing tag with trailing whitespace and line break trimming | -%>
Example tag:
<%- | Boolean $keys_enable = false, String $keys_file = '' | -%>
The parameter tag is optional; if used, it must be the first content in a template. Always close the parameter tag with a right-trimmed delimiter (-%>) to avoid outputting a blank line. Literal text, line breaks, and non-comment tags cannot precede the template’s parameter tag. (Comment tags that precede a parameter tag must use the right-trimming tag to trim trailing whitespace.)

The parameter tag’s pair of pipe characters (|) contains a comma-separated list of parameters. Each parameter follows this format:

Boolean $keys_enable = false
  • An optional data type, which restricts the allowed values for the parameter (defaults to Any)
  • A variable name

  • An optional equals (=) sign and default value, which must match the data type, if one was specified

Parameters with default values are optional, and can be omitted when the template is evaluated. If you want to use a default value of undef, make sure to also specify a data type that allows undef. For example, Optional[String] accepts undef as well as any string.

Comment tags

A comment tag’s contents do not appear in the template's output.
Opening tag <%#
Closing tag %>
Closing tag with space trimming -%>
Example tag:
<%# This is a comment. %>

Literal tag delimiters

If you need the template’s final output to contain a literal <% or %>, you can escape the characters as <%% or %%>. The first literal tag is taken, and the rest of the line is treated as a literal. This means that <%% Test %%> in an EPP template would turn out as <% Test %%>, not <% Test %>.

Accessing EPP variables

Embedded Puppet (EPP) templates can access variables with the $variable syntax used in Puppet.

A template works like a defined type:

  • It has its own anonymous local scope.
  • The parent scope is set to node scope (or top scope if there’s no node definition).
  • When you call the template (with the epp or inline_epp functions), you can use parameters to set variables in its local scope.
  • Unlike Embedded Ruby (ERB) templates, EPP templates cannot directly access variables in the calling class without namespacing. Fully qualify variables or pass them in as parameters.

EPP templates can use short names to access global variables (like $os or $trusted) and their own local variables, but must use qualified names (like $ntp::tinker) to access variables from any class. The exception to this rule is inline_epp.

Special scope rule for inline_epp

If you evaluate a template with the inline_epp function, and if the template has no parameters, either passed or declared, you can access variables from the calling class in the template by using the variables’ short names. This exceptional behavior is only allowed if all of the above conditions are true.

Should I use a parameter or a class variable?

Templates have two ways to use data:

  • Directly access class variables, such as $ntp::tinker

  • Use parameters passed at call time

Use class variables when a template is closely tied to the class that uses it, you don’t expect it to be used anywhere else, and you need to use a lot of variables.

Use parameters when a template is used in several different places and you want to keep it flexible. Remember that declaring parameters with a tag makes a template’s data requirements visible at a glance.

EPP parameters

When you pass parameters when you call a template, the parameters become local variables inside the template. To use a parameter in this way, pass a hash as the last argument of the epp or inline_epp functions.

For example, calling this:
epp('example/example.epp', { 'logfile' => "/var/log/ntp.log" })
to evaluate this template:
<%- | Optional[String] $logfile = undef | -%>
<%# (Declare the $logfile parameter as optional) -%>

<% unless $logfile =~ Undef { -%>
logfile <%= $logfile %>
<% } -%>
The keys of the hash match the variable names you’ll be using in the template, minus the leading $ sign. Parameters must follow the normal rules for local variable names.

If the template uses a parameter tag, it must be the first content in a template and you can only pass the parameters it declares. Passing any additional parameters is a syntax error. However, if a template omits the parameter tag, you can pass it any parameters.

If a template’s parameter tag includes any parameters without default values, they are mandatory. You must pass values for them when calling the template.

Sensitive data

Puppet (version 6.20 and later) can interpolate sensitive values. In your EPP template, you can access the sensitive variable without unwrapping. For example:

host=<%= $db_host %>
password=<%= $db_password %>

The rendered output is automatically sensitive and used as the file content:

db_password= Sensitive('secure_test')
host = examplehost
file { '/etc/service.conf':
  ensure => file,
  content => epp('<module>/service.conf.epp')
}
Note: ERB templates do not support interpolation of sensitive values — you have to manually unwrap and re-wrap these.

Example EPP template

The following example is an EPP translation of the ntp.conf.erb template from the puppetlabs-ntp module.

# ntp.conf: Managed by puppet.
#
<% if $ntp::tinker == true and ($ntp::panic or $ntp::stepout) { -%>
# Enable next tinker options:
# panic - keep ntpd from panicking in the event of a large clock skew
# when a VM guest is suspended and resumed;
# stepout - allow ntpd change offset faster
tinker<% if $ntp::panic { %> panic <%= $ntp::panic %><% } %><% if $ntp::stepout { -%> stepout <%= $ntp::stepout %><% } %>
<% } -%>

<% if $ntp::disable_monitor == true { -%>
disable monitor
<% } -%>
<% if $ntp::disable_auth == true { -%>
disable auth
<% } -%>

<% if $ntp::restrict =~ Array[Data,1] { -%>
# Permit time synchronization with our time source, but do not
# permit the source to query or modify the service on this system.
<% $ntp::restrict.flatten.each |$restrict| { -%>
restrict <%= $restrict %>
<% } -%>
<% } -%>

<% if $ntp::interfaces =~ Array[Data,1] { -%>
# Ignore wildcard interface and only listen on the following specified
# interfaces
interface ignore wildcard
<% $ntp::interfaces.flatten.each |$interface| { -%>
interface listen <%= $interface %>
<% } -%>
<% } -%>

<% if $ntp::broadcastclient == true { -%>
broadcastclient
<% } -%>

# Set up servers for ntpd with next options:
# server - IP address or DNS name of upstream NTP server
# iburst - allow send sync packages faster if upstream unavailable
# prefer - select preferrable server
# minpoll - set minimal update frequency
# maxpoll - set maximal update frequency
<% [$ntp::servers].flatten.each |$server| { -%>
server <%= $server %><% if $ntp::iburst_enable == true { %> iburst<% } %><% if $server in $ntp::preferred_servers { %> prefer<% } %><% if $ntp::minpoll { %> minpoll <%= $ntp::minpoll %><% } %><% if $ntp::maxpoll { %> maxpoll <%= $ntp::maxpoll %><% } %>
<% } -%>

<% if $ntp::udlc { -%>
# Undisciplined Local Clock. This is a fake driver intended for backup
# and when no outside source of synchronized time is available.
server   127.127.1.0
fudge    127.127.1.0 stratum <%= $ntp::udlc_stratum %>
restrict 127.127.1.0
<% } -%>

# Driftfile.
driftfile <%= $ntp::driftfile %>

<% unless $ntp::logfile =~ Undef { -%>
# Logfile
logfile <%= $ntp::logfile %>
<% } -%>

<% unless $ntp::peers =~ Array[Data,0,0] { -%>
# Peers
<% [$ntp::peers].flatten.each |$peer| { -%>
peer <%= $peer %>
<% } -%>
<% } -%>

<% if $ntp::keys_enable { -%>
keys <%= $ntp::keys_file %>
<% unless $ntp::keys_trusted =~ Array[Data,0,0] { -%>
trustedkey <%= $ntp::keys_trusted.join(' ') %>
<% } -%>
<% if $ntp::keys_requestkey =~ String[1] { -%>
requestkey <%= $ntp::keys_requestkey %>
<% } -%>
<% if $ntp::keys_controlkey =~ String[1] { -%>
controlkey <%= $ntp::keys_controlkey %>
<% } -%>

<% } -%>
<% [$ntp::fudge].flatten.each |$entry| { -%>
fudge <%= $entry %>
<% } -%>

<% unless $ntp::leapfile =~ Undef { -%>
# Leapfile
leapfile <%= $ntp::leapfile %>
<% } -%>

To call this template from a manifest (assuming that the template file is located in the templates directory of the puppetlabs-ntp module), add the following code to the manifest:

# epp(<FILE REFERENCE>, [<PARAMETER HASH>])
file { '/etc/ntp.conf':
  ensure  => file,
  content => epp('ntp/ntp.conf.epp'),
  # Loads /etc/puppetlabs/code/environments/production/modules/ntp/templates/ntp.conf.epp
}

Validating and previewing EPP templates

Before deploying a template, validate its syntax and render its output to make sure the template is producing the results you expect. Use the puppet epp compand-line tool for validating and rendering Embedded Puppet (EPP) templates.

EPP validation

To validate your template, run: puppet epp validate <TEMPLATE NAME>

The puppet epp command includes an action that checks EPP code for syntax problems. The <TEMPLATE NAME> can be a file reference or can refer to a <MODULE NAME>/<TEMPLATE FILENAME> as the epp function. If a file reference can also refer to a module, Puppet validates the module’s template instead.

You can also pipe EPP code directly to the validator: cat example.epp | puppet epp validate

The command is silent on a successful validation. It reports and halts on the first error it encounters. For information on how to modify this default behavior, see the command’s man page.

EPP rendering

To render your template, run: puppet epp render <TEMPLATE NAME>

You can render EPP from the command line with puppet epp render. If Puppet can evaluate the template, it outputs the result.

If your template relies on specific parameters or values to function, you can simulate those values by passing a hash to the --values option. For example:
puppet epp render example.epp --values '{x => 10, y => 20}'
You can also render inline EPP by using the -e flag or piping EPP code to puppet epp render, and even simulate facts using YAML. For details, see the command’s man page.

Evaluating EPP templates

After you have an EPP template, you can pass it to a function that evaluates it and returns a final string. The actual template can be either a separate file or a string value.

Evaluating EPP templates that are in a template file

Put template files in the templates directory of a module. EPP files use the .epp extension.

To use a EPP template file, evaluate it with the epp function. For example:
# epp(<FILE REFERENCE>, [<PARAMETER HASH>])
file { '/etc/ntp.conf':
  ensure  => file,
  content => epp('ntp/ntp.conf.epp', {'service_name' => 'xntpd', 'iburst_enable' => true}),
  # Loads /etc/puppetlabs/code/environments/production/modules/ntp/templates/ntp.conf.epp
}

The first argument to the function is the file reference: a string in the form '<MODULE>/<FILE>', which loads <FILE> from <MODULE>’s templates directory. For example, the file reference ntp/ntp.conf.epp loads the <MODULES DIRECTORY>/ntp/templates/ntp.conf.epp file.

Some EPP templates declare parameters, and you can provide values for them by passing a parameter hash to the epp function.

The keys of the hash must be valid local variable names (minus the $). Inside the template, Puppet creates variables with those names and assign their values from the hash. For example, with a parameter hash of {'service_name' => 'xntpd', 'iburst_enable' => true}, an EPP template would receive variables called $service_name and $iburst_enable.

When structuring your parameter hash, remember:

  • If a template declares any mandatory parameters, you must set values for them with a parameter hash.
  • If a template declares any optional parameters, you can choose to provide values or let them use their defaults.
  • If a template declares no parameters, you can pass any number of parameters with any names; otherwise, you can only choose from the parameters requested by the template.

Evaluating EPP template strings

If you have a string value that contains template content, you can evaluate it with the inline_epp function.

In older versions of Puppet, inline templates were mostly used to get around limitations — tiny Ruby fragments were useful for transforming and manipulating data before Puppet had iteration functions like map or puppetlabs/stdlib functions like chomp and keys.

In modern versions of Puppet, inline templates are usable in some of the same situations template files are. Because the heredoc syntax makes it easy to write large and complicated strings in a manifest, you can use inline_epp to reduce the number of files needed for a simple module that manages a small config file.

For example:

$ntp_conf_template = @(END)
...template content goes here...
END

# inline_epp(<TEMPLATE STRING>, [<PARAMETER HASH>])
file { '/etc/ntp.conf':
  ensure  => file,
  content => inline_epp($ntp_conf_template, {'service_name' => 'xntpd', 'iburst_enable' => true}),
}

Some EPP templates declare parameters, and you can provide values for them by passing a parameter hash to the epp function.

The keys of the hash must be valid local variable names (minus the $). Inside the template, Puppet creates variables with those names and assign their values from the hash. For example, with a parameter hash of {'service_name' => 'xntpd', 'iburst_enable' => true}, an EPP template would receive variables called $service_name and $iburst_enable.

When structuring your parameter hash, remember:

  • If a template declares any mandatory parameters, you must set values for them with a parameter hash.
  • If a template declares any optional parameters, you can choose to provide values or let them use their defaults.
  • If a template declares no parameters, you can pass any number of parameters with any names; otherwise, you can only choose from the parameters requested by the template.