38

I'm using puppet to provision a vagrant (ubuntu based) virtual machine. In my script I need to:

sudo apt-get build-dep python-lxml

I know I can install the apt puppet module so I can use:

apt::builddep { 'python-lxml': }

But I can't find any reference about installing a module from the script and how to include/require it. Seems to me that the puppet docs refer only to installing from the command line puppet tool

I also tried doing something like:

define build_dep($pkgname){
    exec {
    "builddepend_$pkgname":
    commmand => "sudo apt-get build-dep $pkgname";
    }
}
build_dep{
    "python-imaging":
    pkgname => "python-imaging";

    "python-lxml":
    pkgname => "python-lxml";
}

But puppet exited with an error on this. And also:

exec{"install apt module":

    command => "puppet module install puppetlabs/apt"
}

class { 'apt':
        require => Exec["install apt module"]}
include apt

apt::builddep { 'python-imaging':
 }

but got could not find declared class apt at..

any ideas? directions? I know I'm missing something obvious but can't figure this out.

EDIT: If I pre-install (with puppet module install from the commandline) the apt:builddep works fine. But I need puppet to handle the module downloading and installation. Some of the other work arounds also work for the basic use case but won't answer my main question.

ayr-ton
  • 353
  • 1
  • 4
  • 13
alonisser
  • 11,542
  • 21
  • 85
  • 139

8 Answers8

66

I ran into this problem as well. The trick is to download the modules using a vagrant shell command before the puppet provisioner runs.

config.vm.provision :shell do |shell|
  shell.inline = "mkdir -p /etc/puppet/modules;
                  puppet module install puppetlabs/nodejs;
                  puppet module install puppetlabs/apache"
end

config.vm.provision :puppet do |puppet|
  puppet.manifests_path = "puppet/manifests"
  puppet.manifest_file = "site.pp"
end

Order is important here, and since the puppet provisioner hasn't run the folder /etc/puppet/modules does not exist yet.

The reason I decided, like alonisser, to install the modules using the puppet module tool instead of using a module folder with the vagrant puppet provisioner was because I didn't want to have to download all of the dependencies of the modules I was going to use and store all of those modules in my source control. Running these two commands results in 5 dependencies that would otherwise sit in my git repository taking up space.

brain_bacon
  • 1,120
  • 11
  • 16
  • Accepted the answer, since it seems to work (partly) and It similiar to the road I took. trying to fine tune puppet executation order has bitten me one time too many . I actually went to using ```git submodule update --init``` to handle (predefind) module dependencies. and run it pre-puppet\vagrant – alonisser Jul 25 '13 at 17:57
  • 13
    It works till you run the provisioning step again on the same virtual machine... then it fails with: "Module whatever is already installed". – Igor Popov Nov 30 '13 at 13:10
  • Maybe if you use the --force option like this puppet module install --force puppetlabs/apache... Should not failed, but it will reinstall the module. As stated in the doc : "Use the --force option to forcibly re-install an existing module.". See : http://docs.puppetlabs.com/puppet/2.7/reference/modules_installing.html – Tony Dec 30 '13 at 12:31
  • 1
    --force will not install the dependent modules though – techarch Jan 02 '14 at 02:30
  • 3
    I did get around multiple provisioning by wrapping the shell command with true, e.g. `(puppet module install puppetlabs/apache; true)` but this could have unintended consequences if the module fails to install for other reasons. – brain_bacon Feb 10 '14 at 05:45
  • Well, at least warnings are still output to the shell if the module install fails - for now that is acceptable to me. – demaniak Mar 08 '14 at 11:07
  • 7
    to avoid the error on a second provision or the necessity to use `--force` you can execute something like `(puppet module list |grep puppetlabs-nodejs ) || (mkdir -p /etc/puppet/modules; puppet module install puppetlabs/nodejs)` (no carriage returns in comments, sorry) – Iacopo Apr 06 '14 at 11:01
  • Puppet code in vagrant lives on the host not the VM. Trying to install on the VM is the root of the problem here. Just keep the forge modules along with any custom puppet code. Use puppet on the host, use puppet on the guest and copy the modules via /vagrant, or just manually download on the host (ensuring you also download any dependencies). – Sean Burlington Nov 03 '14 at 18:46
  • `puppet module install puppetlabs-stdlib 2> /dev/null || /bin/true` worked for me to get around the error messages and provisioning failures – brownmike Sep 28 '15 at 16:56
  • @SeanBurlington I wish I could take this advice but oh my god installing puppet is scary. Walking through "download open source puppet" makes me feel like I'm signing away my first-born, and then I have to install puppet server, and oh but first I have to enable package updates, and dear heaven just make it stop please. – John Clements Apr 23 '18 at 03:21
14

Here's what I did to make the puppet module install command run at most once:

$script = <<EOF
mkdir -p /etc/puppet/modules
(puppet module list | grep puppetlabs-mysql) ||
   puppet module install -v 2.1.0 puppetlabs/mysql
EOF

Vagrant::Config.run do |config|
   config.vm.provision :shell, :inline => $script
wtanaka.com
  • 419
  • 4
  • 11
8

I use an approach similar to @brain_bacon - the additional complication I had is that in addition of pre-packaged modules like puppetlabs/nodejs I needed local modules relative to my Vagrantfile. I didn't want to check in the pre-packaged modules as part of my repository, nor use git submodules because of the problems pointed out by @Igor Popov.

Finally the solution I chose was to use a shell script to download the modules, but forcing their path into the shared directory between the Vagrant VM and the host, and using .gitignore to avoid that path being under source control.

To be clear, with this tree:

jcmendez$ tree
.
├── README.md
├── Vagrantfile
├── files
├── puppet
│   ├── manifests
│   │   └── init.pp
│   └── modules
│       ├── misc
│       │   └── manifests
│       │       └── init.pp
│       ├── mysql
   ...
│       └── wordpress
│           ├── files
│           │   ├── wordpress-db.sql
│           │   ├── wp-config.php
│           │   └── wp-tests-config.php
│           └── manifests
│               └── init.pp
└── wordpress

On .gitignore I added

  /puppet/modules/mysql

On Vagrantfile

  config.vm.provision :shell do |shell|
    shell.inline = "puppet module install puppetlabs/mysql --force --modulepath '/vagrant/puppet/modules'"
  end

  config.vm.provision :puppet do |puppet|
    puppet.manifests_path = 'puppet/manifests'
    puppet.module_path = 'puppet/modules'
    puppet.manifest_file  = "init.pp"
    puppet.options="--verbose --debug"
  end
Juan Carlos Méndez
  • 1,053
  • 10
  • 20
  • 2
    I followed your way (except that I'm attempting puppetlab-apache install): inline shell install and got "Unknown function validate_bool at /tmp/vagrant". Which appears when one of the dependencies is not installed. How did you go about installing apache? – Maciej Gurban Jan 14 '14 at 15:16
  • @DiH - you are right, make sure to read through all the dependencies for each of the packages because the error messages that come out when the dependencies are not met are a bit cryptic. I didn't install apache2, though... we had nginx on our setup. – Juan Carlos Méndez Jan 15 '14 at 15:38
4

You can install puppet modules on the host machine within your vagrantdir:

puppet module --modulepath=./puppet/modules/ install puppetlabs/mysql

Vagrant takes care to mount this directory to the right place. So there is no need to run scripts other than puppet itself on the node.

krock
  • 28,904
  • 13
  • 79
  • 85
sebastianwagner
  • 399
  • 2
  • 9
  • Not really. Simple copying modules may cause you trouble: [Unknown function pick at /tmp/vagrant-puppet/modules-0/postgresql/manifests/params.pp:50](http://www.danielsullivan.me/blog/unknown-function-pick-at-tmpvagrant-puppetmodules-0postgresqlmanifestsparams-pp50/). I am also facing the same problem and it's somewhat non-deterministic. Sometimes happens, sometimes not. But the link quoted states 'just copying the modules' IS its root cause. – sumid Sep 12 '14 at 08:22
2

Inspired by wtanaka.com's answer, I implemented a solution like below which I think is much more readable.

1) create a new ruby file called 'puppet_deps.rb' under the same folder as Vagrantfile with code like below:

def install_dep(name, version, install_dir = nil)
    install_dir ||= '/etc/puppet/modules'
    "mkdir -p #{install_dir} && (puppet module list | grep #{name}) || puppet module install -v #{version} #{name}"
end

2) In your Vagrantfile, you can load this ruby file and use it to specify the puppet dependency:

# on top of your Vagrantfile
require './puppet_deps'
...
...
# in your vm definition, use a shell provisioning this:
config.vm.provision :shell, :inline => install_dep('puppetlabs-firewall', '1.1.3')
config.vm.provision :shell, :inline => install_dep('puppetlabs-stdlib', '4.3.2')
nybon
  • 8,894
  • 9
  • 59
  • 67
2

@brain_bacon's strategy worked almost perfectly for me, with one small caveat - if the module already exists, then the provisioning script fails, halting the provisioning process. The following adjustment fixed this:

config.vm.provision :shell, :run => "always" do |shell|
  shell.inline = %{
    mkdir -p /etc/puppet/modules;
    function install_module {
      folder=`echo $1 | sed s/.*-//`
      if [ ! -d /etc/puppet/modules/$folder ]; then
        puppet module install $1
      fi
    }
    install_module stdlib
    install_module apt
    install_module ruby
  }
end
mistertim
  • 5,123
  • 4
  • 20
  • 27
1

You can create a directory for modules and add the apt module that you downloaded from the forge. So it will be modules/apt/*. Then you can specify this module directory in Vagrantfile (module_path is relative to Vagrantfile):

Vagrant.configure("2") do |config|
  config.vm.provision :puppet do |puppet|
    puppet.module_path = "modules"
  end
end

For more information see the documentation.

Gergo Erdosi
  • 40,904
  • 21
  • 118
  • 94
  • 2
    This doesn't answer the question how to download and install the module automatically from the script, without predownloading etc.. – alonisser Jul 07 '13 at 06:49
  • 1
    Why would you want to do that? Puppet scripts are usually included in the vagrant setup. They will not even be available on your VM later (at least not with default puppet configuration). – Michael Härtl Jul 09 '13 at 06:52
1

Using answers from this question I created this script:

#!/bin/bash

function install_module {
   IFS=':' read module version <<< "$1"
   if (puppet module list | grep $module ) >/dev/null; then
    echo "Module $module is already installed"
   else
        if [ -z "$version" ]; then
            puppet module install $module
        else 
        puppet module install -v $version $module
    fi
    fi  
}

if dpkg --compare-versions `puppet --version` "lt" 3.8.7; then
    sudo apt-get remove --purge -y puppet
    sudo apt-get -y autoremove
fi

if which puppet >/dev/null; then
    echo "Puppet is already installed"
else
    echo "Installing puppet"
    wget https://apt.puppetlabs.com/puppetlabs-release-trusty.deb
    sudo dpkg -i puppetlabs-release-trusty.deb
    sudo apt-get update
    sudo apt-get install -y puppet=3.8.7*
    mkdir -p /etc/puppet/modules
fi

for var in "$@" 
do
    install_module "$var"
done

Then you can use it from your Vagrantfile like this:

puppet_modules = [
    'puppetlabs-stdlib:4.16.0',
    'puppetlabs-apt:2.3.0',
    'puppet-nodejs:2.3.0'
    ]

config.vm.provision "shell", path: "puppet/scripts/puppet.sh", args: puppet_modules.join(" ")

It removes puppet from vm if version is lower than 3.8.7, then installs puppet 3.8.7, and then installs all the modules.

It would probably work only on debian/ubuntu boxes.

Krzysztof Atłasik
  • 21,985
  • 6
  • 54
  • 76