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:
Create/retrieve cookbook you developing/contributing;
Install dependencies for Chef
Install dependencies for the cookbook;
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:
# -*- mode: ruby -*-# vi: set ft=ruby :Vagrant::Config.rundo|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_port80,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_solodo|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/"}}endend
Above is example from my Magento Cookbook, you’ll need to modify to fit
your cookbook specifics.
namespace'sandbox'dodesc'Check if Sandbox has been initialized'task:checkdoifFile.directory?('.sandbox')puts'Checking Sandbox: Available!'elseabort("Please, initialize sandbox: `rake sandbox:init`")endenddesc'Initialize Sandbox'task:init,[:force]do|t,args|unlessFile.directory?('.sandbox')andargs.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<<-EOHcurrent_dir = File.dirname(__FILE__)log_level :infolog_location STDOUTcache_type 'BasicFile'cache_options( :path => "\#{ENV['HOME']}/.chef/checksums" )cookbook_path ["\#{current_dir}/../cookbooks"]EOHendRake::Task['sandbox:dependencies'].invokeendenddesc'Copy current cookbook files into Sandbox'task:cookbook=>["check"]dodest_dir='.sandbox/cookbooks/magento'puts"Copying cookbook to #{dest_dir}:"FileUtils.mkdir_pdest_dirDir.new('.').eachdo|file|iffile!~/^\./puts" - #{file}"FileUtils.cp_rfile,'.sandbox/cookbooks/magento'endendenddesc'Install Cookbook Dependencies'task:dependencies=>["check"]dorequire'chef/cookbook/metadata'md=Chef::Cookbook::Metadata.newmd.from_file('metadata.rb')pwd=Dir.pwdDir.chdir("#{pwd}/.sandbox")# chef repomd.dependencies.eachdo|cookbook,version|# Doesn't do versions.. yetsh%{ knife cookbook site install #{cookbook} }endDir.chdir(pwd)enddesc'Startup Sandbox'task:up=>["cookbook"]dobeginsh%{ vagrant up }rescueabort"TIP: use `rake sandbox:provision` to continue"endenddesc'Provision Sandbox'task:provision=>["cookbook"]dosh%{ vagrant provision }enddesc'Destroy Sandbox'task:destroydosh%{ vagrant destroy }enddesc'SSH to Sandbox instance'task:sshdosh%{ vagrant ssh }endend
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.