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