Red Green Repeat Adventures of a Spec Driven Junkie

Experiments in Puppet

I am continuing from last post and trying out another vagrant provisioner: puppet.


Is a tool that allows state to be automatically maintained on computers without manually checking state.

There are different parts to Puppet: puppet open source (i.e. for individual and/or smaller scale), puppet enterprise (i.e. for whole sites), and mcollective for full orchestration of systems with deploy, healing, and a whole bunch of other stuff..

Puppet Apply

Stand alone puppet can run on any system that has puppet installed. Normally, puppet needs to connect to a puppet master for its state files, known as manifests. Puppet apply allows executing manifests without puppet master (kind of like a self-hosted master.) This is what I focused on.

Puppet File Paths

Vagrant’s puppet provisioner will use specified puppet files located in the configured manifests directory of Vagrantfile. This is how I organized my files:

(vagrant directory)
  |- Vagrantfile
  |- manifests/
     |- your_manifest.pp

emacs-shapshot Puppet Style

Let’s take advantage of puppet’s exec mode to execute the same thing as we had last time to setup the latest emacs-snapshot:

The Vagrantfile:

Vagrant.configure("2") do |config| = 'hashicorp/precise64'

  config.vm.provision 'puppet' do |puppet|
    puppet.manifests_path = 'manifests'
    puppet.manifest_file  = 'emacs_1.pp'

The emacs_1.pp file contents:

exec { 'apt-add-repository':
  command => '/usr/bin/apt-get -y install python-software-properties && \
              /usr/bin/apt-add-repository -y ppa:ubuntu-elisp &&		\
              /usr/bin/apt-get update &&								\
              /usr/bin/apt-get install -y emacs-snapshot'

I am expecting this to work because it is basically exactly what is in the shell provisioner for last time.

Command Mode Result:

==> default: Running provisioner: puppet...
==> default: Running Puppet with emacs_1.pp...
==> default: stdin: is not a tty
==> default: warning: Could not retrieve fact fqdn
==> default: err: /Stage[main]//Exec[install emacs snapshot]/returns: change from notrun to 0 failed: /usr/bin/apt-get -y install python-software-properties && \
==> default:              /usr/bin/apt-add-repository -y ppa:ubuntu-elisp && \
==> default:              /usr/bin/apt-get update &&
==> default:              /usr/bin/apt-get -y install emacs-snapshot returned 100 instead of one of [0] at /tmp/vagrant-puppet/manifests-a11d1078b1b1f2e3bdea27312f6ba513/emacs_1.pp:6
==> default: notice: Finished catalog run in 0.69 seconds
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.


These were the same commands the shell file. Why did it fail??

Bootstrapping Puppet

Hmm. The documentation says:

The Vagrant Puppet provisioner allows you to provision the guest using Puppet, specifically by calling puppet apply, without a Puppet Master.

Guess I will include puppet in the Vagrantfile, the easiest way to do that is have it in… the shell provisioner:

  config.vm.provision "shell", :inline => <<-SHELL
    apt-get update
    apt-get install -y puppet


Trying Again

==> default: ldconfig deferred processing now taking place
==> default: Running provisioner: puppet...
==> default: Running Puppet with emacs_1.pp...
==> default: warning: Could not retrieve fact fqdn
==> default: notice: /Stage[main]//Exec[install emacs snapshot]/returns: executed successfully
==> default: notice: Finished catalog run in 141.76 seconds
bash-3.2$ vagrant ssh
Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic x86_64)

 * Documentation:
New release '14.04.5 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Welcome to your Vagrant-built virtual machine.
Last login: Fri Sep 14 06:23:18 2012 from
vagrant@precise64:~$ emacs --version
GNU Emacs
Copyright (C) 2016 Free Software Foundation, Inc.
You may redistribute copies of GNU Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.

Yes, success! We have the same result as in the shell provisioner.

More Puppet-like

So, vagrant will provision a new machine with emacs-snapshot using puppet instead of shell. Another way of provisioning with puppet, that is more puppet-like, is the following:

package { 'python-software-properties':
  ensure => 'installed'
} ->
exec { 'add repo':
  command => 'apt-add-repository -y ppa:ubuntu-elisp',
  path => '/usr/bin/'
} ->
exec { 'update':
  command => 'apt-get update',
  path => '/usr/bin/'
} ->
package { 'emacs-snapshot':
  ensure => 'latest'

Let’s see how each part works.


package this is telling Puppet what package to ensure is ‘installed’. Another option is to ensure latest.
-> chains commands together, so they execute in a sequential order.
exec is a direct command method used earlier.
  - command is the command
  - path is the directory the executable is in

So, this is almost like the shell, but it’s a little bit wrapped up in more puppet like syntax.


One big reason why my script so far is, I couldn’t figure out how to get good support of apt, advanced package tool

Puppet works on any UNIX system or even Windows, but from the Puppet Cookbook site, it seems rpm based distros (like RedHat or Fedora) have built-in support in puppet

apt based systems need puppet’s apt module to get better support for apt package management (i.e. adding repository, etc.) I haven’t figured out how that works yet.

Puppet Tips

Some things I found useful in developing this article:

Puppet in debug & verbose mode

Puppet can be too magical, especially at the beginning. So, configuring puppet to be more verbose and also run in debug mode instead, set puppet to be in debug mode and verbos with it’s logging:

  config.vm.provision 'puppet' do |puppet|
    puppet.options = "--verbose --debug"

fqdn Error?!

Initially I encountered an error with the provisioner in the log:

==> default: Error: Could not retrieve fact fqdn

The solution is add:

config.vm.hostname = ""  # resolves error: ==> default: Warning: Could not retrieve fact fqdn


Use echo!

An easy way to tell if puppet is executing part of the command: echo!

exec { 'done install':
  command => 'echo "--------------------------------------------------------------------------------",,
  path => '/bin/'


I have shown how to translate the provisioning shell script from my last post to puppet. I feel I have only scratched the surface of puppet’s capabilities judging how it fits with other projects, puppet master, mcollective.

I will continue exploring puppet as it can be a way to setup systems that are not just managed by vagrant.