Hands-On Exercise¶
This exercise ties the course together by building a real component module —
a postfix module — using jig, EPP templates, Hiera data, and the testing
tools. It assumes you have a control repo set up as described in Using
OpenVox.
Prerequisites
A Linux machine with OpenVox installed, jig, git, and a control repo.
Commands run as your normal user except where a # prompt indicates root.
Substitute your real home directory for /home/youruser.
Create the component module¶
Try it yourself
From your home directory, scaffold a module and its classes:
Add the code¶
Edit manifests/init.pp to declare and order the subclasses:
class postfix {
contain postfix::install
contain postfix::config
contain postfix::service
Class['postfix::install']
-> Class['postfix::config']
~> Class['postfix::service']
}
Edit manifests/install.pp to declare the package:
Edit manifests/service.pp to declare the service:
Edit manifests/config.pp to configure postfix from data, rendering each file
from an EPP template:
class postfix::config (
Hash $main_config,
Hash $master_config,
Hash $aliases,
) {
file {
default:
owner => 'root',
group => 'root',
mode => '0644',
;
'/etc/postfix/main.cf':
content => epp('postfix/main.cf.epp', { 'config' => $main_config }),
;
'/etc/postfix/master.cf':
content => epp('postfix/master.cf.epp', { 'config' => $master_config }),
;
'/etc/aliases':
content => epp('postfix/aliases.epp', { 'aliases' => $aliases }),
;
}
exec { '/usr/bin/newaliases':
refreshonly => true,
subscribe => File['/etc/aliases'],
}
}
Warning
This config class contains a deliberate flaw: because postfix::config is
ordered with ~> before postfix::service, a change to /etc/aliases (or
any triggered newaliases) will refresh the whole config class and restart
the service. Watch for this when you test idempotency.
Add the templates¶
Create templates/main.cf.epp:
<% | Hash $config | -%>
# Managed by OpenVox
<% $config.each |$key, $value| { -%>
<%= $key %> = <%= $value %>
<% } -%>
Create templates/master.cf.epp:
<% | Hash $config | -%>
# Managed by OpenVox
<% $config.each |$key, $value| { -%>
<%= ([$key] + $value).join("\t") %>
<% } -%>
Create templates/aliases.epp:
<% | Hash $aliases | -%>
# Managed by OpenVox
<% $aliases.each |$key, $value| { -%>
<%= $key %>: <%= $value %>
<% } -%>
Add the data¶
Edit data/common.yaml to populate the template defaults. First, a
lookup_options entry so the config hashes deep-merge:
The main.cf settings:
postfix::config::main_config:
inet_interfaces: localhost
inet_protocols: all
mydestination: '$myhostname, localhost.$mydomain, localhost'
alias_maps: 'hash:/etc/aliases'
alias_database: 'hash:/etc/aliases'
debugger_command: '
PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
ddd $daemon_directory/$process_name $process_id & sleep 5'
sendmail_path: /usr/sbin/sendmail.postfix
newaliases_path: /usr/bin/newaliases.postfix
mailq_path: /usr/bin/mailq.postfix
manpage_directory: /usr/share/man
sample_directory: /usr/share/doc/postfix-2.10.1/samples
readme_directory: /usr/share/doc/postfix-2.10.1/README_FILES
The master.cf service definitions:
postfix::config::master_config:
smtp: ['inet','n','-','n','-','-','smtpd']
pickup: ['unix','n','-','n','60','1','pickup']
cleanup: ['unix','n','-','n','-','0','cleanup']
qmgr: ['unix','n','-','n','300','1','qmgr']
tlsmgr: ['unix','-','-','n','1000?','1','tlsmgr']
rewrite: ['unix','-','-','n','-','-','trivial-rewrite']
bounce: ['unix','-','-','n','-','0','bounce']
defer: ['unix','-','-','n','-','0','bounce']
trace: ['unix','-','-','n','-','0','bounce']
verify: ['unix','-','-','n','-','1','verify']
flush: ['unix','n','-','n','1000?','0','flush']
proxymap: ['unix','-','-','n','-','-','proxymap']
proxywrite: ['unix','-','-','n','-','1','proxymap']
smtp: ['unix','-','-','n','-','-','smtp']
relay: ['unix','-','-','n','-','-','smtp']
showq: ['unix','n','-','n','-','-','showq']
error: ['unix','-','-','n','-','-','error']
retry: ['unix','-','-','n','-','-','error']
discard: ['unix','-','-','n','-','-','discard']
local: ['unix','-','n','n','-','-','local']
virtual: ['unix','-','n','n','-','-','virtual']
lmtp: ['unix','-','-','n','-','-','lmtp']
anvil: ['unix','-','-','n','-','1','anvil']
scache: ['unix','-','-','n','-','1','scache']
Warning
This data (carried over from the original course) defines the smtp key
twice — once as the inet smtpd service and once as the unix smtp
client. Because a YAML hash can't have two entries with the same key, the
second wins and the smtp inet (smtpd) line is lost. A real postfix
module needs both, so a production design would key master.cf services by
something unique (e.g. service/type) rather than service name alone.
Consider it an exercise in spotting data-model limitations.
Test and commit¶
Try it yourself
Validate, test, and commit the module:
If jig test unit fails because of an unsupported operating system, remove
windows from the operatingsystem_support section of metadata.json.
Declare the module from your control repo¶
Try it yourself
Add the new module to your control repo's Puppetfile:
mod 'example-postfix',
:git => 'file:///home/youruser/postfix',
:branch => :control_branch,
:default_branch => 'main'
Edit data/common.yaml in your control repo to add postfix to the
classes array. Commit the changes to Puppetfile and data/common.yaml,
then deploy as root and check what would change: