Provisioning with Puppet

The pairing server should be easy to destroy and re-create because neither party wants to be responsible for maintaining it or ensuring that all of its software gets upgraded in lockstep with local development environments. That’s why an entire development team should maintain provisioning scripts, such as Chef or Puppet scripts, that install the software the pairing server needs when it boots up. These provisioning scripts provide many advantages over manual setup and traditional Shell scripts because they are portable, configurable, and modular, and thus easier to maintain. We’ll use Puppet to provision our server because its syntax is declarative and does not require knowledge of any particular programming language.

We need to create a directory for our Puppet scripts on our host machine. Make sure you’ve exited the VM and run this command from the pairing-server directory:

 
$ ​mkdir -p puppet/manifests

Now create a puppet/manifests/site.pp file to contain our primary configuration. Then add the following code to it.

pairing-server/puppet/manifests/site.pp
 
$username = "vagrant"
 
$home = "/home/${username}"
 
$app_name = "fulcrum"
 
$app_dir = "${home}/${app_name}"
 
 
Exec {
 
path => ['/usr/sbin', '/usr/bin', '/usr/local/bin', '/sbin', '/bin']
 
}

This sets up some useful variables and configures the path that Puppet will use when our scripts are running. Now we can add a few resources to the site.pp. They will initialize our server with some essential packages and libraries. Put these lines at the end of the file.

pairing-server/puppet/manifests/site.pp
 
stage { 'preinstall':
 
before => Stage['main']
 
}
 
 
class prepare {
 
exec { 'apt-get -y update':
 
unless => "test -e ${app_dir}"
 
}
 
 
package { ['build-essential', 'curl', 'autoconf', 'libgdbm-dev',
 
'automake', 'libtool', 'bison', 'pkg-config', 'libffi-dev',
 
'libyaml-dev', 'libncurses5-dev', 'libxml2', 'libxml2-dev',
 
'libxslt1-dev', 'libqt4-dev', 'postgresql-server-dev-9.1',
 
'nodejs', 'libreadline6-dev', 'libssl-dev', 'zlib1g-dev']:
 
ensure => installed,
 
require => Exec['apt-get -y update']
 
}
 
}
 
class { 'prepare':
 
stage => preinstall
 
}

This will update our operating system’s package manager and download the packages we’ve defined. It’s mostly boilerplate that’s necessary to get our image up-to-date and ready for general development work.

Now we must add the site.pp manifest to our Vagrant configuration so it will run when we start up our VM. Open the Vagrantfile and add the following code to the Vagrant::configure("2") block:

pairing-server/Vagrantfile
 
config.vm.provision :puppet ​do​ |puppet|
 
puppet.manifests_path = ​"puppet/manifests"
 
puppet.manifest_file = ​"site.pp"
 
end

Next, we’ll tell Vagrant to load and execute the Puppet scripts by running the following command:

 
$ ​vagrant reload
 
[default] Attempting graceful shutdown of VM...
 
[default] Setting the name of the VM...
 
[default] Clearing any previously set forwarded ports...
 
[default] Creating shared folders metadata...
 
[default] Clearing any previously set network interfaces...
 
[default] Preparing network interfaces based on configuration...
 
[default] Forwarding ports...
 
[default] -- 22 => 2222 (adapter 1)
 
[default] Booting VM...
 
[default] Waiting for VM to boot. This can take a few minutes.
 
[default] VM booted and ready for use!
 
[default] Configuring and enabling network interfaces...
 
[default] Mounting shared folders...
 
[default] -- /vagrant
 
[default] -- /tmp/vagrant-puppet/manifests
 
[default] -- /tmp/vagrant-puppet/modules-0
 
[default] Running provisioner: VagrantPlugins::Puppet::Provisioner::Puppet...
 
Running Puppet with site.pp...
 
stdin: is not a tty
 
notice: /Stage[preinstall]/Prepare/Exec[apt-get -y update]/returns: execut...
 
notice: Finished catalog run in 6.27 seconds

Much of the output is similar to the vagrant up command, but Vagrant is also running the provisioner, which loads our Puppet scripts.

Now that our VM has been provisioned with these essentials, we can start adding our development tools.

Installing Development Tools

The tools you’ll need for your applications depend on the types of applications you’re building. You may need Node.js, Postgres, Hadoop, or any number of other technologies. For our example, we’ll install a Ruby runtime, the SQLite database, tmux, and the Xvfb virtual windowing system.

We can include all of these tools in our configuration by adding the following code to the end of the site.pp file.

pairing-server/puppet/manifests/site.pp
 
package { ["ruby1.9.1", "ruby1.9.1-dev", "rubygems1.9.1", "irb1.9.1",
 
"libopenssl-ruby1.9.1", "sqlite3", "libsqlite3-dev", "tmux", "xvfb",
 
"firefox"]:
 
ensure => installed
 
}

We need Xvfb because our pairing server is headless (that is, it has no graphical user interface). But the application we’ll be working on is web-based, which means we’ll need a browser to run our tests. Xvfb takes the place of a graphical user interface and even a monitor by performing all graphical operations in memory. In this way, our tests can execute in a browser even though the browser won’t be rendered on a physical display. This is similar to how we ran the Backbone.js tests on PhantomJS in Chapter 2, Collaborating with Text Only.

Now we can use the vagrant provision command to run the provisioner and install our new packages:

 
$ ​vagrant provision

With these tools installed, we’re ready to work on some code. But first we’ll need to get the code.

Installing the Code Base

Our application’s source code is stored in a Git repository. As you probably know, Git is a version-control system that tracks changes to a bunch of files. We’ll create some Puppet resources to check out the Git repository to our pairing server, and we’ll put these resources in a Puppet module. To create the module, run the following command:

 
$ ​mkdir -p puppet/modules/git/manifests

Then add this line of code to the config.vm.provision block in our Vagrantfile so that the provisioner will know where to find our modules the next time it runs.

pairing-server/Vagrantfile
 
puppet.module_path = ​"puppet/modules"

In the puppet/modules/git/manifests directory, create an init.pp file, and add the following code to it:

pairing-server/puppet/modules/git/manifests/init.pp
 
class git {
 
package { "git-core":
 
ensure => "present"
 
}
 
}

This defines a Puppet class named Git that ensures the git-core package is installed. Next, we need to define a function that will allow us to use the git-core package to clone a repository. Add the following code to the end of the file:

pairing-server/puppet/modules/git/manifests/init.pp
 
define git::clone( $path, $source ){
 
exec { "git_clone_${name}":
 
command => "git clone ${source} ${path}",
 
creates => "${path}/.git",
 
user => $username,
 
require => Package[git-core],
 
timeout => 600
 
}
 
}

This function takes a $path argument, which is the location where we want to clone the Git repository, and a $source argument, which is the URL of the Git repository we want to clone.

The function body checks to see if the cloned repository already exists (so that we won’t repeat the cloning step after restarting the VM). If it doesn’t exist, then Puppet will execute the git clone command with our arguments.

Now we can use this function in our Puppet configuration. Open the site.pp file and add the following code to the end of it.

pairing-server/puppet/manifests/site.pp
 
include git
 
git::clone { $app_name :
 
path => $app_dir,
 
source => "git://github.com/malclocke/fulcrum.git"
 
}

This step will vary depending on the location of the application’s repository. The application we’ll be using, Fulcrum, is hosted on GitHub. However, the repository URL we’ve specified is a read-only location. That mean’s we can’t push our commits back to the repository of origin.

In reality, you’ll want to commit your code changes and push them to a shared repository. To do this, you’ll probably need to provide the Git host with an SSH key. We’ll address this later in the chapter when we deploy these Puppet scripts to the cloud.

Let’s run vagrant reload again, and have Puppet download Fulcrum’s source code.

 
$ ​vagrant reload
 
...​
 
[default] Running provisioner: VagrantPlugins::Puppet::Provisioner::Puppet...
 
Running Puppet with site.pp...
 
stdin: is not a ttynotice: /Stage[main]/Git/Package[git-core]/ensure: ensure ...
 
notice: /Stage[main]//Git::Clone[fulcrum]/Exec[git_clone_fulcrum]/returns: ex...
 
notice: Finished catalog run in 35.90 seconds

This time, the provisioner installed Git and cloned the Fulcrum repository. Now we can initialize the code base, and download any libraries it needs.

Initializing the Code Base

The last few resources we’ll add to our configuration are application-specific. Because Fulcrum is a Ruby on Rails application we must set up some configuration files and run Bundler to download its dependencies. Let’s begin with Bundler. Add the following resource to the end of the site.pp file to ensure that it’s installed.

pairing-server/puppet/manifests/site.pp
 
exec {"install_bundler":
 
command => "gem install bundler",
 
require => Package['ruby1.9.1', 'rubygems1.9.1']
 
}

The require attribute instructs Puppet to run this resource after installing the ruby1.9.1 and rubygems1.9.1 packages. Now we can add a resource that uses Bundler to install our application dependencies. Put this code at the end of the site.pp file.

pairing-server/puppet/manifests/site.pp
 
exec { "bundle" :
 
command => "su ${username} -c 'bundle install'",
 
cwd => $app_dir,
 
require => [Git::Clone[$app_name], Exec['install_bundler']]
 
}

The bundle install command will download and install all of the Ruby libraries that Fulcrum needs. We’ve also defined the require attribute so this resource will execute only after the Git repository has been cloned.

Next, we’ll create a function that initializes the database for a given environment. Add this code to the end of the site.pp file.

pairing-server/puppet/manifests/site.pp
 
define fulcrum::setup() {
 
exec { "fulcrum-setup-${name}":
 
command => "bundle exec rake fulcrum:setup db:setup",
 
cwd => $app_dir,
 
environment => "RAILS_ENV=${name}",
 
require => Exec["bundle"],
 
user => $username
 
}
 
}

Now we can invoke this function for the two environments we’ll use in our pair-programming work. Add this code to the end of the file:

pairing-server/puppet/manifests/site.pp
 
fulcrum::setup{"development": ;}
 
fulcrum::setup{"test": ;}

Let’s run the provisioner one more time so it can run Bundler and download our application’s dependencies.

 
$ ​vagrant provision
 
[default] Running provisioner: puppet...
 
Running Puppet with site.pp...
 
stdin: is not a tty
 
notice: /Stage[main]//Exec[bundle]/returns: executed successfully
 
notice: /Stage[main]//Fulcrum::Setup[development]/Exec[fulcrum-setup-develop...
 
notice: /Stage[main]//Fulcrum::Setup[test]/Exec[fulcrum-setup-test]/returns:...
 
notice: Finished catalog run in 95.10 seconds

Now we’re ready to pair-program with our server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.51.36