Vagrant provisioning with chef-solo

vagrant is a wonderful tool when you want to manage virtual machines in a development context. It even supports chef-solo provisioning.

Today I wanted to run a vagrant VM with the same chef-solo configuration I have on the host. So here’s the first Vagrantfile I wrote:

config.vm.box = "debian_squeeze_32"
config.vm.share_folder "chef-cookbooks", "/var/chef", "/var/chef"
config.vm.provision :chef_solo do |chef|
  chef.cookbooks_path = "/var/chef/cookbooks"
  chef.json.merge!(JSON.parse(File.read("/etc/chef/dna.json")))
end

But it ended up with:

% vagrant up     
There was a problem with the configuration of Vagrant. The error message(s)
are printed below:

vm:
* Run list must not be empty.

When you read the documentation, it seems vagrant assumes you add some recipes explicitly through the add_recipe method:

dna = JSON.parse(File.read("/etc/chef/dna.json"))
dna["recipes"].each do |recipe|
chef.add_recipe(recipe)
chef.json.merge!(dna)

…which ends with a Chef error:

[default] /usr/lib/ruby/1.8/chef/node.rb:382:in `consume_run_list': stderr
[default] : please set the node's run list using the 'run_list' attribute only. (Chef::Exceptions::AmbiguousRunlistSpecification)

Ok, let’s remove the recipes key in our json file:

dna = JSON.parse(File.read("/etc/chef/dna.json"))
dna.delete("recipes").each do |recipe|
chef.add_recipe(recipe)
chef.json.merge!(dna)

It works. But it’s not really clean. After a research in the vagrant gem source code, I found that json[:run_list] does exactly what I want, so here’s the final Vagrantfile:

config.vm.box = "debian_squeeze_32"
config.vm.share_folder "chef-cookbooks", "/var/chef", "/var/chef"
config.vm.provision :chef_solo do |chef|
  dna = JSON.parse(File.read("/etc/chef/dna.json"))
  dna[:run_list] = dna.delete("recipes")
  chef.cookbooks_path = "/var/chef/cookbooks"
  chef.json.merge!(dna)
end

Next!