Sandboxing Cookbook Development With Gems, Vagrant and Rake Tasks

Recently I had a passion to hack on my Magento Cookbook project, but the time I wasted, just to get started after 10 month of break, feels like a problem and cause me to start looking for a solution to simplify life for myself and, hopefully, future contributors.

Cookbook Development Workflow

Let me briefly remind you how it looks like to get started with cookbook development:

  1. Clone Chef Repository from Opscode’s GitHub repository;
  2. Create/retrieve cookbook you developing/contributing;
  3. Install dependencies for Chef
  4. Install dependencies for the cookbook;
  5. Configure virtual environment to verify your changes.

The first three is fairly easy, although repetitive.

Fourth might be cumbersome, because at the moment of writing there was no command to resolve dependencies of locally developed cookbooks.

And the fifth step is advanced. You’ve to decide to go straight with Cloud Instances and start spending the money for sandbox or use Vagrant which is nice, but takes some learning curve.

Also inconsistency might lead extra troubles which cause negative impact on contributions.

Thus, despite the fact the cookbooks consists of simple ruby scripts, the environment setup still quite expensive.

Introducing Sandbox

Let’s say there is cookbook-foo repository you interested to play with.

Now imagine…

What if you could get all operational dependencies with:

bundle install

What if you could initialize sandbox environment with Chef Repository, current cookbook and all of it’s dependencies specified in metadata.rb as simple as:

rake sandbox:init

What if you could bootstrap VirtualBox instance, with Chef Solo, as simple as:

rake sandbox:up

And then provision running instance after some changes or whenever previous command failed:

rake sandbox:provision

What do you think?

All you need is include 3 files, listed below, into your cookbook repository:

(Gemfile) download
1
2
3
4
5
source "http://rubygems.org"

gem 'rake'
gem "chef", "~> 10.12.0"
gem "vagrant", "~> 1.0.3"

You know what it is for, right? :)

(Vagrantfile) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant::Config.run do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "lucid32"

  # Forward a port from the guest to the host, which allows for outside
  # computers to access the VM, whereas host only networking does not.
  config.vm.forward_port 80, 8080

  # Enable provisioning with chef solo, specifying a cookbooks path, roles
  # path, and data_bags path (all relative to this Vagrantfile), and adding
  # some recipes and/or roles.
  #
  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ".sandbox/cookbooks"
    chef.roles_path = ".sandbox/roles"
    chef.data_bags_path = ".sandbox/data_bags"
    # chef.add_recipe "magento::nginx"
    # chef.add_recipe "magento::mysql"
    chef.add_recipe "magento::sample"
    # chef.add_role "web"

    # You may also specify custom JSON attributes:
    chef.json = { "mysql" => { "bind_address" => "127.0.0.1", "server_root_password" => "toor" }, "magento" => { "url" => "http://localhost:8080/" } }
  end
end

Above is example from my Magento Cookbook, you’ll need to modify to fit your cookbook specifics.

(Rakefile) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
namespace 'sandbox' do
  desc 'Check if Sandbox has been initialized'
  task :check do
    if File.directory?('.sandbox')
      puts 'Checking Sandbox: Available!'
    else
      abort("Please, initialize sandbox: `rake sandbox:init`")
    end
  end

  desc 'Initialize Sandbox'
  task :init, [:force] do |t, args|
    unless File.directory?('.sandbox') and args.force != 'force'
      sh %{ rm -rf .sandbox }
      sh %{ git clone git://github.com/opscode/chef-repo.git .sandbox }
      mkdir('.sandbox/.chef')
      ::File.open('.sandbox/.chef/knife.rb', "w") do |f|
        f.puts <<-EOH
current_dir = File.dirname(__FILE__)
log_level                :info
log_location             STDOUT
cache_type               'BasicFile'
cache_options( :path => "\#{ENV['HOME']}/.chef/checksums" )
cookbook_path            ["\#{current_dir}/../cookbooks"]
EOH
      end
      Rake::Task['sandbox:dependencies'].invoke
    end
  end

  desc 'Copy current cookbook files into Sandbox'
  task :cookbook => ["check"] do
    dest_dir = '.sandbox/cookbooks/magento'
    puts "Copying cookbook to #{dest_dir}:"
    FileUtils.mkdir_p dest_dir
    Dir.new('.').each do |file|
      if file !~ /^\./
        puts " - #{file}"
        FileUtils.cp_r file, '.sandbox/cookbooks/magento'
      end
    end
  end

  desc 'Install Cookbook Dependencies'
  task :dependencies => ["check"] do
    require 'chef/cookbook/metadata'
    md = Chef::Cookbook::Metadata.new
    md.from_file('metadata.rb')
    pwd = Dir.pwd
    Dir.chdir("#{pwd}/.sandbox") # chef repo
    md.dependencies.each do |cookbook, version|
      # Doesn't do versions.. yet
      sh %{ knife cookbook site install #{cookbook} }
    end
    Dir.chdir(pwd)
  end

  desc 'Startup Sandbox'
  task :up => ["cookbook"] do
    begin
      sh %{ vagrant up }
    rescue
      abort "TIP: use `rake sandbox:provision` to continue"
    end
  end

  desc 'Provision Sandbox'
  task :provision => ["cookbook"] do
    sh %{ vagrant provision }
  end

  desc 'Destroy Sandbox'
  task :destroy do
    sh %{ vagrant destroy }
  end

  desc 'SSH to Sandbox instance'
  task :ssh do
    sh %{ vagrant ssh }
  end
end

Rakefile is where all magic reside, use rake -T to see complete list of the tasks available.

Conclusion

The technique isn’t limited to cookbooks development and can be applied to any project, especially useful for reproducing complex environments, replacing quite long tutorials by a few commands.

Comments