Path to Puppet Magic
In my last
post,
time explained how to get puppet to work with vagrant. The puppet script worked,
but honestly, I felt the solution was less than satisfying. I took advantage of
puppet’s exec
and ->
commands to basically create an enhanced shell.
Where’s the puppet magic? Where the file provisioning would be a simple file like:
class { 'apt': }
apt::ppa{ 'ppa:ubuntu-elisp': }
package { 'emacs-snapshot':
ensure => 'latest',
require => Apt::Ppa['ppa:ubuntu-elisp']
}
Well, I dug deep and found the answer.
I will share the end result first and document errors I encountered to help anyone else struggling with similar errors. These errors and their solutions were not really documented anywhere, so I want others to save a little pain and get started with puppet smoothly.
Simplified Manifest
As I noted earlier, puppet directly supports yum based package management, so apt based package management system have to jump through some hoops to get it working.
Well, there’s a puppet module for handling apt, like adding a ppa repository directly like:
manifest/default.pp:
apt::ppa{ 'ppa:ubuntu-elisp': }
Isn’t that a lot nicer than:
manifest/default.pp:
package { 'python-software-properties':
ensure => 'installed'
} ->
exec { 'add repo':
command => 'apt-add-repository -y ppa:ubuntu-elisp',
path => '/usr/bin/'
}
The above is the puppet magic I wanted to happen. I didn’t want to script out every single step like the latter.
Puppet Magic: emacs-snapshot
This is the manifest I want to have:
manifests/default.pp:
class { 'apt': }
apt::ppa{ 'ppa:ubuntu-elisp': }
package { 'emacs-snapshot':
ensure => 'latest',
require => Apt::Ppa['ppa:ubuntu-elisp']
}
Vagrantfile:
Vagrant.configure("2") do |config|
config.vm.box = 'ubuntu/trusty64'
config.vm.hostname = "vagrant.example.com"
config.vm.provision 'puppet'
end
I would expect things to just work… but the output of $ vagrant
provision
is:
==> default: Running provisioner: puppet...
==> default: Running Puppet with default.pp...
==> default: Error: Puppet::Parser::AST::Resource failed with error ArgumentError: Could not find declared class apt at /tmp/vagrant-puppet/manifests-a11d1078b1b1f2e3bdea27312f6ba513/default.pp:1 on node vagrant.example.com
==> default: Wrapped exception:
==> default: Could not find declared class apt
==> default: Error: Puppet::Parser::AST::Resource failed with error ArgumentError: Could not find declared class apt at /tmp/vagrant-puppet/manifests-a11d1078b1b1f2e3bdea27312f6ba513/default.pp:1 on node vagrant.example.com
Which is kind of expected since there is no loading of the puppet module apt anywhere…
Bootstrapping Puppet Modules
Well, the crazy part is: puppet uses puppet to get puppet modules, so there is a need a working version of puppet to load modules. Sometimes, the host system doesn’t have puppet setup (i.e. macOS…)
One way I have solved this: load the modules from the guest machine and copy the result to the shared vagrant folder.
In vagrant machine, run:
$ puppet module install puppetlabs-apt
$ cp -r .puppet/modules /vagrant/
Note: In puppet5, the modules loads from directory
/opt/puppetlabs/puppet/modules, so the cp command would be: $ cp -r
/opt/puppetlabs/puppet/modules /vagrant
Let’s run with the puppet provisioner with the following files:
Vagrantfile:
Vagrant.configure("2") do |config|
config.vm.box = 'ubuntu/trusty64'
config.vm.hostname = "vagrant.example.com"
config.vm.provision 'puppet' do |puppet|
puppet.module_path = 'modules'
end
end
manifests/default.pp:
class { 'apt': }
apt::ppa{ 'ppa:ubuntu-elisp': }
package { 'emacs-snapshot':
ensure => 'latest',
require => Apt::Ppa['ppa:ubuntu-elisp']
}
provisioning again, the result is:
==> default: Running provisioner: puppet...
==> default: Running Puppet with default.pp...
==> default: Error: Syntax error at 'Hash'; expected ')' at /tmp/vagrant-puppet/modules-43a2450d049859e41f5e034c104286f8/apt/manifests/init.pp:6 on node vagrant.example.com
==> default: Error: Syntax error at 'Hash'; expected ')' at /tmp/vagrant-puppet/modules-43a2450d049859e41f5e034c104286f8/apt/manifests/init.pp:6 on node vagrant.example.com
There is something really wrong. I didn’t write that code. It is from a module puppet labs wrote, the authors of the puppet! So, there’s something REALLY weird.
Default Puppet Versions
When I look at the installed versions of puppet of Ubuntu 14.04 - trusty, the result is:
$ puppet --version
=> 3.4.3
In, Ubuntu 12.04 - precise, the version is 2.7.11!
As of this writing, the current version of puppet: 5!
The default installed version of puppet is a long way off! No wonder there are errors regarding library code! (Why didn’t the module error out on installation…?)
Upgrading to Puppet5
I wish upgrading puppet was easy, but version 4 had big changes in how puppet operates with the system. This is the method I used to upgrade to the latest version of puppet, following puppet platform install method
- use apt-get purge puppet to remove installed version of puppet
- install the associated .deb package for new puppet repositories
- update repository listings
- install puppet-agent
Note: puppet is not installed, because this guide focuses with running puppet agent on a local machine, not to connect with a master puppet node.
Note: puppet5 needs a newer glibc than the one install on Ubuntu 12.04 - precise.
Vagrantfile:
Vagrant.configure("2") do |config|
config.vm.box = 'ubuntu/trusty64'
config.vm.hostname = "vagrant.example.com"
# http://www.terrarum.net/blog/masterless-puppet-with-vagrant.html
config.vm.provision 'shell', :inline => <<-SHELL
# install puppet5...
apt-get purge -y puppet # remove puppet3
wget http://apt.puppetlabs.com/puppet5-release-trusty.deb
dpkg -i puppet5-release-trusty.deb
apt-get update
apt-get install -y puppet-agent # install puppet-agent
SHELL
config.vm.provision 'puppet' do |puppet|
puppet.module_path = 'modules'
end
end
Now, let’s provision to have puppet5 installed and have it provision using the new manifest and modules…
$ vagrant provision
error now:
==> default: Processing triggers for ureadahead (0.100.0-16) ...
==> default: Running provisioner: puppet...
==> default: Running Puppet with default.pp...
==> default: Error: Could not parse application options: invalid option: --manifestdir
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 the?? I did not set manifestdir
anywhere in the Vagrantfile…
manifestdir Error
Well, exploring further, it looks like with puppet4, the manifestdir marked for deprecation, and removed in puppet5.
Vagrant by default passes manifestdir
to the guest’s puppet provisioner, which
in this case is puppet5. Puppet5 throws an error since it does not take
manifestdir
as an option anymore.
Looking at the vagrant github discussion on this issue, it looks like the solution is to set environment paths in the puppet provisioner configuration:
puppet.environment = 'production'
puppet.environment_path = 'environments'
Magic!
This is the setup for vagrant to get puppet to magically provision a system to have the latest emacs installed.
Vagrantfile:
Vagrant.configure("2") do |config|
config.vm.box = 'ubuntu/trusty64'
config.vm.hostname = "vagrant.example.com"
# http://www.terrarum.net/blog/masterless-puppet-with-vagrant.html
config.vm.provision 'shell', :inline => <<-SHELL
# install puppet5...
apt-get purge -y puppet # remove puppet3
wget http://apt.puppetlabs.com/puppet5-release-trusty.deb
dpkg -i puppet5-release-trusty.deb
apt-get update
apt-get install -y puppet-agent # install puppet-agent
SHELL
# https://github.com/mitchellh/vagrant/issues/3740#issuecomment-92106636
config.vm.provision 'puppet' do |puppet|
puppet.environment_path = 'environments'
puppet.environment = 'production'
puppet.module_path = 'modules'
end
end
environments/production/manifests/default.pp:
class { 'apt': }
apt::ppa{ 'ppa:ubuntu-elisp': }
package { 'emacs-snapshot':
ensure => 'latest',
require => Apt::Ppa['ppa:ubuntu-elisp']
}
and run the provisioner:
$ vagrant provision
and the result:
==> default: Notice: /Stage[main]/Apt/Apt::Setting[conf-update-stamp]/File[/etc/apt/apt.conf.d/15update-stamp]/content: content changed '{md5}b9de0ac9e2c9854b1bb213e362dc4e41' to '{md5}0962d70c4ec78bbfa6f3544ae0c41974'
==> default: Notice: /Stage[main]/Main/Apt::Ppa[ppa:ubuntu-elisp]/Exec[add-apt-repository-ppa:ubuntu-elisp]/returns: executed successfully
==> default: Notice: /Stage[main]/Apt::Update/Exec[apt_update]: Triggered 'refresh' from 1 event
==> default: Notice: /Stage[main]/Main/Apt::Ppa[ppa:ubuntu-elisp]/File[/etc/apt/sources.list.d/ppa:ubuntu-elisp.list]/ensure: created
==> default: Notice: /Stage[main]/Main/Package[emacs-snapshot]/ensure: created
==> default: Notice: Applied catalog in 54.07 seconds
going in and checking:
vagrant@vagrant:~$ emacs --version
GNU Emacs 26.0.50.2
Copyright (C) 2016 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
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.
Success!
Conclusion
There is a lot of magic with puppet, and when things don’t work out, debugging it can take a lot of work. It’s even harder when there’s another moving part, here it is vagrant.
I’ve documented the solution that works for me at the time of writing. Including the errors here to help others trying to understand the problem better.
If there are any changes needed, please reach out to me directly and I’ll do my best to help (and update this article.)
Next time, I will go over the whole setup again and include additional manifests to make a system more secure.