Puppet Manifests Mix
So far, I wrote how I made these manifests:
- emacs installation
- configure two factor authentication
- configure fail2ban
- setup email login notifications
Let’s add a manifest to have slack notifications on login (from my previous article) so there is an email and a slack notification whenever there is a login.
Add Login Slack Notifications
The steps to have the same to have slack notifications on login:
- configure slack for webhook
- configure pam.d to execute a slack script
- include curl package
- include slack script
Configure Slack
Follow directions in my previous article to setup slack.
note: this will require administrative privileges on the slack.
The most important thing to have is the webhook address for the slack script. The webhook address will look like a regular web URL and be like:
http://slack.generated.webhook/services/xx...x/yy...y/zz....z
Configure pam.d
This is exactly the same as email notification manifest, but change the pam.d configuration to use the slack script called: slack_notification.sh:
file_line { 'pamd_config':
path => '/etc/pam.d/sshd',
line => 'session optional pam_exec.so /usr/local/bin/slack_notification.sh'
}
Include curl
The slack script uses curl to activate the slack webhook, make sure puppet installs it:
package { 'curl':
ensure => 'installed'
}
Include slack script
Similar to the email script, puppet creates the script. This time, the script
will come from another file instead of in the file. The script file is in the
same location as the Vagrantfile
location.
file { 'slack_notification.sh':
ensure => 'file',
path => '/usr/local/bin/slack_notification.sh',
mode => 'ugo+x',
source => '/vagrant/slack_notification.sh'
}
note: the source location references /vagrant folder as puppet-apply is running within the host computer, which is also shared between guest and host.
Inlining Shell Script
This time, I use the source
option to include the script. Inlining raised
these errors in puppet apply:
==> default: Warning: Unrecognized escape sequence '\`' at /tmp/vagrant-puppet/environments/production/manifests/slack_login_notificati
ons.pp:49:2
I suspect there was too much
Inlining vs. Sourcing
These are some rules I use to decide whether to inline or source a script:
- Inline so all the dependencies are in a single file, better for short scripts.
- Source when a file is long, like over 10 lines so it does not clutter the puppet manifest file.
- Source when there are escapes in escapes…
\\\\\\
.
The shell script details: here
Run Everything
Now that script is setup, let’s run everything together:
$ vagrant provision
...
==> default: Error: Evaluation Error: Error while evaluating a Resource Statement, Duplicate declaration: File_line[pamd config] is alr
eady declared in file /tmp/vagrant-puppet/environments/production/manifests/email_login_notifications.pp:28; cannot redeclare at /tmp/v
agrant-puppet/environments/production/manifests/slack_login_notifications.pp:52 at /tmp/vagrant-puppet/environments/production/manifest
s/slack_login_notifications.pp:52:1 on node vagrant.example.com
The SSH command responded with a non-zero exit status. Vagrant
assumes that this means the command failed. The output for this command
should be in the log above. Please read the output to determine what
went wrong.
A double declaration? From this article, it looks like having each manifest as their own class would solve the problem.
Puppet Classes
To create a class in puppet, just wrap everything in a class declaration.
So, before the manifest file contents are:
class { 'apt': }
apt::ppa{ 'ppa:ubuntu-elisp': }
package { 'emacs-snapshot':
ensure => 'latest',
require => Apt::Ppa['ppa:ubuntu-elisp']
}
Now, to make it into a class, the same file is now:
class 'emacs' {
class { 'apt': }
apt::ppa{ 'ppa:ubuntu-elisp': }
package { 'emacs-snapshot':
ensure => 'latest',
require => Apt::Ppa['ppa:ubuntu-elisp']
}
}
Run with Puppet Classes
$ vagrant destroy
$ vagrant up
==> default: (Reading database ... 61863 files and directories currently installed.)
...
==> default: Running Puppet with environment production...
==> default: Notice: Compiled catalog for vagrant.example.com in environment production in 0.04 seconds
==> default: Notice: Applied catalog in 0.01 seconds
Good, no errors. Let’s check the result:
$ vagrant ssh
bash-3.2$ vagrant ssh
Welcome to Ubuntu 14.04.5 LTS (GNU/Linux 3.13.0-128-generic x86_64)
...
vagrant@vagrant:~$ ll /usr/local/bin/
total 8
drwxr-xr-x 2 root root 4096 Aug 18 20:40 ./
drwxr-xr-x 10 root root 4096 Aug 18 20:40 ../
vagrant@vagrant:~$
Nothing there?? huh?? Puppet ran without errors. What’s going on?
Include Puppet Class
It looks like having each manifest as it’s own class is fine to namespace
declarations from each other, but there need to have include <class name>
in
the file.
This is kind of bootstrapping the file together. Define a class and then use it. This allows resue of classes and modules in files outside of the original definition. A great way to keep things DRY… (Don’t Repeat Yourself).
class 'emacs' {
class { 'apt': }
apt::ppa{ 'ppa:ubuntu-elisp': }
package { 'emacs-snapshot':
ensure => 'latest',
require => Apt::Ppa['ppa:ubuntu-elisp']
}
}
include emacs
This style would be useful for larger or more complex groups of manifests. This instance, the goal is to have independent manifests for each system feature, so each file will essentially include itself.
From the Top Again
Let’s run things again:
$ vagrant provision
...
==> default: Running provisioner: puppet...
==> default: Running Puppet with environment production...
==> default: Error: Evaluation Error: Error while evaluating a Resource Statement, Duplicate declaration: File_line[pamd config] is already declared in file /tmp/vagrant-puppet/environments/production/manifests/email_login_notifications.pp:28; cannot redeclare at /tmp/vagrant-puppet/environments/production/manifests/slack_login_notifications.pp:15 at /tmp/vagrant-puppet/environments/production/manifests/slack_login_notifications.pp:15:3 on node vagrant.example.com
The SSH command responded with a non-zero exit status. Vagrant
assumes that this means the command failed. The output for this command
should be in the log above. Please read the output to determine what
went wrong.
What’s the matter now?! Duplicate declaration: File_line[pamd config]
?? Isn’t
everything already namespaced within their own classes?
Unique Class Variables
Well, it looks like there needs to be a unique name for instantiated classes,
like file_line
. Since the email notifications manifest and the slack
notifications manifests both adjust the /etc/pam.d/sshd file and the method used
is essentially the same name: File_line['pamd config']
.
This is similar to the error before when everything was not wrapped in a class: individual class declarations to be unique.
So, let’s make all the class declarations unique, even within the class.
An easy way to do that is to prefix a ‘class’ to the class with naming
convention, class::file
For example, if this was the class:
class 'new class' {
file_line[pam config] {
}
}
Following the convention, the class would becomes:
class 'new class' {
file_line['new class::pam config'] {
}
}
This way, by namespacing additional declarations in the class with the higher level class, there is no name collision. One doesn’t worry about how to make every class/subclass unique for all manifests packaged together.
All files for this article are available at: https://github.com/a-leung/puppet_manifests
Conclusion
Adding the slack notification manifest was not as straight forward as writing a manifest including the end state of the system. If having lots of manifests that may touch the same file, having each manifest in its own class, including that class in the same file, and namespacing all classes so they can be unique is essential to having multiple manifests.