## Embedded Puppet Templates (EPP)
Steve Traylen, IT-CM-LCS
steve.traylen@cern.ch
16th November 2018
---
## Typical Use Case
```shell
# /etc/ntp.conf
# List of time servers.
server ip-time-0.cern.ch
server ip-time-1.cern.ch
```
* Controlling a file with dynamic content.
* Templates can **never** update existing files.
---
### Embedded Ruby (ERB) Example
```puppet
# puppet manifest
$servers = ['ip-time-0.cern.ch','ip-time-1.cern.ch' ]
file{'/etc/ntp.conf':
content => template('ntp/ntp.conf.erb')
}
```
```erb
<%# erb template `ntp.conf.erb` %>
# /etc/ntp.conf
<% @servers.each do | node | -%>
server <%= node %>
<% end -%>
```
* Variables loaded from class's scope as ruby instance variable.
---
### Drawbacks to ERB templates
* Learning ruby? CERN chose puppet rather than CHEF partly to avoid everyone learning ruby.
* The various methods to access global scope have changed over the years:
* `scope['ntp::server']`
* `scope.lookupvar['ntp::server']`
* Ruby allows more than should be done in templates, e.g. dns lookup, maths, ...
---
### Draw backs to ERB templates
* Ruby can behave differently to puppet, e.g undefined variables:
```puppet
$puppet_string = "${notset}abc"
$erb_string = inline_template('<%= @notset %>'abc)
```
* `$puppet_string` will be `"abc"`.
* `$erb_string` results in `@notset` is undefined.
* Created complicated conditions in templates:
```erb
<% if ! @notset.nil? || @notset == '' %->
notset is <@= @noset %>
<% end -%>
```
---
### EPP templates
* Embedded Puppet templates added with Puppet 4.
* Syntax very familiar to puppet e.g `$variable`
* Two functions exist: `epp` and `epp_inline`
* Note ERB is not currently going anywhere.
---
### EPP templates behave like defined types
* Variables can be passed in as parameters from manifest.
* Variables can be accessed from global scope with full path.
* With `epp` function local variables can not be accessed.
* With `epp_inline` local manifest variables can be accessed.
---
### Embedded Puppet(epp) Example
```puppet
class ntp{
$servers = ['ip-time-0.cern.ch','ip-time-1.cern.ch' ]
file{'/etc/ntp.conf':
content => epp('ntp/ntp.conf.epp',{ servers => $servers})
}
}
```
```epp
<%# epp template ntp.conf.epp %>
# /etc/ntp.conf
<% $servers.each | $_node | { -%>
server <%= $_node %>
<% } -%>
```
---
* <% %> execute puppet DSL.
* <%= %> execute puppet DSL and print output.
* A `-` can be used to suppress white space and a newline.
```epp
<%# A epp comment %>
<% ['a','b','c'].each |$_i| { -%>
<%- ['x','y','z'].each |$_j| { -%>
<%= $i %> <%= upcase($j) %>
<-% } -%>
<%- } -%>
```
```
a X
a Y
a Z
b X
...
```
---
## Type Validation
```epp
<%- |
Array[Stdlib::Fqdn] $servers,
| -%>
# /etc/ntp.conf
<% $servers.each | $_node | { -%>
server <%= $_node %>
<% } -%>
```
* Type definitions at start of file.
* Defaults can be specified.
* Following would fail as invalid hostname.
```puppet
$content => epp('ntp/ntp.conf.epp',{
servers => ['ntp_0.cern.ch'],
})
```
---
## Global Variables Space
```puppet
class ntp{
$servers = ['ip-time-0.cern.ch','ip-time-1.cern.ch' ]
file{'/etc/ntp.conf':
content => epp('ntp/ntp.conf.epp')
}
}
```
```epp
# /etc/ntp.conf
<% $ntp::servers.each | $_node | { -%>
server <%= $_node %>
<% } -%>
```
* Accessed same as puppet, full path.
* The facts hash `$facts['os']['family']`.
---
## Inline EPP
```puppet
$var = 'value'
$erb_string = inline_template('<%= @notset %>'abc)
$epp_string = inline_epp('<%= $notset %><%= $var %>abc')
```
* `inline_epp` much like erb's `inline_template`.
* Local variables are loaded locally, unlike `epp`.
* Unset variables will evaluate to "" like puppet.
* ERB was throwing an error.
---
## Command Line Render
```bash
$ puppet epp render file.epp \
--values \
'{"servers" => ["ntp1.example.org","ntp2.example.org"]}'
server ntp1.example.org
server ntp2.example.org
```
* EPP can be redered on the command line.
* Buggy with puppet 4. Okay with puppet 5.
---
### HEREDOC Simple
```puppet
$motd = @(EOF)
Welcome to this machine
It is best machine at CERN.
EOF
```
* Puppet manifests support HERE docs.
---
### HEREDOC Left Justification
```puppet
if $setmotd {
$motd = @(EOF)
This machine is even better though.
This text is actually left justified
| EOF
}
```
* Left justification can be offset.
* This make indented code a lot prettier.
* Lines up with `|` symbol.
---
### HEREDOC Substitution
```puppet
$motd = @("EOF")
This machine $::fqdn is really the
best machine.
| EOF
```
* Variables can be substituted.
* Quotes around the `EOF`.
---
### File Validation
```puppet
file{'/etc/sshd/sshd_config':
content => epp('ssh/sshd_config.epp'),
validate_cmd => '/usr/sbin/sshd -t %'
}
```
```
/usr/sbin/sshd -t /opt/pl/cache/sshd_config
```
* Executes a command to check validity of file
* Non valid file:
* actual destination not replaced.
* resource fail.
* no service restart.
---
### Resources
* [Puppet EPP Reference Guide](https://puppet.com/docs/puppet/5.5/lang_template_epp.html)
* [Puppet HEREDOC Guide](https://puppet.com/docs/puppet/5.4/lang_data_string.html#heredocs)
* These slides:
* https://codimd.web.cern.ch/p/S1pIkDtaX#/
---
### Conclusions of EPP
* There are no downsides to using EPP templates.
* Maybe lack of syntax highlighting in vim, gitlab, ...
* The type validation is the most useful feature:
* I broke a service yesterday with 12GB vs 12G.
* After converting to EPP that will never happen again.
{"title":"Puppet Embedded Templates(EPP)","tags":"puppet epp erb templates","progress":true,"slideOptions":{"center":false,"theme":"moon","transition":"fade"}}