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