Capistrano Provisioning

Capistrano Provisioning is an extension to Capistrano that allows you to define clusters of servers and run provisioning tasks on them, such as installing users. It is a replacement for the fabric gem (http://rubygems.org/gems/fabric).

A word of caution

This software is in alpha - we're releasing early so we can start using this gem in anger.

Installation

With bundler

Add the following to your Gemfile:

gem 'capistrano-provisioning'

If your bundler environment isn't initialised yet, you'll need to add something like this to your deploy.rb/Capfile:

require 'rubygems'
require 'bundler'

Bundler.setup
require 'capistrano-provisioning/recipes'   

Without bundler

gem install capistrano-provisioning

Either within your Capfile, or your deploy.rb, add:

require 'capistrano-provisioning/recipes'

Usage

Once you have required the gem, the cluster command is available to you within capistrano recipes.

The simplest usage of this would be something like:

cluster :web, 'web1.example.com'

In fact, this is shorthand for the following:

cluster :web do
  servers 'web1.example.com'
end

Both of these would add a cap task called 'web'; calling this will mean any task specified later in the chain will run on the 'web' cluster. Eg:

cap web invoke COMMAND='uptime'

Which will run the 'uptime' command on 'web1.example.com'.

You can specify multiple remote hosts like so:

cluster :web, 'web1.example.com', 'web2.example.com'

So... this far it's quite similar to cap's own 'role' syntax. After this, however, life gets interesting...

Chaining

If you want to run a task on multiple clusters, simply chain them as so:

cap web db invoke COMMAND='uptime'

... Assuming that you've defined a web and a db cluster!

This will also work with namespaced clusters.

Installing users

Specify the users that belong on a cluster as so:

cluster :web do
  servers 'web1.example.com'
  users 'bob', 'joe'
end

And put these users' public ssh keys in config/keys directory as so config/keys/bob.pub and config/keys/joe.pub

Running:

cap web install_users

... will now create these users on the servers if they don't exist, and add the keys to their authorized_keys file.

You can specify groups as so:

cluster :web do
  servers 'web1.example.com'
  users 'bob', 'joe', :groups => ['some_group']
  users 'rupert'
end

This will add Bob and Joe to some_group (:groups is an array, so add as many as you like), but won't add Rupert.

Bootstrapping

To specify a block of code that is to be run to 'bootstrap' a server, do this:

cluster :web do
  servers 'web1.example.com'
  bootstrap do
    run 'some_command'
  end
end

Run the following:

cap web run_bootstrap

And some_command will be run on the servers.

Namespaces and default users

Namespaces work as you would expect:

namespace :system1 do
  cluster :web do
    # ...
  end

  cluster :db do
    # ...
  end
end

This block will create the following tasks:

cap system1:web
cap system1:db

As well as

cap system1:all

Which will load all of the clusters defined in system1.

It is also possible to define a default group of users for a namespace:

namespace :system1 do
  default_users 'bob', 'joe', :groups => ['some_group']
  default_users 'rupert'
  cluster :web do
    # ...
  end

  cluster :db do
    # ...
  end
end

Which would mean that running:

cap system1:web install_users

... would add Bob, Joe and Rupert to the web cluster, with the appropriate groups.

To only install specific users, specify them in a hosts variable:

cap system1:web install_users USERS='bob'

This will only install bob - useful for if you're just adding one user and don't want to do a fullscale pass of all the keys. Note, though, that this user still needs to be defined within the recipe.

Inheriting default users

By default, namespaces do not inherit default users from the namespace above. If you want this inheritance, it's easy:

namespace :nested_namespace do
  inherit_default_users
end

If you want those users to have an additional group within this namespace only, use the following:

namespace :nested_namespace do
  inherit_default_users :additional_groups => 'additional_group'
end

You can also pass an array of additional groups:

namespace :nested_namespace do
  inherit_default_users :additional_groups => ['additional_group_1', 'additional_group_2']
end

Inheriting specific default users

These groups of default users can also be named, so you can atomically specify inheritance:

namespace :system1 do
  default_users 'bob', 'joe', :groups => ['some_group']
  default_users 'rupert'
  cluster :web do
    # ...
  end

  cluster :db do
    # ...
  end
end

Clusters can also inherit these named groups:

namespace :system1 do
  default_users :admins, 'bob', 'joe'
  cluster :web do
    user :admins
  end
end

These can be mixed in with normal user names:

namespace :system1 do
  default_users :admins, 'bob', 'joe'
  cluster :web do
    user :admins, 'sam'
  end
end

Features we guess we're probably going to need

  • Ability to output business-friendly documentation of how the clusters are set up (and what user access exists)
  • A way to remove user accounts that don't belong

Note on Patches/Pull Requests

  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
  • Send me a pull request. Bonus points for topic branches.

Copyright (c) 2010 Sam Phillips. See LICENSE for details.