Creating templates using Embedded Ruby

Embedded Ruby (ERB) is a templating language based on Ruby. Puppet evaluates ERB templates with the template and inline_template functions.

If you’ve used ERB in other projects, it might have had different features enabled. This page describes how ERB works with Puppet .

Note: Puppet has a parallel templating system called Embedded Puppet (EPP), which has similar functionality to ERB, but is based on the Puppet language. EPP is the preferred and safer method because it isolates environments. See EPP and environment isolation for more information.

ERB structure and syntax

An ERB template looks like a plain-text document interspersed with tags containing Ruby code. When evaluated, this tagged code can modify text in the template.

Puppet passes data to templates via special objects and variables, which you can use in the tagged Ruby code to control the templates' output. The following example shows non-printing tags (<%), expression-printing tags (<%=), and comment tags (<%#). A hyphen in a tag (-) strips leading or trailing whitespace when printing the evaluated template:
<% if @keys_enable -%>
<%# Printing the keys file, trusted key, request key, and control key: -%>
keys <%= @keys_file %>
<% unless @keys_trusted.empty? -%>
trustedkey <%= @keys_trusted.join(' ') %>
<% end -%>
<% if @keys_requestkey != '' -%>
requestkey <%= @keys_requestkey %>
<% end -%>
<% if @keys_controlkey != '' -%>
controlkey <%= @keys_controlkey %>
<% end -%>

<% end -%>

ERB tags

Embedded Ruby (ERB) has two tags for Ruby code expressions, a tag for comments, and a way to escape tag delimiters.

The following table provides an overview of the main tag types used with ERB. See the sections below for additional detail about each tag, including instructions on trimming whitespace and escaping special characters.
I want to ... ERB tag syntax
Insert the value of a single expression. <%= EXPRESSION %>
Execute an expression without inserting a value. <% EXPRESSION %>
Add a comment. <%# COMMENT %>
Text outside a tag is treated as literal text, but is subject to any tagged Ruby 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 into the output.
Opening tag <%=
Closing tag %>

Closing tag with trailing whitespace and line break trimming

-%>
Example tag:
<%= @networking.fqdn %>
It must contain a snippet of Ruby code that resolves to a value; if the value isn’t a string, it is automatically converted to a string using its to_s method.

For example, to insert the value of the fqdn and hostname facts in an ERB template for an Apache config file:

ServerName <%= @networking.fqdn %>
ServerAlias <%= @networking.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
<% end -%>

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
<% do |server| %>server <%= server %>
<% end -%>

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

Comment tags

A comment tag’s contents do not appear in the template's output.
Opening tag <%#
Closing tag %>
Closing tag with line break 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 ERB template would turn out as <% Test %%>, not <% Test %>.

Accessing Puppet variables

ERB templates can access Puppet variables. This is the main source of data for templates.

An ERB template has its own local scope, and its parent scope is set to the class or defined type that evaluates the template. This means a template can use short names for variables from that class or type, but it can’t insert new variables into it.

There are two ways to access variables in an ERB template:

  • @variable
  • scope['variable'] and its older equivalent, scope.lookupvar('variable')

@variable

All variables in the current scope (including global variables) are passed to templates as Ruby instance variables, which begin with “at” signs (@). If you can access a variable by its short name in the surrounding manifest, you can access it in the template by replacing its $ sign with an @, so that $os becomes @os, and $trusted becomes @trusted.

This is the most legible way to access variables, but it doesn’t support variables from other scopes. For that, you need to use the scope object.

scope['variable'] or scope.lookupvar('variable')

Puppet also passes templates an object called scope, which can access all variables (including out-of-scope variables) with a hash-style access expression. For example, to access $ntp::tinker you would use scope['ntp::tinker'].

Another way to use the scope object is to call its lookupvar method and pass the variable’s name as its argument, as in scope.lookupvar('ntp::tinker'). This is exactly equivalent to the above, if slightly less convenient. This usage predates the hash-style indexing added in Puppet 3.0.

Puppet data types in Ruby

Puppet's data types are converted to Ruby classes as follows:
Puppet type Ruby class
Boolean Boolean
String String
Number Subtype of Numeric
Array Array
Hash Hash
Default Symbol (value :default)
Regexp Regexp
Resource reference Puppet::Pops::Types::PResourceType or Puppet::Pops::Types::PHostClassType
Lambda (code block) Puppet::Pops::Evaluator::Closure
Data type (type) A type class under Puppet::Pops::Types, such as Puppet::Pops::Types::PIntegerType
Undef NilClass (value nil)
Note: If a Puppet variable was never defined, its value is undef, which means its value in a template is nil.

Using Ruby in ERB templates

To manipulate and print data in ERB templates, you’ll need to know some Ruby. A full introductory Ruby tutorial is outside the scope of these docs, but this page provides an overview of Ruby basics commonly used in ERB templates.

Using if statements

The if ... end statement in Ruby lets you write conditional text. Put the control statements in non-printing tags, and the conditional text between the tags:
<% if <CONDITION> %> text goes here <% end %>
For example:
<% if @broadcast != "NONE" %>broadcast <%= @broadcast %><% end %>
The general format of an if statement is:
if <CONDITION>
  ... code ...
elsif <CONDITION>
  ... other code ...
end

Using iteration

Ruby lets you iterate over arrays and hashes with the each method. This method takes a block of code and executes it one time for each element in the array or hash. In a template, untagged text is treated as part of the code that gets repeated. You can think of literal text as an instruction, telling the evaluator to insert that text into the final output.

To write a block of code in Ruby, use either do |arguments| ... end or {|arguments| ... }. Note that this is different from Puppet lambdas — but they work similarly.
<% @values.each do |val| -%>
Some stuff with <%= val %>
<% end -%>
If $values was set to ['one', 'two'], this example would produce:
Some stuff with one
Some stuff with two
This example also trims line breaks for the non-printing tags, so they won’t appear as blank lines in the output.

Manipulating data

Your templates generally use data from Puppet variables. These values almost always be strings, numbers, arrays, and hashes.

These become the equivalent Ruby objects when you access them from an ERB template.

For information about the ways you can transform these objects, see the Ruby documentation for strings, integers, arrays, and hashes.

Also, note that the special undef value in Puppet becomes the special nil value in Ruby in ERB templates.

Calling Puppet functions from ERB templates

You can use Puppet functions inside ERB templates by calling the scope.call_function(<NAME>, <ARGS>) method.

This method takes two arguments:

  • The name of the function, as a string.

  • All arguments to the function, as an array. This must be an array even for one argument or zero arguments.
For example, to evaluate one template inside another:
<%= scope.call_function('template', ["my_module/template2.erb"]) %>
To log a warning using the Puppet logging system, so that the warning appears in reports:
<%= scope.call_function('warning', ["Template was missing some data; this config file might be malformed."]) %>
Note:

scope.call_function was added in Puppet 4.2.

Previous versions of Puppet created a function_<NAME> method on the scope object for each function. These could be called with an arguments array, such as <%= scope.function_template(["my_module/template2.erb"]) %>.

While this method still works in Puppet 4.2 and later versions, the auto-generated methods don’t support the modern function APIs, which are now used by the majority of built-in functions.

Example ERB template

The following example is taken from the puppetlabs-ntp module.

# ntp.conf: Managed by puppet.
#
<% if @tinker == true and (@panic or @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 @panic -%> panic <%= @panic %><% end %><% if @stepout -%> stepout <%= @stepout %><% end %>
<% end -%>

<% if @disable_monitor == true -%>
disable monitor
<% end -%>
<% if @disable_auth == true -%>
disable auth
<% end -%>

<% if @restrict != [] -%>
# Permit time synchronization with our time source, but do not
# permit the source to query or modify the service on this system.
<% @restrict.flatten.each do |restrict| -%>
restrict <%= restrict %>
<% end -%>
<% end -%>

<% if @interfaces != [] -%>
# Ignore wildcard interface and only listen on the following specified
# interfaces
interface ignore wildcard
<% @interfaces.flatten.each do |interface| -%>
interface listen <%= interface %>
<% end -%>
<% end -%>

<% if @broadcastclient == true -%>
broadcastclient
<% end -%>

# 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
<% [@servers].flatten.each do |server| -%>
server <%= server %><% if @iburst_enable == true -%> iburst<% end %><% if @preferred_servers.include?(server) -%> prefer<% end %><% if @minpoll -%> minpoll <%= @minpoll %><% end %><% if @maxpoll -%> maxpoll <%= @maxpoll %><% end %>
<% end -%>

<% if @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 <%= @udlc_stratum %>
restrict 127.127.1.0
<% end -%>

# Driftfile.
driftfile <%= @driftfile %>

<% unless @logfile.nil? -%>
# Logfile
logfile <%= @logfile %>
<% end -%>

<% unless @peers.empty? -%>
# Peers
<% [@peers].flatten.each do |peer| -%>
peer <%= peer %>
<% end -%>
<% end -%>

<% if @keys_enable -%>
keys <%= @keys_file %>
<% unless @keys_trusted.empty? -%>
trustedkey <%= @keys_trusted.join(' ') %>
<% end -%>
<% if @keys_requestkey != '' -%>
requestkey <%= @keys_requestkey %>
<% end -%>
<% if @keys_controlkey != '' -%>
controlkey <%= @keys_controlkey %>
<% end -%>

<% end -%>
<% [@fudge].flatten.each do |entry| -%>
fudge <%= entry %>
<% end -%>

<% unless @leapfile.nil? -%>
# Leapfile
leapfile <%= @leapfile %>
<% end -%>

Validating ERB 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 Ruby erb command to check Embedded Ruby (ERB) syntax.

ERB validation

To validate your ERB template, pipe the output from the erb command into ruby:
erb -P -x -T '-' example.erb | ruby -c
The -P switch ignores lines that start with ‘%’, the -x switch outputs the template’s Ruby script, and -T '-' sets the trim mode to be consistent with Puppet’s behavior. This output gets piped into Ruby’s syntax checker (-c).
If you need to validate many templates quickly, you can implement this command as a shell function in your shell’s login script, such as .bashrc, .zshrc, or .profile:
validate_erb() {
  erb -P -x -T '-' $1 | ruby -c
}
You can then run validate_erb example.erb to validate an ERB template.

Evaluating ERB templates

After you have an ERB 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 ERB templates that are in a template file

Put template files in the templates directory of a module. ERB files use the .erb extension.

To use a ERB template file, evaluate it with the template function. For example:
# template(<FILE REFERENCE>, [<ADDITIONAL FILES>, ...])
file { '/etc/ntp.conf':
  ensure  => file,
  content => template('ntp/ntp.conf.erb'),
  # Loads /etc/puppetlabs/code/environments/production/modules/ntp/templates/ntp.conf.erb
}

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 activemq/amq/activemq.xml.erb loads the <MODULES DIRECTORY>/activemq/templates/amq/activemq.xml.erb file.

The template function can take any number of additional template files, and concatenate their outputs together to produce the final string.

Evaluating ERB template strings

If you have a string value that contains template content, you can evaluate it with the inline_template 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_erb 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_template(<TEMPLATE STRING>, [<ADDITIONAL STRINGS>, ...])
file { '/etc/ntp.conf':
  ensure  => file,
  content => inline_template($ntp_conf_template),
}

The inline_template function can take any number of additional template strings, and concatenate their outputs together to produce the final value.