Red Green Repeat Adventures of a Spec Driven Junkie

Puppet Manifests Mix

So far, I wrote how I made these manifests:

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:

  1. Inline so all the dependencies are in a single file, better for short scripts.
  2. Source when a file is long, like over 10 lines so it does not clutter the puppet manifest file.
  3. 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.