From f86bc912ebe6b977c515f0c3231b0b67071e4cb8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 12:56:52 -0800 Subject: [PATCH 01/16] Base provisioning class --- lib/vagrant.rb | 3 ++- lib/vagrant/provisioners/base.rb | 16 ++++++++++++++++ test/vagrant/provisioners/base_test.rb | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 lib/vagrant/provisioners/base.rb create mode 100644 test/vagrant/provisioners/base_test.rb diff --git a/lib/vagrant.rb b/lib/vagrant.rb index c26475201..1c2f38080 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -4,7 +4,8 @@ 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}.each do |f| + net/scp fileutils vagrant/util vagrant/actions/base vagrant/downloaders/base vagrant/actions/runner + vagrant/provisioners/base}.each do |f| require f end diff --git a/lib/vagrant/provisioners/base.rb b/lib/vagrant/provisioners/base.rb new file mode 100644 index 000000000..26394086c --- /dev/null +++ b/lib/vagrant/provisioners/base.rb @@ -0,0 +1,16 @@ +module Vagrant + module Provisioners + # The base class for a "provisioner." A provisioner is responsible for + # provisioning a Vagrant system. This has been abstracted out to provide + # support for multiple solutions such as Chef Solo, Chef Client, and + # Puppet. + class Base + include Vagrant::Util + + # This is the single method called to provision the system. This method + # is expected to do whatever necessary to provision the system (create files, + # SSH, etc.) + def provision!; end + end + end +end \ No newline at end of file diff --git a/test/vagrant/provisioners/base_test.rb b/test/vagrant/provisioners/base_test.rb new file mode 100644 index 000000000..6473fcf77 --- /dev/null +++ b/test/vagrant/provisioners/base_test.rb @@ -0,0 +1,20 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'test_helper') + +class BaseProvisionerTest < Test::Unit::TestCase + should "include the util class so subclasses have access to it" do + assert Vagrant::Provisioners::Base.include?(Vagrant::Util) + end + + context "base instance" do + setup do + @base = Vagrant::Provisioners::Base.new + end + + should "implement provision! which does nothing" do + assert_nothing_raised do + assert @base.respond_to?(:provision!) + @base.provision! + end + end + end +end From 0d026da21ee5a47966f47e82e4dce3f836d27627 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 13:01:53 -0800 Subject: [PATCH 02/16] Base class for chef solo --- lib/vagrant/provisioners/chef_solo.rb | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lib/vagrant/provisioners/chef_solo.rb diff --git a/lib/vagrant/provisioners/chef_solo.rb b/lib/vagrant/provisioners/chef_solo.rb new file mode 100644 index 000000000..d47e6bc54 --- /dev/null +++ b/lib/vagrant/provisioners/chef_solo.rb @@ -0,0 +1,7 @@ +module Vagrant + module Provisioners + # This class implements provisioning via chef-solo. + class ChefSolo < Base + end + end +end \ No newline at end of file From d81e5dc28d4530178296db90f97c71439bdc2c64 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 14:27:02 -0800 Subject: [PATCH 03/16] Remove all "chef" configuration and moved chef solo provisioning to its own class --- config/default.rb | 7 +- lib/vagrant.rb | 2 +- lib/vagrant/actions/vm/provision.rb | 63 ----------- lib/vagrant/actions/vm/reload.rb | 2 +- lib/vagrant/actions/vm/up.rb | 2 +- lib/vagrant/config.rb | 25 +---- lib/vagrant/provisioners/base.rb | 8 +- lib/vagrant/provisioners/chef_solo.rb | 81 ++++++++++++++ test/test_helper.rb | 7 +- test/vagrant/actions/vm/provision_test.rb | 98 +---------------- test/vagrant/actions/vm/reload_test.rb | 3 +- test/vagrant/actions/vm/up_test.rb | 2 +- test/vagrant/config_test.rb | 12 -- test/vagrant/provisioners/base_test.rb | 7 ++ test/vagrant/provisioners/chef_solo_test.rb | 116 ++++++++++++++++++++ 15 files changed, 228 insertions(+), 207 deletions(-) create mode 100644 test/vagrant/provisioners/chef_solo_test.rb diff --git a/config/default.rb b/config/default.rb index e925c15c0..6e69f1206 100644 --- a/config/default.rb +++ b/config/default.rb @@ -20,10 +20,9 @@ Vagrant::Config.run do |config| config.package.name = 'vagrant' config.package.extension = '.box' - config.chef.enabled = false - config.chef.cookbooks_path = "cookbooks" - config.chef.provisioning_path = "/tmp/vagrant-chef" - config.chef.json = { + config.chef_solo.cookbooks_path = "cookbooks" + config.chef_solo.provisioning_path = "/tmp/vagrant-chef" + config.chef_solo.json = { :instance_role => "vagrant", :recipes => ["vagrant_main"] } diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 1c2f38080..284f1ed4a 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/provisioners/base}.each do |f| + vagrant/config vagrant/provisioners/base}.each do |f| require f end diff --git a/lib/vagrant/actions/vm/provision.rb b/lib/vagrant/actions/vm/provision.rb index 3750fb21a..9a42d87b1 100644 --- a/lib/vagrant/actions/vm/provision.rb +++ b/lib/vagrant/actions/vm/provision.rb @@ -2,69 +2,6 @@ module Vagrant module Actions module VM class Provision < Base - def prepare - Vagrant.config.vm.share_folder("vagrant-provisioning", cookbooks_path, File.expand_path(Vagrant.config.chef.cookbooks_path, Env.root_path)) - end - - def execute! - chown_provisioning_folder - setup_json - setup_solo_config - run_chef_solo - end - - def chown_provisioning_folder - logger.info "Setting permissions on 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 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 - - def setup_solo_config - solo_file = <<-solo -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.provisioning_path, "solo.rb")) - end - - def run_chef_solo - logger.info "Running chef recipes..." - SSH.execute do |ssh| - 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}") - end - end - end - - def cookbooks_path - File.join(Vagrant.config.chef.provisioning_path, "cookbooks") - end end end end diff --git a/lib/vagrant/actions/vm/reload.rb b/lib/vagrant/actions/vm/reload.rb index d699e0ff5..940cf183b 100644 --- a/lib/vagrant/actions/vm/reload.rb +++ b/lib/vagrant/actions/vm/reload.rb @@ -5,7 +5,7 @@ module Vagrant def prepare steps = [ForwardPorts, SharedFolders, Boot] steps.unshift(Halt) if @runner.vm.running? - steps << Provision if Vagrant.config.chef.enabled + steps << Provision if !Vagrant.config.vm.provisioner.nil? steps.each do |action_klass| @runner.add_action(action_klass) diff --git a/lib/vagrant/actions/vm/up.rb b/lib/vagrant/actions/vm/up.rb index 190fc64f1..6556e4863 100644 --- a/lib/vagrant/actions/vm/up.rb +++ b/lib/vagrant/actions/vm/up.rb @@ -17,7 +17,7 @@ msg # Up is a "meta-action" so it really just queues up a bunch # of other actions in its place: steps = [Import, ForwardPorts, SharedFolders, Boot] - steps << Provision if Vagrant.config.chef.enabled + steps << Provision if !Vagrant.config.vm.provisioner.nil? steps.insert(0, MoveHardDrive) if Vagrant.config.vm.hd_location steps.each do |action_klass| diff --git a/lib/vagrant/config.rb b/lib/vagrant/config.rb index 0b6459536..afeaa78e4 100644 --- a/lib/vagrant/config.rb +++ b/lib/vagrant/config.rb @@ -14,7 +14,7 @@ module Vagrant end def configures(key, klass) - @@config.class.configures(key, klass) + config.class.configures(key, klass) end def config @@ -75,11 +75,12 @@ module Vagrant attr_reader :shared_folders attr_accessor :hd_location attr_accessor :disk_image_format - + attr_accessor :provisioner def initialize @forwarded_ports = {} @shared_folders = {} + @provisioner = nil end def forward_port(name, guestport, hostport, protocol="TCP") @@ -112,25 +113,6 @@ module Vagrant attr_accessor :extension end - class ChefConfig < Base - attr_accessor :cookbooks_path - attr_accessor :provisioning_path - attr_accessor :json - attr_accessor :enabled - - def initialize - @enabled = false - 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 - class VagrantConfig < Base attr_accessor :dotfile_name attr_accessor :log_output @@ -159,7 +141,6 @@ module Vagrant configures :package, PackageConfig configures :ssh, SSHConfig configures :vm, VMConfig - configures :chef, ChefConfig configures :vagrant, VagrantConfig def initialize diff --git a/lib/vagrant/provisioners/base.rb b/lib/vagrant/provisioners/base.rb index 26394086c..cb7a650cf 100644 --- a/lib/vagrant/provisioners/base.rb +++ b/lib/vagrant/provisioners/base.rb @@ -7,7 +7,13 @@ module Vagrant class Base include Vagrant::Util - # This is the single method called to provision the system. This method + # This is the method called to "prepare" the provisioner. This is called + # before any actions are run by the action runner (see {Vagrant::Actions::Runner}). + # This can be used to setup shared folders, forward ports, etc. Whatever is + # necessary on a "meta" level. + def prepare; end + + # This is the method called to provision the system. This method # is expected to do whatever necessary to provision the system (create files, # SSH, etc.) def provision!; end diff --git a/lib/vagrant/provisioners/chef_solo.rb b/lib/vagrant/provisioners/chef_solo.rb index d47e6bc54..180257db6 100644 --- a/lib/vagrant/provisioners/chef_solo.rb +++ b/lib/vagrant/provisioners/chef_solo.rb @@ -2,6 +2,87 @@ 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 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 + + def prepare + Vagrant.config.vm.share_folder("vagrant-chef-solo", cookbooks_path, File.expand_path(Vagrant.config.chef_solo.cookbooks_path, Env.root_path)) + end + + def provision! + chown_provisioning_folder + setup_json + setup_solo_config + 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}" +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")) + 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| + # 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}") + end + end + end + + def cookbooks_path + File.join(Vagrant.config.chef_solo.provisioning_path, "cookbooks") + end end end end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 4c14f3b43..77642a96d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -42,9 +42,10 @@ class Test::Unit::TestCase config.package.name = 'vagrant' config.package.extension = '.box' - config.chef.cookbooks_path = "cookbooks" - config.chef.provisioning_path = "/tmp/hobo-chef" - config.chef.json = { + # Chef solo + config.chef_solo.cookbooks_path = "cookbooks" + config.chef_solo.provisioning_path = "/tmp/hobo-chef" + config.chef_solo.json = { :recipes => ["hobo_main"] } diff --git a/test/vagrant/actions/vm/provision_test.rb b/test/vagrant/actions/vm/provision_test.rb index 64e6b808c..70f90afcc 100644 --- a/test/vagrant/actions/vm/provision_test.rb +++ b/test/vagrant/actions/vm/provision_test.rb @@ -2,103 +2,7 @@ require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper') class ProvisionActionTest < Test::Unit::TestCase setup do - @mock_vm, @vm, @action = mock_action(Vagrant::Actions::VM::Provision) - - Vagrant::SSH.stubs(:execute) - Vagrant::SSH.stubs(:upload!) - + @runner, @vm, @action = mock_action(Vagrant::Actions::VM::Provision) mock_config end - - context "shared folders" do - should "setup shared folder on VM for the cookbooks" do - 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-provisioning", "bar", "foo").once - @action.prepare - end - end - - context "cookbooks path" do - should "return the proper cookbook path" do - 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.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 - - 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.provisioning_path}" -cookbook_path "#{@action.cookbooks_path}" -config - - StringIO.expects(:new).with(expected_config).once - @action.setup_solo_config - end - - 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.provisioning_path, "solo.rb").once.returns("bar") - Vagrant::SSH.expects(:upload!).with("foo", "bar").once - @action.setup_solo_config - end - end - - 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.provisioning_path} && sudo chef-solo -c solo.rb -j dna.json").once - Vagrant::SSH.expects(:execute).yields(ssh) - @action.run_chef_solo - end - end end diff --git a/test/vagrant/actions/vm/reload_test.rb b/test/vagrant/actions/vm/reload_test.rb index 9218390ff..4bd912d71 100644 --- a/test/vagrant/actions/vm/reload_test.rb +++ b/test/vagrant/actions/vm/reload_test.rb @@ -33,7 +33,8 @@ class ReloadActionTest < Test::Unit::TestCase should "add in the provisioning step if enabled" do mock_config do |config| - config.chef.enabled = true + # Dummy provisioner to test + config.vm.provisioner = "foo" end @default_order.push(Vagrant::Actions::VM::Provision) diff --git a/test/vagrant/actions/vm/up_test.rb b/test/vagrant/actions/vm/up_test.rb index 520c70194..c39dfedca 100644 --- a/test/vagrant/actions/vm/up_test.rb +++ b/test/vagrant/actions/vm/up_test.rb @@ -47,7 +47,7 @@ class UpActionTest < Test::Unit::TestCase should "add in the provisioning step if enabled" do mock_config do |config| - config.chef.enabled = true + config.vm.provisioner = "foo" end @default_order.push(Vagrant::Actions::VM::Provision) diff --git a/test/vagrant/config_test.rb b/test/vagrant/config_test.rb index 311296e3e..547e07cb3 100644 --- a/test/vagrant/config_test.rb +++ b/test/vagrant/config_test.rb @@ -117,18 +117,6 @@ class ConfigTest < Test::Unit::TestCase end end - context "chef config" do - setup do - @config = Vagrant::Config::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 "top config class" do setup do @configures_list = [] diff --git a/test/vagrant/provisioners/base_test.rb b/test/vagrant/provisioners/base_test.rb index 6473fcf77..403665cc1 100644 --- a/test/vagrant/provisioners/base_test.rb +++ b/test/vagrant/provisioners/base_test.rb @@ -16,5 +16,12 @@ class BaseProvisionerTest < Test::Unit::TestCase @base.provision! end end + + should "implement prepare which does nothing" do + assert_nothing_raised do + assert @base.respond_to?(:prepare) + @base.prepare + end + end end end diff --git a/test/vagrant/provisioners/chef_solo_test.rb b/test/vagrant/provisioners/chef_solo_test.rb new file mode 100644 index 000000000..31822a893 --- /dev/null +++ b/test/vagrant/provisioners/chef_solo_test.rb @@ -0,0 +1,116 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'test_helper') + +class ChefSoloProvisionerTest < Test::Unit::TestCase + setup do + @action = Vagrant::Provisioners::ChefSolo.new + + Vagrant::SSH.stubs(:execute) + Vagrant::SSH.stubs(:upload!) + + 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") + @action.expects(:cookbooks_path).returns("bar") + Vagrant.config.vm.expects(:share_folder).with("vagrant-chef-solo", "bar", "foo").once + @action.prepare + end + end + + context "cookbooks path" do + should "return the proper cookbook path" do + cookbooks_path = File.join(Vagrant.config.chef_solo.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}" +cookbook_path "#{@action.cookbooks_path}" +config + + StringIO.expects(:new).with(expected_config).once + @action.setup_solo_config + end + + 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") + Vagrant::SSH.expects(:upload!).with("foo", "bar").once + @action.setup_solo_config + end + end + + 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 + Vagrant::SSH.expects(:execute).yields(ssh) + @action.run_chef_solo + end + end +end From 86ffbe5d32b82911a0c35f68e53337aa54b3c8f4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 14:54:12 -0800 Subject: [PATCH 04/16] Provision class now properly figures out the provisioner and executes it --- lib/vagrant/actions/vm/provision.rb | 39 ++++++++++ test/vagrant/actions/vm/provision_test.rb | 87 +++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/lib/vagrant/actions/vm/provision.rb b/lib/vagrant/actions/vm/provision.rb index 9a42d87b1..f712652d1 100644 --- a/lib/vagrant/actions/vm/provision.rb +++ b/lib/vagrant/actions/vm/provision.rb @@ -2,6 +2,45 @@ module Vagrant module Actions module VM class Provision < Base + attr_reader :provisioner + + def intialize(*args) + super + + @provisioner = nil + end + + def prepare + provisioner = Vagrant.config.vm.provisioner + + if provisioner.nil? + logger.info("Provisioning not enabled, ignoring this step") + return + end + + if provisioner.is_a?(Class) + @provisioner = provisioner.new + raise ActionException.new("Provisioners must be an instance of Vagrant::Provisioners::Base") unless @provisioner.is_a?(Provisioners::Base) + elsif provisioner.is_a?(Symbol) + # We have a few hard coded provisioners for built-ins + mapping = { + :chef_solo => Provisioners::ChefSolo + } + + provisioner_klass = mapping[provisioner] + raise ActionException.new("Unknown provisioner type: #{provisioner}") if provisioner_klass.nil? + @provisioner = provisioner_klass.new + end + + logger.info "Provisioning enabld with #{@provisioner.class}" + end + + def execute! + if provisioner + logger.info "Beginning provisining process..." + provisioner.provision! + end + end end end end diff --git a/test/vagrant/actions/vm/provision_test.rb b/test/vagrant/actions/vm/provision_test.rb index 70f90afcc..74cf5c95f 100644 --- a/test/vagrant/actions/vm/provision_test.rb +++ b/test/vagrant/actions/vm/provision_test.rb @@ -5,4 +5,91 @@ class ProvisionActionTest < Test::Unit::TestCase @runner, @vm, @action = mock_action(Vagrant::Actions::VM::Provision) mock_config end + + context "initialization" do + should "have a nil provisioner by default" do + assert_nil @action.provisioner + end + end + + context "executing" do + should "do nothing if the provisioner is nil" do + @action.expects(:provisioner).returns(nil) + assert_nothing_raised { @action.execute! } + end + + should "call `provision!` on the provisioner" do + provisioner = mock("provisioner") + provisioner.expects(:provision!).once + @action.expects(:provisioner).twice.returns(provisioner) + @action.execute! + end + end + + context "preparing" do + context "with a nil provisioner" do + setup do + mock_config do |config| + config.vm.provisioner = nil + end + end + + should "not set a provisioner if set to nil" do + @action.prepare + assert_nil @action.provisioner + end + end + + context "with a Class provisioner" do + setup do + @instance = mock("instance") + @instance.stubs(:is_a?).with(Vagrant::Provisioners::Base).returns(true) + @klass = mock("klass") + @klass.stubs(:is_a?).with(Class).returns(true) + @klass.stubs(:new).returns(@instance) + + mock_config do |config| + config.vm.provisioner = @klass + end + end + + should "set the provisioner to an instantiation of the class" do + @klass.expects(:new).once.returns(@instance) + assert_nothing_raised { @action.prepare } + assert_equal @instance, @action.provisioner + end + + should "raise an exception if the class is not a subclass of the provisioner base" do + @instance.expects(:is_a?).with(Vagrant::Provisioners::Base).returns(false) + assert_raises(Vagrant::Actions::ActionException) { + @action.prepare + } + end + end + + context "with a Symbol provisioner" do + def provisioner_expectation(symbol, provisioner) + mock_config do |config| + config.vm.provisioner = symbol + end + + assert_nothing_raised { @action.prepare } + assert @action.provisioner.is_a?(provisioner) + end + + should "raise an ActionException if its an unknown symbol" do + mock_config do |config| + config.vm.provisioner = :this_will_never_exist + end + + assert_raises(Vagrant::Actions::ActionException) { + @action.prepare + } + end + + should "set :chef_solo to the ChefSolo provisioner" do + provisioner_expectation(:chef_solo, Vagrant::Provisioners::ChefSolo) + end + end + end end From 2763540b445678f015af736d2ec94f72d4348513 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 14:58:41 -0800 Subject: [PATCH 05/16] Move default chef solo configuration to the chef solo provisioner --- config/default.rb | 8 +------- lib/vagrant/provisioners/chef_solo.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/config/default.rb b/config/default.rb index 6e69f1206..f107499c8 100644 --- a/config/default.rb +++ b/config/default.rb @@ -16,14 +16,8 @@ Vagrant::Config.run do |config| config.vm.project_directory = "/vagrant" config.vm.forward_port("ssh", 22, 2222) config.vm.disk_image_format = 'VMDK' + config.vm.provisioner = nil config.package.name = 'vagrant' config.package.extension = '.box' - - config.chef_solo.cookbooks_path = "cookbooks" - config.chef_solo.provisioning_path = "/tmp/vagrant-chef" - config.chef_solo.json = { - :instance_role => "vagrant", - :recipes => ["vagrant_main"] - } end diff --git a/lib/vagrant/provisioners/chef_solo.rb b/lib/vagrant/provisioners/chef_solo.rb index 180257db6..0fb4b37c2 100644 --- a/lib/vagrant/provisioners/chef_solo.rb +++ b/lib/vagrant/provisioners/chef_solo.rb @@ -8,6 +8,15 @@ module Vagrant attr_accessor :provisioning_path attr_accessor :json + def initialize + @cookbooks_path = "cookbooks" + @provisioning_path = "/tmp/vagrant-chef" + @json = { + :instance_role => "vagrant", + :recipes => ["vagrant_main"] + } + end + def to_json # Overridden so that the 'json' key could be removed, since its just # merged into the config anyways From 759d904628b9532bae64465661742a20fe178db3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 15:12:45 -0800 Subject: [PATCH 06/16] Provision action must call "prepare" on the provisioner --- lib/vagrant/actions/vm/provision.rb | 3 ++- test/vagrant/actions/vm/provision_test.rb | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/actions/vm/provision.rb b/lib/vagrant/actions/vm/provision.rb index f712652d1..d2e07c8a1 100644 --- a/lib/vagrant/actions/vm/provision.rb +++ b/lib/vagrant/actions/vm/provision.rb @@ -32,7 +32,8 @@ module Vagrant @provisioner = provisioner_klass.new end - logger.info "Provisioning enabld with #{@provisioner.class}" + logger.info "Provisioning enabled with #{@provisioner.class}" + @provisioner.prepare end def execute! diff --git a/test/vagrant/actions/vm/provision_test.rb b/test/vagrant/actions/vm/provision_test.rb index 74cf5c95f..0cd863b18 100644 --- a/test/vagrant/actions/vm/provision_test.rb +++ b/test/vagrant/actions/vm/provision_test.rb @@ -44,6 +44,7 @@ class ProvisionActionTest < Test::Unit::TestCase setup do @instance = mock("instance") @instance.stubs(:is_a?).with(Vagrant::Provisioners::Base).returns(true) + @instance.stubs(:prepare) @klass = mock("klass") @klass.stubs(:is_a?).with(Class).returns(true) @klass.stubs(:new).returns(@instance) @@ -59,6 +60,11 @@ class ProvisionActionTest < Test::Unit::TestCase assert_equal @instance, @action.provisioner end + should "call prepare on the instance" do + @instance.expects(:prepare).once + @action.prepare + end + should "raise an exception if the class is not a subclass of the provisioner base" do @instance.expects(:is_a?).with(Vagrant::Provisioners::Base).returns(false) assert_raises(Vagrant::Actions::ActionException) { @@ -90,6 +96,14 @@ class ProvisionActionTest < Test::Unit::TestCase should "set :chef_solo to the ChefSolo provisioner" do provisioner_expectation(:chef_solo, Vagrant::Provisioners::ChefSolo) end + + should "call prepare on the instance" do + instance = mock("instance") + instance.expects(:prepare).once + instance.stubs(:is_a?).returns(true) + Vagrant::Provisioners::ChefSolo.expects(:new).returns(instance) + provisioner_expectation(:chef_solo, Vagrant::Provisioners::ChefSolo) + end end end end From f6bc0ff5fca3419e3f40f15d5f74a32a295996fc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 18:34:52 -0800 Subject: [PATCH 07/16] Change chef solo to use `run_list` instead of `recipes` --- lib/vagrant/provisioners/chef_solo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant/provisioners/chef_solo.rb b/lib/vagrant/provisioners/chef_solo.rb index 0fb4b37c2..ec689a9f7 100644 --- a/lib/vagrant/provisioners/chef_solo.rb +++ b/lib/vagrant/provisioners/chef_solo.rb @@ -13,7 +13,7 @@ module Vagrant @provisioning_path = "/tmp/vagrant-chef" @json = { :instance_role => "vagrant", - :recipes => ["vagrant_main"] + :run_list => ["recipe[vagrant_main]"] } end From df648803dd4ca8dbcf6d198998b2a919019f05ac Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 19:00:49 -0800 Subject: [PATCH 08/16] 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 From df2e80140db4397db0416188b117cd9d04ea4091 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 21:57:51 -0800 Subject: [PATCH 09/16] Chef server provisioner --- lib/vagrant/provisioners/chef.rb | 10 ++ lib/vagrant/provisioners/chef_server.rb | 76 +++++++++++ test/test_helper.rb | 2 + test/vagrant/provisioners/chef_server_test.rb | 123 ++++++++++++++++++ test/vagrant/provisioners/chef_solo_test.rb | 11 ++ test/vagrant/provisioners/chef_test.rb | 6 +- 6 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 lib/vagrant/provisioners/chef_server.rb create mode 100644 test/vagrant/provisioners/chef_server_test.rb diff --git a/lib/vagrant/provisioners/chef.rb b/lib/vagrant/provisioners/chef.rb index 8c5683a48..f9f195f39 100644 --- a/lib/vagrant/provisioners/chef.rb +++ b/lib/vagrant/provisioners/chef.rb @@ -6,11 +6,20 @@ module Vagrant class Chef < Base # This is the configuration which is available through `config.chef` class ChefConfig < Vagrant::Config::Base + # Chef server specific config + attr_accessor :chef_server_url + attr_accessor :validation_key_path + attr_accessor :validation_client_name + + # Chef solo specific config attr_accessor :cookbooks_path + + # Shared config attr_accessor :provisioning_path attr_accessor :json def initialize + @validation_client_name = "chef-validator" @cookbooks_path = "cookbooks" @provisioning_path = "/tmp/vagrant-chef" @json = { @@ -38,6 +47,7 @@ module Vagrant def chown_provisioning_folder logger.info "Setting permissions on chef provisioning folder..." SSH.execute do |ssh| + ssh.exec!("sudo mkdir -p #{Vagrant.config.chef.provisioning_path}") ssh.exec!("sudo chown #{Vagrant.config.ssh.username} #{Vagrant.config.chef.provisioning_path}") end end diff --git a/lib/vagrant/provisioners/chef_server.rb b/lib/vagrant/provisioners/chef_server.rb new file mode 100644 index 000000000..a40d12bcb --- /dev/null +++ b/lib/vagrant/provisioners/chef_server.rb @@ -0,0 +1,76 @@ +module Vagrant + module Provisioners + # This class implements provisioning via chef-client, allowing provisioning + # with a chef server. + class ChefServer < Chef + def prepare + if Vagrant.config.chef.validation_key_path.nil? + raise Actions::ActionException.new(<<-msg) +Chef server provisioning requires that the `config.chef.validation_key_path` configuration +be set to a path on your local machine of the validation key used to register the +VM with the chef server. +msg + end + + if Vagrant.config.chef.chef_server_url.nil? + raise Actions::ActionException.new(<<-msg) +Chef server provisioning requires that the `config.chef.chef_server_url` be set to the +URL of your chef server. Examples include "http://12.12.12.12:4000" and +"http://myserver.com:4000" (the port of course can be different, but 4000 is the default) +msg + end + end + + def provision! + chown_provisioning_folder + upload_validation_key + setup_json + setup_config + run_chef_client + end + + def upload_validation_key + logger.info "Uploading chef client validation key..." + SSH.upload!(Vagrant.config.chef.validation_key_path, guest_validation_key_path) + end + + def setup_config + solo_file = <<-solo +log_level :info +log_location STDOUT +ssl_verify_mode :verify_none +chef_server_url "#{Vagrant.config.chef.chef_server_url}" + +validation_client_name "#{Vagrant.config.chef.validation_client_name}" +validation_key "#{guest_validation_key_path}" +client_key "/etc/chef/client.pem" + +file_store_path "/srv/chef/file_store" +file_cache_path "/srv/chef/cache" + +pid_file "/var/run/chef/chef-client.pid" + +Mixlib::Log::Formatter.show_time = true +solo + + logger.info "Uploading chef-client configuration script..." + SSH.upload!(StringIO.new(solo_file), File.join(Vagrant.config.chef.provisioning_path, "client.rb")) + end + + def run_chef_client + logger.info "Running chef-client..." + SSH.execute do |ssh| + ssh.exec!("cd #{Vagrant.config.chef.provisioning_path} && sudo chef-client -c client.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}") + end + end + end + + def guest_validation_key_path + File.join(Vagrant.config.chef.provisioning_path, "validation.pem") + end + end + end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index ad64df4d0..a29838700 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -43,6 +43,8 @@ class Test::Unit::TestCase config.package.extension = '.box' # Chef + config.chef.chef_server_url = "http://localhost:4000" + config.chef.validation_key_path = "validation.pem" config.chef.cookbooks_path = "cookbooks" config.chef.provisioning_path = "/tmp/hobo-chef" config.chef.json = { diff --git a/test/vagrant/provisioners/chef_server_test.rb b/test/vagrant/provisioners/chef_server_test.rb new file mode 100644 index 000000000..51139bf69 --- /dev/null +++ b/test/vagrant/provisioners/chef_server_test.rb @@ -0,0 +1,123 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'test_helper') + +class ChefServerProvisionerTest < Test::Unit::TestCase + setup do + @action = Vagrant::Provisioners::ChefServer.new + + Vagrant::SSH.stubs(:execute) + Vagrant::SSH.stubs(:upload!) + + mock_config + end + + context "provisioning" do + should "run the proper sequence of methods in order" do + prov_seq = sequence("prov_seq") + @action.expects(:chown_provisioning_folder).once.in_sequence(prov_seq) + @action.expects(:upload_validation_key).once.in_sequence(prov_seq) + @action.expects(:setup_json).once.in_sequence(prov_seq) + @action.expects(:setup_config).once.in_sequence(prov_seq) + @action.expects(:run_chef_client).once.in_sequence(prov_seq) + @action.provision! + end + end + + context "preparing" do + should "not raise an exception if validation_key_path is set" do + mock_config do |config| + config.chef.validation_key_path = "7" + end + + assert_nothing_raised { @action.prepare } + end + + should "raise an exception if validation_key_path is nil" do + mock_config do |config| + config.chef.validation_key_path = nil + end + + assert_raises(Vagrant::Actions::ActionException) { + @action.prepare + } + end + + should "not raise an exception if chef_server_url is set" do + mock_config do |config| + config.chef.chef_server_url = "7" + end + + assert_nothing_raised { @action.prepare } + end + + should "raise an exception if chef_server_url is nil" do + mock_config do |config| + config.chef.chef_server_url = nil + end + + assert_raises(Vagrant::Actions::ActionException) { + @action.prepare + } + end + end + + context "uploading the validation key" do + should "upload the validation key to the provisioning path" do + @action.expects(:guest_validation_key_path).once.returns("bar") + Vagrant::SSH.expects(:upload!).with(Vagrant.config.chef.validation_key_path, "bar").once + @action.upload_validation_key + end + end + + context "the guest validation key path" do + should "be the provisioning path joined with validation.pem" do + result = mock("result") + File.expects(:join).with(Vagrant.config.chef.provisioning_path, "validation.pem").once.returns(result) + assert_equal result, @action.guest_validation_key_path + end + end + + context "generating and uploading chef client configuration file" do + setup do + @action.stubs(:guest_validation_key_path).returns("foo") + end + + should "upload properly generate the configuration file using configuration data" do + expected_config = <<-config +log_level :info +log_location STDOUT +ssl_verify_mode :verify_none +chef_server_url "#{Vagrant.config.chef.chef_server_url}" + +validation_client_name "#{Vagrant.config.chef.validation_client_name}" +validation_key "#{@action.guest_validation_key_path}" +client_key "/etc/chef/client.pem" + +file_store_path "/srv/chef/file_store" +file_cache_path "/srv/chef/cache" + +pid_file "/var/run/chef/chef-client.pid" + +Mixlib::Log::Formatter.show_time = true +config + + StringIO.expects(:new).with(expected_config).once + @action.setup_config + end + + should "upload this file as client.rb to the provisioning folder" do + StringIO.expects(:new).returns("foo") + File.expects(:join).with(Vagrant.config.chef.provisioning_path, "client.rb").once.returns("bar") + Vagrant::SSH.expects(:upload!).with("foo", "bar").once + @action.setup_config + end + end + + context "running chef client" do + should "cd into the provisioning directory and run chef client" do + ssh = mock("ssh") + ssh.expects(:exec!).with("cd #{Vagrant.config.chef.provisioning_path} && sudo chef-client -c client.rb -j dna.json").once + Vagrant::SSH.expects(:execute).yields(ssh) + @action.run_chef_client + end + end +end diff --git a/test/vagrant/provisioners/chef_solo_test.rb b/test/vagrant/provisioners/chef_solo_test.rb index 9f8156cb3..41032a5c8 100644 --- a/test/vagrant/provisioners/chef_solo_test.rb +++ b/test/vagrant/provisioners/chef_solo_test.rb @@ -10,6 +10,17 @@ class ChefSoloProvisionerTest < Test::Unit::TestCase mock_config end + context "provisioning" do + should "run the proper sequence of methods in order" do + prov_seq = sequence("prov_seq") + @action.expects(:chown_provisioning_folder).once.in_sequence(prov_seq) + @action.expects(:setup_json).once.in_sequence(prov_seq) + @action.expects(:setup_solo_config).once.in_sequence(prov_seq) + @action.expects(:run_chef_solo).once.in_sequence(prov_seq) + @action.provision! + end + end + context "shared folders" do should "setup shared folder on VM for the cookbooks" do File.expects(:expand_path).with(Vagrant.config.chef.cookbooks_path, Vagrant::Env.root_path).returns("foo") diff --git a/test/vagrant/provisioners/chef_test.rb b/test/vagrant/provisioners/chef_test.rb index 4c4590339..2536e319c 100644 --- a/test/vagrant/provisioners/chef_test.rb +++ b/test/vagrant/provisioners/chef_test.rb @@ -31,9 +31,11 @@ class ChefProvisionerTest < Test::Unit::TestCase end context "permissions on provisioning folder" do - should "chown the folder to the ssh user" do + should "create and chown the folder to the ssh user" do + ssh_seq = sequence("ssh_seq") ssh = mock("ssh") - ssh.expects(:exec!).with("sudo chown #{Vagrant.config.ssh.username} #{Vagrant.config.chef.provisioning_path}") + ssh.expects(:exec!).with("sudo mkdir -p #{Vagrant.config.chef.provisioning_path}").once.in_sequence(ssh_seq) + ssh.expects(:exec!).with("sudo chown #{Vagrant.config.ssh.username} #{Vagrant.config.chef.provisioning_path}").once.in_sequence(ssh_seq) Vagrant::SSH.expects(:execute).yields(ssh) @action.chown_provisioning_folder end From a4428cd201e0cd84f5fd6bb848a744cc73552c9d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 21:58:51 -0800 Subject: [PATCH 10/16] Added :chef_server provisioner shortcut --- lib/vagrant/actions/vm/provision.rb | 3 ++- test/vagrant/actions/vm/provision_test.rb | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/actions/vm/provision.rb b/lib/vagrant/actions/vm/provision.rb index d2e07c8a1..656da4c18 100644 --- a/lib/vagrant/actions/vm/provision.rb +++ b/lib/vagrant/actions/vm/provision.rb @@ -24,7 +24,8 @@ module Vagrant elsif provisioner.is_a?(Symbol) # We have a few hard coded provisioners for built-ins mapping = { - :chef_solo => Provisioners::ChefSolo + :chef_solo => Provisioners::ChefSolo, + :chef_server => Provisioners::ChefServer } provisioner_klass = mapping[provisioner] diff --git a/test/vagrant/actions/vm/provision_test.rb b/test/vagrant/actions/vm/provision_test.rb index 0cd863b18..cc3448f8a 100644 --- a/test/vagrant/actions/vm/provision_test.rb +++ b/test/vagrant/actions/vm/provision_test.rb @@ -97,6 +97,10 @@ class ProvisionActionTest < Test::Unit::TestCase provisioner_expectation(:chef_solo, Vagrant::Provisioners::ChefSolo) end + should "set :chef_server to the ChefServer provisioner" do + provisioner_expectation(:chef_server, Vagrant::Provisioners::ChefServer) + end + should "call prepare on the instance" do instance = mock("instance") instance.expects(:prepare).once From 9384917b81db6a05ef400beb8fc4d27bd9d2b141 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 22:42:42 -0800 Subject: [PATCH 11/16] Chef server provisioner works. Added the auto-creation of the client key config. --- lib/vagrant/provisioners/chef.rb | 3 ++ lib/vagrant/provisioners/chef_server.rb | 18 +++++++++-- test/test_helper.rb | 1 + test/vagrant/provisioners/chef_server_test.rb | 32 +++++++++++++++++-- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/lib/vagrant/provisioners/chef.rb b/lib/vagrant/provisioners/chef.rb index f9f195f39..10c3d5213 100644 --- a/lib/vagrant/provisioners/chef.rb +++ b/lib/vagrant/provisioners/chef.rb @@ -10,6 +10,7 @@ module Vagrant attr_accessor :chef_server_url attr_accessor :validation_key_path attr_accessor :validation_client_name + attr_accessor :client_key_path # Chef solo specific config attr_accessor :cookbooks_path @@ -20,6 +21,8 @@ module Vagrant def initialize @validation_client_name = "chef-validator" + @client_key_path = "/etc/chef/client.pem" + @cookbooks_path = "cookbooks" @provisioning_path = "/tmp/vagrant-chef" @json = { diff --git a/lib/vagrant/provisioners/chef_server.rb b/lib/vagrant/provisioners/chef_server.rb index a40d12bcb..4aa971929 100644 --- a/lib/vagrant/provisioners/chef_server.rb +++ b/lib/vagrant/provisioners/chef_server.rb @@ -23,15 +23,25 @@ msg def provision! chown_provisioning_folder + create_client_key_folder upload_validation_key setup_json setup_config run_chef_client end + def create_client_key_folder + logger.info "Creating folder to hold client key..." + path = Pathname.new(Vagrant.config.chef.client_key_path) + + SSH.execute do |ssh| + ssh.exec!("sudo mkdir -p #{path.dirname}") + end + end + def upload_validation_key logger.info "Uploading chef client validation key..." - SSH.upload!(Vagrant.config.chef.validation_key_path, guest_validation_key_path) + SSH.upload!(validation_key_path, guest_validation_key_path) end def setup_config @@ -43,7 +53,7 @@ chef_server_url "#{Vagrant.config.chef.chef_server_url}" validation_client_name "#{Vagrant.config.chef.validation_client_name}" validation_key "#{guest_validation_key_path}" -client_key "/etc/chef/client.pem" +client_key "#{Vagrant.config.chef.client_key_path}" file_store_path "/srv/chef/file_store" file_cache_path "/srv/chef/cache" @@ -68,6 +78,10 @@ solo end end + def validation_key_path + File.expand_path(Vagrant.config.chef.validation_key_path, Env.root_path) + end + def guest_validation_key_path File.join(Vagrant.config.chef.provisioning_path, "validation.pem") end diff --git a/test/test_helper.rb b/test/test_helper.rb index a29838700..17dc61e40 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -45,6 +45,7 @@ class Test::Unit::TestCase # Chef config.chef.chef_server_url = "http://localhost:4000" config.chef.validation_key_path = "validation.pem" + config.chef.client_key_path = "/zoo/foo/bar.pem" config.chef.cookbooks_path = "cookbooks" config.chef.provisioning_path = "/tmp/hobo-chef" config.chef.json = { diff --git a/test/vagrant/provisioners/chef_server_test.rb b/test/vagrant/provisioners/chef_server_test.rb index 51139bf69..8b8ddd462 100644 --- a/test/vagrant/provisioners/chef_server_test.rb +++ b/test/vagrant/provisioners/chef_server_test.rb @@ -14,6 +14,7 @@ class ChefServerProvisionerTest < Test::Unit::TestCase should "run the proper sequence of methods in order" do prov_seq = sequence("prov_seq") @action.expects(:chown_provisioning_folder).once.in_sequence(prov_seq) + @action.expects(:create_client_key_folder).once.in_sequence(prov_seq) @action.expects(:upload_validation_key).once.in_sequence(prov_seq) @action.expects(:setup_json).once.in_sequence(prov_seq) @action.expects(:setup_config).once.in_sequence(prov_seq) @@ -60,14 +61,41 @@ class ChefServerProvisionerTest < Test::Unit::TestCase end end + context "creating the client key folder" do + setup do + @raw_path = "/foo/bar/baz.pem" + mock_config do |config| + config.chef.client_key_path = @raw_path + end + + @path = Pathname.new(@raw_path) + end + + should "create the folder using the dirname of the path" do + ssh = mock("ssh") + ssh.expects(:exec!).with("sudo mkdir -p #{@path.dirname}").once + Vagrant::SSH.expects(:execute).yields(ssh) + @action.create_client_key_folder + end + end + context "uploading the validation key" do should "upload the validation key to the provisioning path" do + @action.expects(:validation_key_path).once.returns("foo") @action.expects(:guest_validation_key_path).once.returns("bar") - Vagrant::SSH.expects(:upload!).with(Vagrant.config.chef.validation_key_path, "bar").once + Vagrant::SSH.expects(:upload!).with("foo", "bar").once @action.upload_validation_key end end + context "the validation key path" do + should "expand the configured key path" do + result = mock("result") + File.expects(:expand_path).with(Vagrant.config.chef.validation_key_path, Vagrant::Env.root_path).once.returns(result) + assert_equal result, @action.validation_key_path + end + end + context "the guest validation key path" do should "be the provisioning path joined with validation.pem" do result = mock("result") @@ -90,7 +118,7 @@ chef_server_url "#{Vagrant.config.chef.chef_server_url}" validation_client_name "#{Vagrant.config.chef.validation_client_name}" validation_key "#{@action.guest_validation_key_path}" -client_key "/etc/chef/client.pem" +client_key "#{Vagrant.config.chef.client_key_path}" file_store_path "/srv/chef/file_store" file_cache_path "/srv/chef/cache" From 5f695d8f4d3d112d6c76af24d5d2b3ee10dc8c48 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 10 Mar 2010 23:06:20 -0800 Subject: [PATCH 12/16] Added file existence test for the validation key for chef server provisioning --- lib/vagrant/provisioners/chef_server.rb | 6 +++++ test/vagrant/actions/vm/provision_test.rb | 13 ++++------ test/vagrant/provisioners/chef_server_test.rb | 24 +++++++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/lib/vagrant/provisioners/chef_server.rb b/lib/vagrant/provisioners/chef_server.rb index 4aa971929..88e8a93cc 100644 --- a/lib/vagrant/provisioners/chef_server.rb +++ b/lib/vagrant/provisioners/chef_server.rb @@ -9,6 +9,12 @@ module Vagrant Chef server provisioning requires that the `config.chef.validation_key_path` configuration be set to a path on your local machine of the validation key used to register the VM with the chef server. +msg + elsif !File.file?(Vagrant.config.chef.validation_key_path) + raise Actions::ActionException.new(<<-msg) +The validation key set for `config.chef.validation_key_path` does not exist! This +file needs to exist so it can be uploaded to the virtual machine. It is +currently set to "#{Vagrant.config.chef.validation_key_path}" msg end diff --git a/test/vagrant/actions/vm/provision_test.rb b/test/vagrant/actions/vm/provision_test.rb index cc3448f8a..630296634 100644 --- a/test/vagrant/actions/vm/provision_test.rb +++ b/test/vagrant/actions/vm/provision_test.rb @@ -79,8 +79,11 @@ class ProvisionActionTest < Test::Unit::TestCase config.vm.provisioner = symbol end + instance = mock("instance") + instance.expects(:prepare).once + provisioner.expects(:new).returns(instance) assert_nothing_raised { @action.prepare } - assert @action.provisioner.is_a?(provisioner) + assert_equal instance, @action.provisioner end should "raise an ActionException if its an unknown symbol" do @@ -100,14 +103,6 @@ class ProvisionActionTest < Test::Unit::TestCase should "set :chef_server to the ChefServer provisioner" do provisioner_expectation(:chef_server, Vagrant::Provisioners::ChefServer) end - - should "call prepare on the instance" do - instance = mock("instance") - instance.expects(:prepare).once - instance.stubs(:is_a?).returns(true) - Vagrant::Provisioners::ChefSolo.expects(:new).returns(instance) - provisioner_expectation(:chef_solo, Vagrant::Provisioners::ChefSolo) - end end end end diff --git a/test/vagrant/provisioners/chef_server_test.rb b/test/vagrant/provisioners/chef_server_test.rb index 8b8ddd462..7212a3604 100644 --- a/test/vagrant/provisioners/chef_server_test.rb +++ b/test/vagrant/provisioners/chef_server_test.rb @@ -24,6 +24,10 @@ class ChefServerProvisionerTest < Test::Unit::TestCase end context "preparing" do + setup do + File.stubs(:file?).returns(true) + end + should "not raise an exception if validation_key_path is set" do mock_config do |config| config.chef.validation_key_path = "7" @@ -42,6 +46,26 @@ class ChefServerProvisionerTest < Test::Unit::TestCase } end + should "not raise an exception if validation_key_path does exist" do + mock_config do |config| + config.chef.validation_key_path = "7" + end + + File.expects(:file?).with(Vagrant.config.chef.validation_key_path).returns(true) + assert_nothing_raised { @action.prepare } + end + + should "raise an exception if validation_key_path doesn't exist" do + mock_config do |config| + config.chef.validation_key_path = "7" + end + + File.expects(:file?).with(Vagrant.config.chef.validation_key_path).returns(false) + assert_raises(Vagrant::Actions::ActionException) { + @action.prepare + } + end + should "not raise an exception if chef_server_url is set" do mock_config do |config| config.chef.chef_server_url = "7" From df2f76297ddf46315c520857ec6729b01a303823 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 11 Mar 2010 01:05:57 -0800 Subject: [PATCH 13/16] Helper methods on chef config `add_recipe` and `add_role` and `run_list` --- lib/vagrant/provisioners/chef.rb | 22 +++++++++++++++ test/vagrant/provisioners/chef_test.rb | 37 +++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/provisioners/chef.rb b/lib/vagrant/provisioners/chef.rb index 10c3d5213..b59a8f804 100644 --- a/lib/vagrant/provisioners/chef.rb +++ b/lib/vagrant/provisioners/chef.rb @@ -31,6 +31,28 @@ module Vagrant } end + # Returns the run list for the provisioning + def run_list + json[:run_list] + end + + # Sets the run list to the specified value + def run_list=(value) + json[:run_list] = value + end + + # Adds a recipe to the run list + def add_recipe(name) + name = "recipe[#{name}]" unless name =~ /^recipe\[(.+?)\]$/ + run_list << name + end + + # Adds a role to the run list + def add_role(name) + name = "role[#{name}]" unless name =~ /^role\[(.+?)\]$/ + run_list << name + end + def to_json # Overridden so that the 'json' key could be removed, since its just # merged into the config anyways diff --git a/test/vagrant/provisioners/chef_test.rb b/test/vagrant/provisioners/chef_test.rb index 2536e319c..916dabc46 100644 --- a/test/vagrant/provisioners/chef_test.rb +++ b/test/vagrant/provisioners/chef_test.rb @@ -21,13 +21,48 @@ class ChefProvisionerTest < Test::Unit::TestCase context "config" do setup do @config = Vagrant::Provisioners::Chef::ChefConfig.new - @config.json = "HEY" + @config.run_list.clear end should "not include the 'json' key in the config dump" do result = JSON.parse(@config.to_json) assert !result.has_key?("json") end + + should "provide accessors to the run list" do + @config.run_list << "foo" + assert !@config.run_list.empty? + assert_equal ["foo"], @config.run_list + end + + should "provide a writer for the run list" do + data = mock("data") + + assert_nothing_raised { + @config.run_list = data + assert_equal data, @config.run_list + } + end + + should "add a recipe to the run list" do + @config.add_recipe("foo") + assert_equal "recipe[foo]", @config.run_list[0] + end + + should "not wrap the recipe in 'recipe[]' if it was in the name" do + @config.add_recipe("recipe[foo]") + assert_equal "recipe[foo]", @config.run_list[0] + end + + should "add a role to the run list" do + @config.add_role("user") + assert_equal "role[user]", @config.run_list[0] + end + + should "not wrap the role in 'role[]' if it was in the name" do + @config.add_role("role[user]") + assert_equal "role[user]", @config.run_list[0] + end end context "permissions on provisioning folder" do From f3d735fae162359f15c85fd812523721fa748d25 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 11 Mar 2010 15:56:44 -0800 Subject: [PATCH 14/16] Up the default timeout, which allows the SSH connection to work even without an internet connection --- config/default.rb | 2 +- script/vagrant-ssh-expect.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/default.rb b/config/default.rb index f107499c8..dc24ca140 100644 --- a/config/default.rb +++ b/config/default.rb @@ -9,7 +9,7 @@ Vagrant::Config.run do |config| config.ssh.host = "localhost" config.ssh.forwarded_port_key = "ssh" config.ssh.max_tries = 10 - config.ssh.timeout = 10 + config.ssh.timeout = 30 config.vm.box_ovf = "box.ovf" config.vm.base_mac = "0800279C2E42" diff --git a/script/vagrant-ssh-expect.sh b/script/vagrant-ssh-expect.sh index f27aeebeb..280bd8efc 100755 --- a/script/vagrant-ssh-expect.sh +++ b/script/vagrant-ssh-expect.sh @@ -4,6 +4,7 @@ set uname [lrange $argv 0 0] set password [lrange $argv 1 1] set host [lrange $argv 2 2] set port [lrange $argv 3 3] +set timeout 30 if { $port != "" } { set port_option "-p $port" From 3a4881bf2eb6173bc42e95797320d44a7379524b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 11 Mar 2010 21:59:44 -0800 Subject: [PATCH 15/16] Chef solo cookbooks paths can now take arrays of folders, in addition to just a single string --- lib/vagrant/provisioners/chef_solo.rb | 31 ++++++- test/vagrant/provisioners/chef_solo_test.rb | 94 ++++++++++++++++++--- 2 files changed, 112 insertions(+), 13 deletions(-) diff --git a/lib/vagrant/provisioners/chef_solo.rb b/lib/vagrant/provisioners/chef_solo.rb index c1163569a..df68c6a4c 100644 --- a/lib/vagrant/provisioners/chef_solo.rb +++ b/lib/vagrant/provisioners/chef_solo.rb @@ -3,7 +3,7 @@ module Vagrant # This class implements provisioning via chef-solo. class ChefSolo < Chef def prepare - Vagrant.config.vm.share_folder("vagrant-chef-solo", cookbooks_path, File.expand_path(Vagrant.config.chef.cookbooks_path, Env.root_path)) + share_cookbook_folders end def provision! @@ -13,10 +13,16 @@ module Vagrant run_chef_solo end + def share_cookbook_folders + host_cookbook_paths.each_with_index do |cookbook, i| + Vagrant.config.vm.share_folder("vagrant-chef-solo-#{i}", cookbook_path(i), cookbook) + end + end + def setup_solo_config solo_file = <<-solo file_cache_path "#{Vagrant.config.chef.provisioning_path}" -cookbook_path "#{cookbooks_path}" +cookbook_path #{cookbooks_path} solo logger.info "Uploading chef-solo configuration script..." @@ -34,8 +40,27 @@ solo end end + def host_cookbook_paths + cookbooks = Vagrant.config.chef.cookbooks_path + cookbooks = [cookbooks] unless cookbooks.is_a?(Array) + cookbooks.collect! { |cookbook| File.expand_path(cookbook, Env.root_path) } + return cookbooks + end + + def cookbook_path(i) + File.join(Vagrant.config.chef.provisioning_path, "cookbooks-#{i}") + end + def cookbooks_path - File.join(Vagrant.config.chef.provisioning_path, "cookbooks") + result = [] + host_cookbook_paths.each_with_index do |host_path, i| + result << cookbook_path(i) + end + + # We're lucky that ruby's string and array syntax for strings is the + # same as JSON, so we can just convert to JSON here and use that + result = result.to_s if result.length == 1 + result.to_json end end end diff --git a/test/vagrant/provisioners/chef_solo_test.rb b/test/vagrant/provisioners/chef_solo_test.rb index 41032a5c8..9859ca771 100644 --- a/test/vagrant/provisioners/chef_solo_test.rb +++ b/test/vagrant/provisioners/chef_solo_test.rb @@ -10,6 +10,13 @@ class ChefSoloProvisionerTest < Test::Unit::TestCase mock_config end + context "preparing" do + should "share cookbook folders" do + @action.expects(:share_cookbook_folders).once + @action.prepare + end + end + context "provisioning" do should "run the proper sequence of methods in order" do prov_seq = sequence("prov_seq") @@ -21,19 +28,86 @@ class ChefSoloProvisionerTest < Test::Unit::TestCase end end - context "shared folders" do - should "setup shared folder on VM for the cookbooks" do - 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 + context "sharing cookbook folders" do + setup do + @host_cookbook_paths = ["foo", "bar"] + @action.stubs(:host_cookbook_paths).returns(@host_cookbook_paths) + end + + should "share each cookbook folder" do + share_seq = sequence("share_seq") + @host_cookbook_paths.each_with_index do |cookbook, i| + Vagrant.config.vm.expects(:share_folder).with("vagrant-chef-solo-#{i}", @action.cookbook_path(i), cookbook).in_sequence(share_seq) + end + + @action.share_cookbook_folders + end + end + + context "host cookbooks paths" do + should "expand the path of the cookbooks relative to the environment root path" do + @cookbook = "foo" + @expanded = "bar" + File.expects(:expand_path).with(@cookbook, Vagrant::Env.root_path).returns(@expanded) + + mock_config do |config| + config.chef.cookbooks_path = @cookbook + end + + assert_equal [@expanded], @action.host_cookbook_paths + end + + should "return as an array if was originally a string" do + File.stubs(:expand_path).returns("foo") + + mock_config do |config| + config.chef.cookbooks_path = "foo" + end + + assert_equal ["foo"], @action.host_cookbook_paths + end + + should "return the array of cookbooks if its an array" do + cookbooks = ["foo", "bar"] + mock_config do |config| + config.chef.cookbooks_path = cookbooks + end + + expand_seq = sequence('expand_seq') + cookbooks.each do |cookbook| + File.expects(:expand_path).with(cookbook, Vagrant::Env.root_path).returns(cookbook) + end + + assert_equal cookbooks, @action.host_cookbook_paths end end context "cookbooks path" do - should "return the proper cookbook path" do - cookbooks_path = File.join(Vagrant.config.chef.provisioning_path, "cookbooks") - assert_equal cookbooks_path, @action.cookbooks_path + should "return a proper path to a single cookbook" do + expected = File.join(Vagrant.config.chef.provisioning_path, "cookbooks-5") + assert_equal expected, @action.cookbook_path(5) + end + + should "return array-representation of cookbook paths if multiple" do + @cookbooks = (0..5).inject([]) do |acc, i| + acc << @action.cookbook_path(i) + end + + mock_config do |config| + config.chef.cookbooks_path = @cookbooks + end + + assert_equal @cookbooks.to_json, @action.cookbooks_path + end + + should "return a single string representation if cookbook paths is single" do + @cookbooks = @action.cookbook_path(0) + + mock_config do |config| + config.chef.cookbooks_path = @cookbooks + end + + assert_equal @cookbooks.to_json, @action.cookbooks_path end end @@ -41,7 +115,7 @@ class ChefSoloProvisionerTest < Test::Unit::TestCase should "upload properly generate the configuration file using configuration data" do expected_config = <<-config file_cache_path "#{Vagrant.config.chef.provisioning_path}" -cookbook_path "#{@action.cookbooks_path}" +cookbook_path #{@action.cookbooks_path} config StringIO.expects(:new).with(expected_config).once From b86ef6aab9185632bde1c5177993b299e9db05b1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 11 Mar 2010 22:44:50 -0800 Subject: [PATCH 16/16] Silly typo fix --- lib/vagrant/actions/vm/provision.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant/actions/vm/provision.rb b/lib/vagrant/actions/vm/provision.rb index 656da4c18..14b765614 100644 --- a/lib/vagrant/actions/vm/provision.rb +++ b/lib/vagrant/actions/vm/provision.rb @@ -39,7 +39,7 @@ module Vagrant def execute! if provisioner - logger.info "Beginning provisining process..." + logger.info "Beginning provisioning process..." provisioner.provision! end end