Testing Puppet and Hiera

Puppet Labs logoAt MaestroDev we have been using Puppet 3 for a quite some time now, and one of the main reasons to upgrade from Puppet 2.x was the ability of using Hiera as a data backend for all the variables that customize the different vms. We don’t have a lot of machines but pretty much all of them have some difference so Hiera allows us to have the same manifests and modules apply to all the machines by just using different parameters in each server.

But testing Hiera is not that simple. With rspec-puppet you can test each class by passing parameters, but how can you test a class that calls another class and so on, and at some point there you need to inject a parameter?

Well, this is possible with the hiera-puppet-helper gem you can stub the hiera data backend

source 'https://rubygems.org'

group :rake do
gem 'puppet'
gem 'rspec-puppet'
gem 'hiera-puppet-helper'
gem 'rake'
gem 'puppetlabs_spec_helper'
end
require 'puppetlabs_spec_helper/module_spec_helper'
require 'hiera-puppet-helper/rspec'
require 'hiera'
require 'puppet/indirector/hiera'

# config hiera to work with let(:hiera_data)
def hiera_stub
  config = Hiera::Config.load(hiera_config)
  config[:logger] = 'puppet'
  Hiera.new(:config => config)
end

RSpec.configure do |c|
  c.mock_framework = :rspec

  c.before(:each) do
    Puppet::Indirector::Hiera.stub(:hiera => hiera_stub)
  end

end

And then you can use let(:hiera_data) to inject any parameters automatically into the puppet classes from your rspec tests.

require 'spec_helper'

describe 'mymodule::myclass' do

let(:hiera_data) {{
  'mymodule::myclass::myparam' => 'myvalue'
}}

it { should contain_class('mymodule::myclass').with_myparam('myvalue') }

Check out a full module using hiera-puppet-helper at maestro_nodes.

PuppetConf video: How to Develop Puppet Modules

How to Develop Puppet Modules. From Source to the Forge With Zero Clicks (slides)

Puppet Modules are a great way to reuse code, share your development with other people and take advantage of the hundreds of modules already available in the community. But how to create, test and publish them as easily as possible? now that infrastructure is defined as code, we need to use development best practices to build, test, deploy and use Puppet modules themselves.

Three steps for a fully automated process

  • Continuous Integration of Puppet Modules
  • Automatic release and upload to the Puppet Forge
  • Deploy to Puppet master

More about PuppetConf in my previous entry.

PuppetConf recap: How to Develop Puppet Modules. From Source to the Forge With Zero Clicks

Puppet Labs logoLast week I was at PuppetConf in San Francisco, speaking about automating the build-test-release-deploy lifecycle for Puppet modules and how we use them. Check out the PuppetLabs blog The Puppet Forge: Modules From Design to Deployment  and see below the original post at MaestroDev’s blog and some (quite unrelated) pictures I took. More conference pictures in the official Flickr account.

Last week PuppetConf took place at the Fairmont in San Francisco, gathering Puppet users and enthusiasts from all over the world for five days of training, development and sessions.

MaestroDev was present at the event as we are heavy Puppet users, and contributors! We are currently the 3rd most frequent contributor to Puppet modules in the Puppet Forge and publish 30 modules, of the 50 that we use on a day to day basis.

Our architect Carlos Sanchez gave a presentation on How to Develop Puppet Modules: From Source to the Forge With Zero Clicks, showing how to apply an automatic build-test-release-deploy cycle for Puppet Modules, because, when you use Infrastructure-as-Code you have to apply the development best practices to your infrastructure. In a demo, he showed how we use Maestro to automatically build, test, release and deploy our Puppet modules to the Puppet Forge, triggered on each commit for a truly Continuous Delivery experience.


And, not only do we use Puppet to provide module deployment to the Forge, we also integrate Maestro with Puppet and Puppet Enterprise to automate Puppet updates across your Puppet agents.  When you put these capabilities together with other development automation capabilities you achieve Continuous Delivery for both your applications and infrastructure.

Imagine you are building or releasing the latest version of your software and need to propagate an update through all the Puppet agents running, as well as any updates to config files or packages managed by Puppet. Instead of waiting for the next Puppet run to happen, Maestro can automatically deploy the Puppet manifests and modules to the Puppet master after being tested, and propagate those changes as well as the latest application built to all or a subset of the servers running with Puppet agents.

Thanks to all the Puppet Labs guys for a great event, and especially to Ryan Coleman for the work he is doing on the Forge and his help getting these Maestro integrations working.  We look forward to seeing everyone at the next event!

Testing puppet modules

Puppet Labs logoThere are several steps depending on how much involved the tests are, what parts are tested and, of course, how long it takes to run the tests.

For unit testing we use rspec puppet, and we can check that our manifests and modules compile and contain the expected values. It can be used to test that specific types, classes or definitions are in the compiled catalog and that the parameters math the expectations.

Later on we can do some integration testing starting a new VM with Vagrant and checking that there are no errors in the provisioning, as well as checking that some conditions are met.

For rspec-puppet, PuppetLabs has created a project called puppetlabs_spec_helper that let’s us avoid writing a bunch of boilerplate. A missing point though is that it only allows to use modules for testing from git. If you’re already using librarian-puppet (and you should!) you can easily use the same Puppetfile for deploying modules and to test them. Doing otherwise sounds like a bit of useless testing, you could end with different versions in different development machines, CI server, puppet master,… So just add a call to librarian puppet in your rakefile to populate the rspec-puppet fixtures before running the specs.

Unfortunately rspec-puppet doesn’t work with Puppet 3.0.x and  at least Puppet 3.1.0-rc1 is required. It was a bit of a setback when we moved to Puppet 3 and started using hiera, which is proving to be very useful to have simpler manifests and external data injected for our Maestro installations with Puppet from scratch.

You can also use the same Puppetfile to start Vagrant boxes with the exact same version of the modules. We are using Cucumber and Aruba to execute vagrant, provision the VM with puppet and check several things, like open ports, services up,… but that’s a different story 🙂

Example

In this puppet-for-java-devs project you will find the bits that showcase all these tools integrated. It includes definition of a 3-tier system with Puppet definitions for a postgresql database, tomcat nodes with a war installed and apache nodes fronting them.

Install all required gems

bundle install

Install all Puppet modules with Puppet Librarian

librarian-puppet install

Run the specs with puppet-rspec

bundle exec rake

Start all the vms with Vagrant

vagrant up

Rakefile

require 'bundler'
Bundler.require(:rake)
require 'rake/clean'

require 'puppetlabs_spec_helper/rake_tasks'

CLEAN.include('modules', 'spec/fixtures/', 'doc')
CLOBBER.include('.tmp', '.librarian')

task :librarian_spec_prep do
 sh "librarian-puppet install"
end
task :spec_prep => :librarian_spec_prep

task :default => [:spec]

Puppetfile for librarian-puppet

forge 'http://forge.puppetlabs.com'

mod 'puppetlabs/java', '0.1.6'
mod 'puppetlabs/apache', '0.4.0'
mod 'inkling/postgresql', '0.2.0'
mod 'puppetlabs/firewall', '0.0.4'
mod 'tomcat', :git => 'https://github.com/carlossg/puppet-tomcat.git', :ref => 'centos'
mod 'maestrodev/maven', '1.x'
mod 'stahnma/epel', '0.0.2'
mod 'maestrodev/avahi', '1.x'
mod 'other', :path => 'mymodules/other'

tomcat_spec.rb with rspec-puppet

require 'spec_helper'

describe 'tomcat1.acme.com' do
  let(:facts) { {:osfamily => 'RedHat', :operatingsystem => 'CentOS', :operatingsystemrelease => 6.3} }

  it { should contain_class('java').with_distribution /openjdk/ }

  it "configure webapp" do
    should contain_maven('/srv/tomcat/appfuse/webapps/ROOT.war')
    should contain_maven('/srv/tomcat/appfuse/webapps/ROOT/WEB-INF/lib/postgresql-9.1-901.jdbc4.jar')
  end
end

Vagrantfile

Vagrant::Config.run do |config|
  config.vm.box = "CentOS-6.3-x86_64-minimal"
  config.vm.box_url = "https://dl.dropbox.com/u/7225008/Vagrant/CentOS-6.3-x86_64-minimal.box"

  config.vm.customize ["modifyvm", :id, "--rtcuseutc", "on"] # use UTC clock https://github.com/mitchellh/vagrant/issues/912

  # db server
  config.vm.define :db do |config|
    config.vm.host_name = "db.acme.local"
    config.vm.customize ["modifyvm", :id, "--name", "db"] # name for VirtualBox GUI
    config.vm.forward_port 5432, 5432
    config.vm.network :hostonly, "192.168.33.10"
    config.vm.provision :puppet do |puppet|
      puppet.module_path = "modules"
      puppet.manifest_file = "site.pp"
    end
  end

  # tomcat server
  config.vm.define :tomcat1 do |config|
    config.vm.host_name = "tomcat1.acme.local"
    config.vm.customize ["modifyvm", :id, "--name", "tomcat1"] # name for VirtualBox GUI
    config.vm.forward_port 8080, 8081
    config.vm.network :hostonly, "192.168.33.11"
    config.vm.provision :puppet do |puppet|
      puppet.module_path = "modules"
      puppet.manifest_file = "site.pp"
    end
  end

  # web server
  config.vm.define :www do |config|
    config.vm.host_name = "www.acme.local"
    config.vm.customize ["modifyvm", :id, "--name", "www"] # name for VirtualBox GUI
    config.vm.forward_port 80, 8080
    config.vm.network :hostonly, "192.168.33.12"
    config.vm.provision :puppet do |puppet|
      puppet.module_path = "modules"
      puppet.manifest_file = "site.pp"
    end
  end

end

Managing Puppet modules with librarian-puppet

Puppet Labs logoOnce you have your Infrastructure defined as code you need to use coding best practises.

This definitely applies to Puppet code, you have Puppet classes to encapsulate functionality, and modules that group classes together for reuse and redistribution. Modules can be reused from git repositories or from the Puppet Forge, a web archive of tarballed modules in the same fashion as rpm or maven repositories. Modules in the forge are defined by a provider, module name and version, much like Maven groupid, artifactid and version, and a provider has full control over the modules that can be published under its name, with a more open approach, as anybody can upload anything themselves through the forge website, just signup and start uploading them.

But trouble starts when you are using a number of modules from different places, as each module can have its own set of dependencies. How can the dependencies be defined, downloaded and installed automatically as happens with any other package manager?

There are several options actually

  • Git submodules. This is the poor man’s version, where you add the git repositories of other modules as git submodules and use git submodule init and update commands to stay up to date
  • Puppet module tool. Allows the installation of modules from the Puppet forge based on the module name provider/name and version. It will download and unpackage the module in the directory specified
  • Librarian-puppet. It is an extension of ruby’s bundler model to install gems, having a file that defines your dependencies, Gemfile in the case of bundler, Puppetfile in the case of librarian, and a .lock file with the resolved dependencies.

The resolution model is different than those of yum, apt-get or maven, that resolve dependencies at every run on the client, relying on the clients to consistently do that over versions and machines. In the bundler or librarian model, resolution only happens once and it’s saved to the lock file, which is used from there on by the clients until dependencies are changed.

Dependency declaration

The librarian Puppetfile allows defining module dependencies from several sources

Puppet Forge

Using the forge provider and module name, and optionally version it calls puppet module tool to fetch the tarball and extract it in the designed directory

mod "maestrodev/maven", "1.0.0"

Git

Modules can also be cloned from a git repository optionally defining what branch, tag or revision to checkout

mod "maven",
  :git => "https://github.com/maestrodev/puppet-maven.git",
  :ref => 'v1.0.0'

Path folders

Useful mostly for testing or while transitioning to a full dependency model, local paths can be used as modules

mod 'mymodule', :path => './private/mymodule'

Improvements and issues

Librarian offers several improvements over the puppet module tool. Besides being able to bring in modules from different sources, librarian locks the versions of modules used, which is a must in order to consistently reproduce results. Let’s say you define a dependency on module maestrodev/maven > 1.0.0. If you run it now then librarian will lock the version to 1.0.0. If 1.1.0 is released later your dependencies will stay the same. If you try to run the puppet module tool with

puppet module install maestrodev-maven

you would end with 1.0.0 now and  a newer version later on.

There are some problems, and we hit (and fixed!) a lot of them. We have a fork of the original project where we keep updating the maestrodev branch while our pull requests don’t make it to the original repo. Unfortunately some of the patches have been sitting there for too long and we went ahead and created the librarian-puppet-maestrodev gem  that you can use instead of the original one.

Some of the issues you’ll likely find are the mismatch between puppet module version conventions and gem versions. librarian uses the bundler gemfile resolution mechanism which does not accept some versions such as the ones including dashes 1.0-rc1, or the puppet module dependency type 1.x which our librarian gem will adequately convert to ~>1.0 instead of failing. Check out the rubygems version policies for more details.

Next post I’ll cover how to use librarian in combination with puppetlabs spec helper to make your puppet testing much easier and consistent, and I’ll cover the topic in my upcoming talk on continuous delivery at ApacheCon Portland in February, plus you’ll find me next monday the 28th at the Los Angeles DevOps meetup, and at DevOps Days LA February 22nd as part of the SCALE conference.

Puppet Module of the Week: maestrodev/maven – Maven repository artifact downloads

This is a guest post I wrote in the Puppetlabs blog for their Module of the Week program about the MaestroDev/maven module we created.

Module of the Week: maestrodev/maven – Maven repository artifact downloads

Purpose Manage Apache Maven installation and download artifacts from Maven repositories
Module maestrodev/maven
Puppet Version 2.7+
Platforms RHEL5, RHEL6

The maven module allows Puppet users to install and configure Apache Maven, the build and project management tool, as well as easily use dependencies from Maven repositories.

If you use Maven repositories to store the artifacts resulting from your development process, whether you use Maven, Ivy, Gradle or any other tool capable of pushing builds to Maven repositories, this module defines a new maven type that will let you deploy those artifacts into any Puppet managed server. For instance, you can deploy WAR files directly from your Maven repository by just using their groupId, artifactId and version, bridging development and provisioning without any extra steps or packaging like RPMs or debs.

The maven type allows you to easily provision servers during development by using SNAPSHOT versions—using the latest build for provisioning. Together with a CI tool, this enables you to always keep your development servers up to date.

In this first version, this module supports

  • Installing Apache Maven
  • Configuring Maven settings.xml for repository configuration
  • Configuring Maven environment variables
  • Downloading artifacts from Maven repositories

Installing the module

Complexity Easy
Installation Time 2 minutes

Installing the Maven module is as simple as using the Puppet module tool, available in Puppet 2.7.14+ and Puppet Enterprise 2.5+, and also available as a RubyGem:

$ puppet module install maestrodev-maven
Preparing to install into /etc/puppet/modules ...
Downloading from http://forge.puppetlabs.com ...
Installing -- do not interrupt ...
/etc/puppet/modules
└─┬ maestrodev-maven (v0.0.1)
  └── maestrodev-wget (v0.0.1)

Alternatively, you can install the Maven module manually:

$ cd /etc/puppet/modules/

$ wget http://forge.puppetlabs.com/system/releases/m/maestrodev/maestrodev-maven-0.0.1.tar.gz

$ tar zxvf maestrodev-maven-0.0.1.tar.gz && rm maestrodev-maven-0.0.1.tar.gz
$ mv maestrodev-maven-0.0.1 maven
$ wget http://forge.puppetlabs.com/system/releases/m/maestrodev/maestrodev-wget-0.0.1.tar.gz
$ tar zxvf maestrodev-wget-0.0.1.tar.gz && rm maestrodev-wget-0.0.1.tar.gz
$ mv maestrodev-wget-0.0.1 wget

Resource Overview

CLASSES

maven class

This class installs Apache Maven with a default version of 2.2.1

maven::maven class

Installs Apache Maven, allowing you to specify the version of Maven you wish to install

DEFINITIONS

maven::environment

The definition allows us to configure Apache Maven environment variables on a per-user basis.

maven::settings

Configures $HOME/.m2/settings.xml per user with repositories, mirrors, credentials and properties.

TYPES

maven

This new type lets us download files from remote Maven repositories. Maven must be previously installed.

Testing the module

The module includes some Puppet rspec tests that use the puppetlabs_spec_helper, so it’s simple to implement, and all the fixtures will be automatically downloaded and tests run.

There is a Gemfile included to install all the dependent gems, so after running

$ bundle install

The tests can be executed with

$ bundle exec rake spec

Configuring the module

Complexity Easy
Installation Time 5 minutes

To install Maven there are two options, a simple one to install the default version (2.2.1):

include maven

or a slightly more complex option that customizes the version:

class { "maven::maven":
  version => "3.0.4"
}

Maven will be downloaded by default from the main Apache archive location. It can be configured to be downloaded from a different repository, like one in the local network, by using this repository syntax used throughout the module.

$repo = {
  id       => "myrepo",
  username => "myuser",
  password => "mypassword",
  url      => "http://repo.acme.com",
  mirrorof => "external:*" # if you want to use the repo as a mirror, see maven::settings below
}

class { "maven::maven":
  version => "3.0.4",
  repo    => $repo
}

Once you have Maven installed you can configure the Maven settings.xml for different users, override the mirrors, servers, localRepository, active properties and default repository. It is particularly useful to force Maven to use a repository in the internal network for faster downloads. These settings are used by both command line Maven and the maven puppet type.

We are using hashes to be able to reuse repository definitions, without copy and paste, like the $repo definition above.

# Create a settings.xml with the repo credentials
maven::settings { 'maven' :
  mirrors             => [$central], # mirrors entry in settings.xml, uses id, url, mirrorof from the hash passed
  servers             => [$central], # servers entry in settings.xml, uses id, username, password from the hash passed
  user                => 'maven',
  default_repo_config => {
    url       => $repo['url],
    snapshots => {
      enabled      => 'true',
      updatePolicy => 'always'
    },
    releases  => {
      enabled      => 'true',
      updatePolicy => 'always'
    }
  }
  properties          => {
    myproperty => 'myvalue'
  },
  local_repo          => '/home/maven/.m2/repository'
}

We can override the central repository with mirrors, whichb add repositories to the mirrors settings. The servers parameter configures each settings.xml server entry for user and password credentials.

With default_repo_config, we can add a repository that will be enabled for all Maven executions, including the aven puppet type. That would be necessary in order to check a remote repository for snapshots, as there is no snapshot repository defined by default in Maven.

The properties parameter is a hash with keys and values for the properties section of the settings, while local_repo overrides Maven default local repository location.

Another Maven file that can be configured to alter the Maven environment variables is $HOME/.mavenrc with the maven::environment class. The .mavenrc is sourced by the Apache Maven script for each run.

maven::environment { 'env-maven-user' :
  user                 => 'maven',
  maven_opts           => '-XX:MaxPermSize=256m',
  maven_path_additions => '/usr/local/bin'
}

Probably the module’s most useful functionality is the ability to download artifacts from Maven repositories. This requires having Maven correctly installed and configured, which can be done with the previous classes and definitions, and uses the Maven dependency:get plugin behind the scenes. The title of the maven resource is used as the file destination, and the user to run maven as can be set with the user parameter.

maven { "/tmp/maven-core-2.2.1.jar":
  id    => "org.apache.maven:maven-core:2.2.1:jar",
  repos => ["central::default::http://repo.maven.apache.org/maven2","http://mirrors.ibiblio.org/pub/mirrors/maven2"],
  user  => "maven",
}

With the optional parameter repos, we can define what repositories to download the dependencies from if not using the default Maven central. The parameter is in the form expected by the Maven dependency plugin, that is id::[layout]::url or just url, separated by a comma.

Or, a little more verbose:

maven { "/tmp/maven-core-2.2.1-sources.jar":
  groupid    => "org.apache.maven",
  artifactid => "maven-core",
  version    => "2.2.1",
  classifier => "sources",
  packaging  => "jar",
  user       => "maven",
}

Example usage

With some simple declarations we can install Maven in a node, downloading the Apache Maven binaries from apache.org and uncompressing them under /usr/local, and then download any file from the central Maven repo. An example is maven-core-2.2.1.jar, which is located in the repository under org.apache.maven groupId and maven-core artifactId.

# Install Maven
class { "maven::maven": } ->

maven { "/tmp/maven-core-2.2.1.jar":
  id => "org.apache.maven:maven-core:2.2.1:jar",
}

The usage of the shorter form groupId:artifactId:version:packaging allows us to be more concise, but we could do the same using the groupid, artifactid, version, packaging parameters of the maven type. Note that we are using the chain arrow (->) to explicitly install Maven before using it to download the jar file.

You should have a /tmp/maven-core-2.2.1.jar file with contents matching those of http://repo.maven.apache.org/maven2/org/apache/maven/maven-core/2.2.1/maven-core-2.2.1.jar.

Conclusion

If you use Apache Maven this module comes in handy for installing and configuring it on any machine in a consistent and repeatable way. This module also consumes the output artifacts from the development process in later stages of product delivery without extra steps or re-packaging.

Please let us know if you have any issues with the module. We are looking for new ways to improve the module, such as removing the need for wget to be installed. We look forward to your feedback!

Learn More:

Puppet for Java developers talk at JavaZone Oslo 2012

I am in Oslo right now speaking at JavaZone about Puppet for Java developers covering some of the basics but then getting into using Vagrant, Puppet and Puppet modules, to manage maven dependencies, postgresql, tomcat, and apache as examples.

The sample code showcases how to effectively use Puppet and modules, with unit testing and testing with Vagrant.

Update: The video is now up. Run a bit short on time and didn’t have as much time as I wanted for the demo but hopefully the sample code is useful to understand the tools involved.

Puppet is an infrastructure-as-code tool that allows easy and automated provisioning of servers, defining the packages, configuration, services,… in code. Enabling DevOps culture, tools like Puppet help drive Agile development all the way to operations and systems administration, and along with continuous integration tools like Jenkins, it is a key piece to accomplish repeatability and continuous delivery, automating the operations side during development, QA or production, and enabling testing of systems configuration.
Traditionally a field for system administrators, Puppet can empower developers, allowing both to collaborate coding the infrastructure needed for their developments, whether it runs in hardware, virtual machines or cloud. Developers and sysadmins can define what JDK version must be installed, application server, version, configuration files, war and jar files,… and easily make changes that propagate across all nodes.
Using Vagrant, a command line automation layer for VirtualBox, they can also spin off virtual machines in their local box, easily from scratch with the same configuration as production servers, do development or testing and tear them down afterwards.
We’ll show how to install and manage Puppet nodes with JDK, multiple application server instances with installed web applications, database, configuration files and all the supporting services. Including getting up and running with Vagrant and VirtualBox for quickstart and Puppet experiments, as well as setting up automated testing of the Puppet code.

Learning Puppet or Chef? Check out Vagrant!

If you are starting to use Puppet or Chef, you must have Vagrant.

Learning Puppet can be a tedious task, getting up the master, agents, writing your first manifests,… A good way to start is using Vagrant, an Oracle VirtualBox command line automation tool, that allows easy Puppet and Chef provisioning on VirtualBox VMs.

Vagrant projects are composed by base boxes, specifically configured for Vagrant with Puppet/Chef, vagrant username and password, and anything else you may want to add, plus the configuration to apply to those base boxes, defined with Puppet or Chef. That way we can have several projects using the same base boxes shared where the only difference are the Puppet/Chef definitions. For instance a database VM and a web server VM can both use the same base box and just have different Puppet manifests, and when Vagrant starts them, it will apply the specific configuration. That also allows to share boxes and configuration files across teams, for instance having one base box with the Linux flavor used in a team, we can just have in source control the Puppet manifests to apply for the different configurations that anybody from Operations to Developers can use.

There is a list of available VMs or base boxes ready to use with Vagrant at www.vagrantbox.es. But you can build your own and share it anywhere, as they are just (big) VirtualBox VM files, easily using VeeWee, or changing a base box and rebundling it with vagrant package.

Usage

Once you have installed Vagrant and VirtualBox.

Vagrant init will create a sample Vagrantfile, the project definition file that can be customized.

$ vagrant init myproject

Then in the Vagrantfile you can change the default box settings, and add basic Puppet provisioning

config.vm.box = "centos-6"
config.vm.box_url = "https://vagrant-centos-6.s3.amazonaws.com/centos-6.box"

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

In manifests/site.pp you can try any puppet manifest.

file { '/etc/motd':
  content => 'Welcome to your Vagrant-built virtual machine! Managed by Puppet.\n'
}

Vagrant up will download the box the first time, start the VM and apply any configuration defined in Puppet

$ vagrant up

vagrant ssh will open a shell into the box. Under the hood vagrant is redirecting the host port 2222 to the vagrant box 22

$ vagrant ssh

The vm can be suspended and resumed at any time

$ vagrant suspend
$ vagrant resume

and later on destroyed, which will delete all the VM files.

$ vagrant destroy

And then we can start again from scratch with vagrant up getting a completely new vm where we can make any mistakes 🙂

Introduction to Puppet

Enough about philosophical posts, let’s get started with some practical Puppet.

Manifests

Puppet configuration files are called manifests, written in a ruby-like DSL. Puppet provides types and functions to manage typical resources (files, services, users, groups,…) and new ones can be defined through extensions called modules.

The standard types that can be used are listed in the Puppet reference. There is a cheat sheet available (pdf) with the main ones.

The resources are grouped in classes, that can later be easily reused.

class 'maven' {
  exec { 'maven-untar':
    command => 'tar xf /tmp/x.tgz',
    cwd     => '/opt',
    creates => "/opt/apache-maven-${version}",
    path    => ["/bin"],
  } ->
  file { '/usr/bin/mvn':
    ensure => link,
    target => "/opt/apache-maven-${version}/bin/mvn",
  }
  file { '/usr/local/bin/mvn':
    ensure  => absent,
    require => Exec["maven-untar"],
  }
  file { "${home}/.mavenrc":
    mode    => '0600',
    owner   => $user,
    content => template('maven/mavenrc.erb'),
    require => User[$user],
  }
}

Infrastructure IS code, for example we can specify that we want the openssh-server package installed

package { 'openssh-server':
  ensure => present,
}

Declarative model

Puppet uses a declarative model, where we define state, not process. We define that a service must be running and puppet will start it if not running, or do nothing if it already is.

service { 'ntp':
  name   => 'ntpd',
  ensure => running,
}

There is no scripting, we don’t make the service start, just define whether it should be running. This is key to understand how puppet works. A side effect is that variables can only be assigned once, so they are pretty much like constants.

Architecture

Puppet is arranged in a master – agent architecture.  The master serves the manifests and files, and the agents poll the master at specific intervals of time to get their configuration. The master does not push anything into the client.

Agents identify with the master using SSL, so the first time an agent tries to connect to the master, the agent certificate needs to be approved (in the default configuration), and that’s usually a source of problems.

File structure

Puppet configuration files are usually in /etc/puppet.

The main files in there are manifests/site.pp which defines the configurations, and the manifests/nodes.pp that defines how those configurations apply to the different nodes or agents, based on their hostname, generally, or other properties.

Site

class 'dave' {
  user { 'dave':
    ensure     => present,
    uid        => '507',
    gid        => 'admin',
    shell      => '/bin/zsh',
    home       => '/home/dave',
    managehome => true,
  }
  file {'/tmp/test1':
    ensure  => present,
    content => "Hi.",
  }
}

Nodes

node 'someserver.domain.com' {
  class { 'dave': }
}

More information

More information about types, resources, manifests, variables,… at learning puppet from PuppetLabs.