A beginners guide to Vagrant and Puppet, part 3 - facts, conditionals and modules

Finishing this guide to Vagrant and Puppet, I would like to show some advanced Puppet resources. As I said before, Puppet is really powerful and extensive - I'm covering just the main concepts so you can have a good starting point for creating your Vagrant boxes. If you didn't see the previous 2 posts, I strongly recommend you to read them. Here they are: Part 1 (Vagrant basics) and Part 2 (provisioning and Puppet) .

Facts

In the previous part of this guide, we saw a simple example of a Puppet class to install Apache. We saw that we can define variables and use them in our classes / manifests.

Facts are really similar to variables, except that they are pre-defined (like the PHP superglobals, for instance). Puppet uses a tool called "Facter" to collect information about the system (in our case, about the virtual machine created by Vagrant - the Guest OS), and the facts represent this data. To get a list of all the pre-defined (or core) facts, have a look here. They include, for example: ip address, uptime, OS family, hostname, whether the machine is virtual or not, and other system-related data. You can also define custom facts, specific information that you want to gather in order to perform some action or establish a condition.

Facts are widely used in open source puppet modules, to make them more versatile. Even if you are going to develop a very specific puppet module, you shall make it the more decoupled you can, so you can reuse it out-of-the-box within other projects / servers. We're going to see a practical example of facts usage in the next topic.

Conditionals

Puppet also offers conditional structures, just like in a regular programming language. The if statement, for instance, looks like this:

if $operatingsystem == 'Ubuntu' {
  notice('Cool! I like you')
}
elsif $operatingsystem == 'Windows' {
  warning('What the hell are you doing...')
}
else {
  notice("I dont know what to think about ${operatingsystem}. Its a ${osfamily}, isnt it?")
}

Noticed the use of $operatingsystem and $osfamily facts? Add this piece of code to your default.pp (it doesn't need to go in a class) and run vagrant provision (or vagrant up, or vagrant reload) to see the result.

Vagrant will show the conditional message when the provision runs - remember that, since the VM is Ubuntu, you shall always get the first message.  The warning and notice functions have a different output color. Other output functions to check: alert, info, err .

Since you are probably familiar with conditional structures from other programming languages (if you aren't, you should, specially because the dev part of devOps is there for some reason), you can have a look at the other conditional structures available on Puppet, from their official documentation: Unless, Case,  Selectors .

Modules

The Puppet Modules give you the ability to split your Puppet config in separated, functional and reusable configurations.  From the docs: "Modules are just directories with files, arranged in a specific, predictable structure. The manifest files within a module have to obey certain naming restrictions".

You will need a folder to hold your puppet modules - lets create a "modules" folder inside our puppet folder. You have to define the modules path in your Vagrantfile, adding this line to the Puppet block:

puppet.module_path = "puppet/modules"

Puppet will look for the modules inside the puppet/modules path (relative to your project root where your Vagrantfile is).

Creating your own Modules

The modules must follow some guidelines:

  • The module must go in a directory, and the name of the directory is the name of the module.
  • The directory must contain a manifests sub-directory to hold all the .pp files
  • The main manifest must be named init.pp (inside the manifests directory), holding a single class definition - it must have the same name as the module.

So, for instance, lets turn our old default.pp into two modules: system-update and apache (to hold both classes we defined there before). The structure will look like this:

  • Project Root ( ./ )
    • puppet
      • manifests
        • default.pp
      • modules
        • apache
          • manifests
            • init.pp
        • system-update
          • manifests
            • init.pp
    • Vagrantfile
    • (...)

It may look quite verbose compared to our old default.pp file, but this is very useful in "real life" because often we need much from our servers, and defining all the configuration in a single default.pp will get quite confusing and very hard to maintain. Think of Puppet as just another programming language - the more modular, the more you can reuse it in the future, the easier to add or remove new modules. Now, let's copy the classes to the new modules init.pp files:

class apache {

  package { "apache2":
    ensure  => present,
    require => Class["system-update"],
  }

  service { "apache2":
    ensure  => "running",
    require => Package["apache2"],
  }

}
class system-update {

  exec { 'apt-get update':
    command => 'apt-get update',
  }

  $sysPackages = [ "build-essential" ]
  package { $sysPackages:
    ensure => "installed",
    require => Exec['apt-get update'],
  }
}

And our default.pp now shall look like this:

Exec { path => [ "/bin/", "/sbin/" , "/usr/bin/", "/usr/sbin/" ] }

include apache
include system-update

Run the provision again and you will get exactly the same result as before (apache installed on 192.168.33.101), except that now you have your configurations splitted into reusable modules.

More about Classes

We already saw basic classes, and we know we can use facts inside them (which is cool), but what if we want to use real parameters, just as we do in object-oriented programming languages? Yes, we can! And its pretty similar to a PHP method, with default values and everything:

class apache( $document_root = '/var/www', $port = 80)
{
   #class code
}

We learned before that we had to include our module classes; there's no place for parameters in the include, but there's another way to "call" the classes and send some parameters along. This is called resource-like declaration, because it looks very similar to other resources - except that instead of package, for instance, you use class . This is the recommended way for working with classes that depend on parameters, as explained here.

class { "apache":
  "documentroot" => "/vagrant"
}

If you want to create complex modules, you really should read the official puppet documentation for it. We are going to use third-party modules, so lets move on to the next section of the post.

Using third-party modules

As I said before, one of the greatest advantages of working with modules is that you can find open source modules for Puppet quite easily on Github. Now that you are familiar with the modules functionality, we can speed up our process by using third-party modules.

Example42 repository, for instance, has a huge collection of great Puppet modules; the puppetlabs official repository has a great collection of modules as well. Even if you plan to create your own modules, it's always a good idea to have a look at this modules and see how they work.

There are two main ways for adding the modules you want for your puppet conf: either by cloning each module from its repository and placing the contents inside the defined Puppet modules folder, either by using git submodules, which is better to keep them up to date. For our final result, I used these Puppet modules:

  • apt - to manage apt and add ppa's, since we want php 5.4 and it is not available in the default apt repositories
  • stdlib - dependency for the apt module
  • apache - apache module from example42
  • php - php module from example42
  • puppi - used by the php and apache modules, it's commonly used for deploy automation

Adding the modules as git submodules

You need to add each module separately with the command:

git submodule add [repository-url] [destination folder]

For instance, to add the apache module:

$ git submodule add https://github.com/example42/puppet-apache.git puppet/modules/apache 

The command must be run from the git root folder. The destination directory must have the same name as the main module class (in this case, apache). Now, our default.pp file will look like this:

Exec { path => [ "/bin/", "/sbin/" , "/usr/bin/", "/usr/sbin/" ] }

exec { 'apt-get update':
  command => 'apt-get update',
  timeout => 60,
  tries   => 3
}

class { 'apt':
  always_apt_update => true,
}

package { ['python-software-properties']:
  ensure  => 'installed',
  require => Exec['apt-get update'],
}

$sysPackages = [ 'build-essential', 'git', 'curl']
package { $sysPackages:
  ensure => "installed",
  require => Exec['apt-get update'],
}
class { "apache": }

apache::module { 'rewrite': }

apache::vhost { 'default':
  docroot             => '/vagrant/web',
  server_name         => false,
  priority            => '',
  template            => 'apache/virtualhost/vhost.conf.erb',
}

apt::ppa { 'ppa:ondrej/php5':
  before  => Class['php'],
}

class { 'php': }

$phpModules = [ 'imagick', 'xdebug', 'curl', 'mysql', 'cli', 'intl', 'mcrypt', 'memcache']

php::module { $phpModules: }

php::ini { 'php':
  value   => ['date.timezone = "Europe/Amsterdam"'],
  target  => 'php.ini',
  service => 'apache',
}

One last thing to notice: pay attention the line 25 - apache::module . This notation is declaring a subclass from the apache module - if you check the manifests folder inside the apache module folder, you will notice other classes defined in different files.

Normally, a module will ship with some utility classes like this "module" one (which enables apache modules).

Want to use this vagrant basic setup for your development environment? All this code is available in a github repository: vagrantee .

Writing a basic Puppet tutorial is very hard, because we are talking about a complex tool with a really extensive documentation. I hope this helps you to get started, and if you have any questions feel free to leave a comment.

Written by Erika Heidi on Wednesday July 10, 2013
Permalink - Lang: eng - Icon: icon-doc-text - Categories: DevOps, Vagrant - Tags: vagrant, guide, puppet, devops


comments powered by Disqus