Embedded Ruby (ERB) is a templating language based on Ruby. Puppet evaluates ERB templates with the template and inline_template functions.
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 ... | EPP 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 |
-%>
|
<%= @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 EPP template for an
Apache config file:
ServerName <%= @fqdn %>
ServerAlias <%= @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 twoThis 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_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_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.