diff --git a/lib/vagrant/action/vm/provision.rb b/lib/vagrant/action/vm/provision.rb index 9a370f766..3fdc5afc3 100644 --- a/lib/vagrant/action/vm/provision.rb +++ b/lib/vagrant/action/vm/provision.rb @@ -33,7 +33,8 @@ module Vagrant # We have a few hard coded provisioners for built-ins mapping = { :chef_solo => Provisioners::ChefSolo, - :chef_server => Provisioners::ChefServer + :chef_server => Provisioners::ChefServer, + :puppet => Provisioners::Puppet } provisioner_klass = mapping[provisioner] diff --git a/lib/vagrant/provisioners/puppet.rb b/lib/vagrant/provisioners/puppet.rb new file mode 100644 index 000000000..df92ebc88 --- /dev/null +++ b/lib/vagrant/provisioners/puppet.rb @@ -0,0 +1,83 @@ +module Vagrant + module Provisioners + + class PuppetError < Vagrant::Errors::VagrantError + error_namespace("vagrant.provisioners.puppet") + end + + class PuppetConfig < Vagrant::Config::Base + configures :puppet + + attr_accessor :manifest_file + attr_accessor :manifests_path + attr_accessor :pp_path + + def initialize + @manifest_file = "" + @manifests_path = "manifests" + @pp_path = "/tmp/vagrant-puppet" + end + end + + class Puppet < Base + + def prepare + check_manifest_dir + share_manifests + end + + def provision! + verify_binary("puppet") + create_pp_path + set_manifest + run_puppet_client + end + + def check_manifest_dir + Dir.mkdir(env.config.puppet.manifests_path) unless File.directory?(env.config.puppet.manifests_path) + end + + def share_manifests + env.config.vm.share_folder("manifests", env.config.puppet.pp_path, env.config.puppet.manifests_path) + end + + def verify_binary(binary) + vm.ssh.execute do |ssh| + ssh.exec!("which #{binary}", :error_class => PuppetError, :_key => :puppet_not_detected, :binary => binary) + end + end + + def create_pp_path + vm.ssh.execute do |ssh| + ssh.exec!("sudo mkdir -p #{env.config.puppet.pp_path}") + ssh.exec!("sudo chown #{env.config.ssh.username} #{env.config.puppet.pp_path}") + end + end + + def set_manifest + @manifest = !env.config.puppet.manifest_file.empty? ? env.config.puppet.manifest_file : "#{env.config.vm.box}.pp" + + if File.exists?("#{env.config.puppet.manifests_path}/#{@manifest}") + env.ui.info I18n.t("vagrant.provisioners.puppet.manifest_to_run", :manifest => @manifest) + return @manifest + else + raise PuppetError.new(:_key => :manifest_missing, :manifest => @manifest) + end + end + + def run_puppet_client + command = "cd #{env.config.puppet.pp_path} && sudo -E puppet #{@manifest}" + + env.ui.info I18n.t("vagrant.provisioners.puppet.running_puppet") + + vm.ssh.execute do |ssh| + ssh.exec!(command) do |channel, type, data| + ssh.check_exit_status(data, command) if type == :exit_status + env.ui.info("#{data}: #{type}") if type != :exit_status + end + end + end + + end + end +end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index a30ac64f1..ff3286e77 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -13,6 +13,9 @@ en: The chef (either `chef-solo` or `chef-client`) binary was not found on the VM and is required for chef provisioning. Please verify that chef is installed and that the binary is available on the PATH. + puppet_not_detected: |- + The `puppet` binary was not found on the VM and is required for Puppet provisioning. + Please verify that Puppet is installed and that the binary is available on the PATH. cli_missing_env: This command requires that a Vagrant environment be properly passed in as the last parameter. config_validation: |- There was a problem with the configuration of Vagrant. The error message(s) @@ -416,6 +419,16 @@ en: 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. + puppet: + not_detected: |- + The `%{binary}` binary appears to not be in the PATH of the guest. This + could be because the PATH is not properly setup or perhaps Puppet is not + installed on this guest. Puppet provisioning can not continue without + Puppet properly installed. + running_puppet: "Running Puppet..." + manifest_to_run: "Puppet will use the %{manifest} manifest to configure your box." + manifest_missing: "The Puppet %{manifest} manifest is missing. You cannot configure this box." + systems: linux: attempting_halt: "Attempting graceful shutdown of linux..." diff --git a/test/vagrant/action/vm/provision_test.rb b/test/vagrant/action/vm/provision_test.rb index 4607fa70d..45e4f43b0 100644 --- a/test/vagrant/action/vm/provision_test.rb +++ b/test/vagrant/action/vm/provision_test.rb @@ -103,6 +103,10 @@ class ProvisionVMActionTest < Test::Unit::TestCase should "set :chef_server to the ChefServer provisioner" do provisioner_expectation(:chef_server, Vagrant::Provisioners::ChefServer) end + + should "set :puppet to the Puppet provisioner" do + provisioner_expectation(:puppet, Vagrant::Provisioners::Puppet) + end end end diff --git a/test/vagrant/provisioners/puppet_test.rb b/test/vagrant/provisioners/puppet_test.rb new file mode 100644 index 000000000..42dbdae6a --- /dev/null +++ b/test/vagrant/provisioners/puppet_test.rb @@ -0,0 +1,123 @@ +require "test_helper" + +class PuppetProvisionerTest < Test::Unit::TestCase + setup do + @action_env = Vagrant::Action::Environment.new(vagrant_env.vms[:default].env) + + @action = Vagrant::Provisioners::Puppet.new(@action_env) + @env = @action.env + @vm = @action.vm + end + + context "preparing" do + should "share manifests" do + @action.expects(:check_manifest_dir).once + @action.expects(:share_manifests).once + @action.prepare + end + end + + context "provisioning" do + should "run the proper sequence of methods in order" do + prov_seq = sequence("prov_seq") + @action.expects(:verify_binary).with("puppet").once.in_sequence(prov_seq) + @action.expects(:create_pp_path).once.in_sequence(prov_seq) + @action.expects(:set_manifest).once.in_sequence(prov_seq) + @action.expects(:run_puppet_client).once.in_sequence(prov_seq) + @action.provision! + end + end + + context "check manifest_dir" do + setup do + @env.config.puppet.manifests_path = "manifests" + end + + should "should not create the manifest directory if it exists" do + File.expects(:directory?).with(@env.config.puppet.manifests_path).returns(true) + @action.check_manifest_dir + end + + should "create the manifest directory if it does not exist" do + File.stubs(:directory?).with(@env.config.puppet.manifests_path).returns(false) + Dir.expects(:mkdir).with(@env.config.puppet.manifests_path).once + @action.check_manifest_dir + end + end + + context "share manifests folder" do + setup do + @manifests_path = "manifests" + @pp_path = "/tmp/vagrant-puppet" + @action.stubs(:manifests_path).returns(@manifests_path) + @action.stubs(:pp_path).returns(@pp_path) + end + + should "share manifest folder" do + @env.config.vm.expects(:share_folder).with("manifests", @pp_path, @manifests_path) + @action.share_manifests + end + end + + context "verifying binary" do + setup do + @ssh = mock("ssh") + @vm.ssh.stubs(:execute).yields(@ssh) + end + + should "verify binary exists" do + binary = "foo" + @ssh.expects(:exec!).with("which #{binary}", anything) + @action.verify_binary(binary) + end + end + + context "create pp path" 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 mkdir -p #{@env.config.puppet.pp_path}").once.in_sequence(ssh_seq) + ssh.expects(:exec!).with("sudo chown #{@env.config.ssh.username} #{@env.config.puppet.pp_path}").once.in_sequence(ssh_seq) + @vm.ssh.expects(:execute).yields(ssh) + @action.create_pp_path + end + end + + context "setting the manifest" do + setup do + @env.config.puppet.stubs(:manifests_path).returns("manifests") + @env.config.puppet.stubs(:manifest_file).returns("foo.pp") + @env.config.vm.stubs(:box).returns("base") + end + + should "set the manifest if it exists" do + File.stubs(:exists?).with("#{@env.config.puppet.manifests_path}/#{@env.config.puppet.manifest_file}").returns(true) + @action.set_manifest + end + + should "raise an error if the manifest does not exist" do + File.stubs(:exists?).with("#{@env.config.puppet.manifests_path}/#{@env.config.puppet.manifest_file}").returns(false) + assert_raises(Vagrant::Provisioners::PuppetError) { + @action.set_manifest + } + end + end + + context "running puppet client" do + setup do + @ssh = mock("ssh") + @vm.ssh.stubs(:execute).yields(@ssh) + end + + should "cd into the pp_path directory and run puppet" do + @ssh.expects(:exec!).with("cd #{@env.config.puppet.pp_path} && sudo -E puppet #{@manifest}").once + @action.run_puppet_client + end + + should "check the exit status if that is given" do + @ssh.stubs(:exec!).yields(nil, :exit_status, :foo) + @ssh.expects(:check_exit_status).with(:foo, anything).once + @action.run_puppet_client + end + end +end