From df648803dd4ca8dbcf6d198998b2a919019f05ac Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 19:00:49 -0800 Subject: [PATCH] Pull out common chef code into its own abstract provisioner --- lib/vagrant.rb | 2 +- lib/vagrant/provisioners/chef.rb | 67 +++++++++++++++++ lib/vagrant/provisioners/chef_solo.rb | 67 ++--------------- test/test_helper.rb | 8 +-- test/vagrant/provisioners/chef_solo_test.rb | 69 ++---------------- test/vagrant/provisioners/chef_test.rb | 79 +++++++++++++++++++++ 6 files changed, 162 insertions(+), 130 deletions(-) create mode 100644 lib/vagrant/provisioners/chef.rb create mode 100644 test/vagrant/provisioners/chef_test.rb diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 284f1ed4a..a93685a6f 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -5,7 +5,7 @@ PROJECT_ROOT = File.join(libdir, '..') unless defined?(PROJECT_ROOT) # The libs which must be loaded prior to the rest %w{tempfile open-uri json pathname logger uri net/http virtualbox net/ssh archive/tar/minitar net/scp fileutils vagrant/util vagrant/actions/base vagrant/downloaders/base vagrant/actions/runner - vagrant/config vagrant/provisioners/base}.each do |f| + vagrant/config vagrant/provisioners/base vagrant/provisioners/chef}.each do |f| require f end diff --git a/lib/vagrant/provisioners/chef.rb b/lib/vagrant/provisioners/chef.rb new file mode 100644 index 000000000..8c5683a48 --- /dev/null +++ b/lib/vagrant/provisioners/chef.rb @@ -0,0 +1,67 @@ +module Vagrant + module Provisioners + # This class is a base class where the common functinality shared between + # chef-solo and chef-client provisioning are stored. This is **not an actual + # provisioner**. Instead, {ChefSolo} or {ChefServer} should be used. + class Chef < Base + # This is the configuration which is available through `config.chef` + class ChefConfig < Vagrant::Config::Base + attr_accessor :cookbooks_path + attr_accessor :provisioning_path + attr_accessor :json + + def initialize + @cookbooks_path = "cookbooks" + @provisioning_path = "/tmp/vagrant-chef" + @json = { + :instance_role => "vagrant", + :run_list => ["recipe[vagrant_main]"] + } + end + + def to_json + # Overridden so that the 'json' key could be removed, since its just + # merged into the config anyways + data = instance_variables_hash + data.delete(:json) + data.to_json + end + end + + # Tell the Vagrant configure class about our custom configuration + Config.configures :chef, ChefConfig + + def prepare + raise Actions::ActionException.new("Vagrant::Provisioners::Chef is not a valid provisioner! Use ChefSolo or ChefServer instead.") + end + + def chown_provisioning_folder + logger.info "Setting permissions on chef provisioning folder..." + SSH.execute do |ssh| + ssh.exec!("sudo chown #{Vagrant.config.ssh.username} #{Vagrant.config.chef.provisioning_path}") + end + end + + def setup_json + logger.info "Generating chef JSON and uploading..." + + # Set up initial configuration + data = { + :config => Vagrant.config, + :directory => Vagrant.config.vm.project_directory, + } + + # And wrap it under the "vagrant" namespace + data = { :vagrant => data } + + # Merge with the "extra data" which isn't put under the + # vagrant namespace by default + data.merge!(Vagrant.config.chef.json) + + json = data.to_json + + SSH.upload!(StringIO.new(json), File.join(Vagrant.config.chef.provisioning_path, "dna.json")) + end + end + end +end \ No newline at end of file diff --git a/lib/vagrant/provisioners/chef_solo.rb b/lib/vagrant/provisioners/chef_solo.rb index ec689a9f7..c1163569a 100644 --- a/lib/vagrant/provisioners/chef_solo.rb +++ b/lib/vagrant/provisioners/chef_solo.rb @@ -1,36 +1,9 @@ module Vagrant module Provisioners # This class implements provisioning via chef-solo. - class ChefSolo < Base - # This is the configuration which is available through `config.chef_solo` - class CustomConfig < Vagrant::Config::Base - attr_accessor :cookbooks_path - attr_accessor :provisioning_path - attr_accessor :json - - def initialize - @cookbooks_path = "cookbooks" - @provisioning_path = "/tmp/vagrant-chef" - @json = { - :instance_role => "vagrant", - :run_list => ["recipe[vagrant_main]"] - } - end - - def to_json - # Overridden so that the 'json' key could be removed, since its just - # merged into the config anyways - data = instance_variables_hash - data.delete(:json) - data.to_json - end - end - - # Tell the Vagrant configure class about our custom configuration - Config.configures :chef_solo, CustomConfig - + class ChefSolo < Chef def prepare - Vagrant.config.vm.share_folder("vagrant-chef-solo", cookbooks_path, File.expand_path(Vagrant.config.chef_solo.cookbooks_path, Env.root_path)) + Vagrant.config.vm.share_folder("vagrant-chef-solo", cookbooks_path, File.expand_path(Vagrant.config.chef.cookbooks_path, Env.root_path)) end def provision! @@ -40,48 +13,20 @@ module Vagrant run_chef_solo end - def chown_provisioning_folder - logger.info "Setting permissions on chef solo provisioning folder..." - SSH.execute do |ssh| - ssh.exec!("sudo chown #{Vagrant.config.ssh.username} #{Vagrant.config.chef_solo.provisioning_path}") - end - end - - def setup_json - logger.info "Generating chef solo JSON and uploading..." - - # Set up initial configuration - data = { - :config => Vagrant.config, - :directory => Vagrant.config.vm.project_directory, - } - - # And wrap it under the "vagrant" namespace - data = { :vagrant => data } - - # Merge with the "extra data" which isn't put under the - # vagrant namespace by default - data.merge!(Vagrant.config.chef_solo.json) - - json = data.to_json - - SSH.upload!(StringIO.new(json), File.join(Vagrant.config.chef_solo.provisioning_path, "dna.json")) - end - def setup_solo_config solo_file = <<-solo -file_cache_path "#{Vagrant.config.chef_solo.provisioning_path}" +file_cache_path "#{Vagrant.config.chef.provisioning_path}" cookbook_path "#{cookbooks_path}" solo logger.info "Uploading chef-solo configuration script..." - SSH.upload!(StringIO.new(solo_file), File.join(Vagrant.config.chef_solo.provisioning_path, "solo.rb")) + SSH.upload!(StringIO.new(solo_file), File.join(Vagrant.config.chef.provisioning_path, "solo.rb")) end def run_chef_solo logger.info "Running chef-solo..." SSH.execute do |ssh| - ssh.exec!("cd #{Vagrant.config.chef_solo.provisioning_path} && sudo chef-solo -c solo.rb -j dna.json") do |channel, data, stream| + ssh.exec!("cd #{Vagrant.config.chef.provisioning_path} && sudo chef-solo -c solo.rb -j dna.json") do |channel, data, stream| # TODO: Very verbose. It would be easier to save the data and only show it during # an error, or when verbosity level is set high logger.info("#{stream}: #{data}") @@ -90,7 +35,7 @@ solo end def cookbooks_path - File.join(Vagrant.config.chef_solo.provisioning_path, "cookbooks") + File.join(Vagrant.config.chef.provisioning_path, "cookbooks") end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 77642a96d..ad64df4d0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -42,10 +42,10 @@ class Test::Unit::TestCase config.package.name = 'vagrant' config.package.extension = '.box' - # Chef solo - config.chef_solo.cookbooks_path = "cookbooks" - config.chef_solo.provisioning_path = "/tmp/hobo-chef" - config.chef_solo.json = { + # Chef + config.chef.cookbooks_path = "cookbooks" + config.chef.provisioning_path = "/tmp/hobo-chef" + config.chef.json = { :recipes => ["hobo_main"] } diff --git a/test/vagrant/provisioners/chef_solo_test.rb b/test/vagrant/provisioners/chef_solo_test.rb index 31822a893..9f8156cb3 100644 --- a/test/vagrant/provisioners/chef_solo_test.rb +++ b/test/vagrant/provisioners/chef_solo_test.rb @@ -10,21 +10,9 @@ class ChefSoloProvisionerTest < Test::Unit::TestCase mock_config end - context "config" do - setup do - @config = Vagrant::Provisioners::ChefSolo::CustomConfig.new - @config.json = "HEY" - end - - should "not include the 'json' key in the config dump" do - result = JSON.parse(@config.to_json) - assert !result.has_key?("json") - end - end - context "shared folders" do should "setup shared folder on VM for the cookbooks" do - File.expects(:expand_path).with(Vagrant.config.chef_solo.cookbooks_path, Vagrant::Env.root_path).returns("foo") + File.expects(:expand_path).with(Vagrant.config.chef.cookbooks_path, Vagrant::Env.root_path).returns("foo") @action.expects(:cookbooks_path).returns("bar") Vagrant.config.vm.expects(:share_folder).with("vagrant-chef-solo", "bar", "foo").once @action.prepare @@ -33,62 +21,15 @@ class ChefSoloProvisionerTest < Test::Unit::TestCase context "cookbooks path" do should "return the proper cookbook path" do - cookbooks_path = File.join(Vagrant.config.chef_solo.provisioning_path, "cookbooks") + cookbooks_path = File.join(Vagrant.config.chef.provisioning_path, "cookbooks") assert_equal cookbooks_path, @action.cookbooks_path end end - context "permissions on provisioning folder" do - should "chown the folder to the ssh user" do - ssh = mock("ssh") - ssh.expects(:exec!).with("sudo chown #{Vagrant.config.ssh.username} #{Vagrant.config.chef_solo.provisioning_path}") - Vagrant::SSH.expects(:execute).yields(ssh) - @action.chown_provisioning_folder - end - end - - context "generating and uploading json" do - def assert_json - Vagrant::SSH.expects(:upload!).with do |json, path| - data = JSON.parse(json.read) - yield data - true - end - - @action.setup_json - end - - should "merge in the extra json specified in the config" do - Vagrant.config.chef_solo.json = { :foo => "BAR" } - assert_json do |data| - assert_equal "BAR", data["foo"] - end - end - - should "add the directory as a special case to the JSON" do - assert_json do |data| - assert_equal Vagrant.config.vm.project_directory, data["vagrant"]["directory"] - end - end - - should "add the config to the JSON" do - assert_json do |data| - assert_equal Vagrant.config.vm.project_directory, data["vagrant"]["config"]["vm"]["project_directory"] - end - end - - should "upload a StringIO to dna.json" do - StringIO.expects(:new).with(anything).returns("bar") - File.expects(:join).with(Vagrant.config.chef_solo.provisioning_path, "dna.json").once.returns("baz") - Vagrant::SSH.expects(:upload!).with("bar", "baz").once - @action.setup_json - end - end - context "generating and uploading chef solo configuration file" do should "upload properly generate the configuration file using configuration data" do expected_config = <<-config -file_cache_path "#{Vagrant.config.chef_solo.provisioning_path}" +file_cache_path "#{Vagrant.config.chef.provisioning_path}" cookbook_path "#{@action.cookbooks_path}" config @@ -99,7 +40,7 @@ config should "upload this file as solo.rb to the provisioning folder" do @action.expects(:cookbooks_path).returns("cookbooks") StringIO.expects(:new).returns("foo") - File.expects(:join).with(Vagrant.config.chef_solo.provisioning_path, "solo.rb").once.returns("bar") + File.expects(:join).with(Vagrant.config.chef.provisioning_path, "solo.rb").once.returns("bar") Vagrant::SSH.expects(:upload!).with("foo", "bar").once @action.setup_solo_config end @@ -108,7 +49,7 @@ config context "running chef solo" do should "cd into the provisioning directory and run chef solo" do ssh = mock("ssh") - ssh.expects(:exec!).with("cd #{Vagrant.config.chef_solo.provisioning_path} && sudo chef-solo -c solo.rb -j dna.json").once + ssh.expects(:exec!).with("cd #{Vagrant.config.chef.provisioning_path} && sudo chef-solo -c solo.rb -j dna.json").once Vagrant::SSH.expects(:execute).yields(ssh) @action.run_chef_solo end diff --git a/test/vagrant/provisioners/chef_test.rb b/test/vagrant/provisioners/chef_test.rb new file mode 100644 index 000000000..4c4590339 --- /dev/null +++ b/test/vagrant/provisioners/chef_test.rb @@ -0,0 +1,79 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'test_helper') + +class ChefProvisionerTest < Test::Unit::TestCase + setup do + @action = Vagrant::Provisioners::Chef.new + + Vagrant::SSH.stubs(:execute) + Vagrant::SSH.stubs(:upload!) + + mock_config + end + + context "preparing" do + should "raise an ActionException" do + assert_raises(Vagrant::Actions::ActionException) { + @action.prepare + } + end + end + + context "config" do + setup do + @config = Vagrant::Provisioners::Chef::ChefConfig.new + @config.json = "HEY" + end + + should "not include the 'json' key in the config dump" do + result = JSON.parse(@config.to_json) + assert !result.has_key?("json") + end + end + + context "permissions on provisioning folder" do + should "chown the folder to the ssh user" do + ssh = mock("ssh") + ssh.expects(:exec!).with("sudo chown #{Vagrant.config.ssh.username} #{Vagrant.config.chef.provisioning_path}") + Vagrant::SSH.expects(:execute).yields(ssh) + @action.chown_provisioning_folder + end + end + + context "generating and uploading json" do + def assert_json + Vagrant::SSH.expects(:upload!).with do |json, path| + data = JSON.parse(json.read) + yield data + true + end + + @action.setup_json + end + + should "merge in the extra json specified in the config" do + Vagrant.config.chef.json = { :foo => "BAR" } + assert_json do |data| + assert_equal "BAR", data["foo"] + end + end + + should "add the directory as a special case to the JSON" do + assert_json do |data| + assert_equal Vagrant.config.vm.project_directory, data["vagrant"]["directory"] + end + end + + should "add the config to the JSON" do + assert_json do |data| + assert_equal Vagrant.config.vm.project_directory, data["vagrant"]["config"]["vm"]["project_directory"] + end + end + + should "upload a StringIO to dna.json" do + StringIO.expects(:new).with(anything).returns("bar") + File.expects(:join).with(Vagrant.config.chef.provisioning_path, "dna.json").once.returns("baz") + Vagrant::SSH.expects(:upload!).with("bar", "baz").once + @action.setup_json + end + end +end