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.
<% |
), 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.
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 %>
|
if
statement only appears in the output if the
condition is true.Expression-printing tags
Opening tag |
<%=
|
Closing tag |
%>
|
Closing tag with trailing white space and line break trimming |
-%>
|
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
Opening tag |
<%
|
Opening tag with indentation trimming |
<%-
|
Closing tag |
%>
|
Closing tag with trailing white space and line break trimming |
-%>
|
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
Opening tag with indentation trimming |
<%- |
|
Closing tag with trailing white space and line break trimming |
| -%>
|
<%- | 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 white space.)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
) -
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
Opening tag |
<%#
|
Closing tag |
%>
|
Closing tag with space trimming |
-%>
|
<%# 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
orinline_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.
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')
}
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.
--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.
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.