Pull out common chef code into its own abstract provisioner

This commit is contained in:
Mitchell Hashimoto 2010-03-10 19:00:49 -08:00
parent f6bc0ff5fc
commit df648803dd
6 changed files with 162 additions and 130 deletions

View File

@ -5,7 +5,7 @@ PROJECT_ROOT = File.join(libdir, '..') unless defined?(PROJECT_ROOT)
# The libs which must be loaded prior to the rest # 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 %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 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 require f
end end

View File

@ -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

View File

@ -1,36 +1,9 @@
module Vagrant module Vagrant
module Provisioners module Provisioners
# This class implements provisioning via chef-solo. # This class implements provisioning via chef-solo.
class ChefSolo < Base class ChefSolo < Chef
# 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
def prepare 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 end
def provision! def provision!
@ -40,48 +13,20 @@ module Vagrant
run_chef_solo run_chef_solo
end 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 def setup_solo_config
solo_file = <<-solo solo_file = <<-solo
file_cache_path "#{Vagrant.config.chef_solo.provisioning_path}" file_cache_path "#{Vagrant.config.chef.provisioning_path}"
cookbook_path "#{cookbooks_path}" cookbook_path "#{cookbooks_path}"
solo solo
logger.info "Uploading chef-solo configuration script..." 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 end
def run_chef_solo def run_chef_solo
logger.info "Running chef-solo..." logger.info "Running chef-solo..."
SSH.execute do |ssh| 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 # 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 # an error, or when verbosity level is set high
logger.info("#{stream}: #{data}") logger.info("#{stream}: #{data}")
@ -90,7 +35,7 @@ solo
end end
def cookbooks_path def cookbooks_path
File.join(Vagrant.config.chef_solo.provisioning_path, "cookbooks") File.join(Vagrant.config.chef.provisioning_path, "cookbooks")
end end
end end
end end

View File

@ -42,10 +42,10 @@ class Test::Unit::TestCase
config.package.name = 'vagrant' config.package.name = 'vagrant'
config.package.extension = '.box' config.package.extension = '.box'
# Chef solo # Chef
config.chef_solo.cookbooks_path = "cookbooks" config.chef.cookbooks_path = "cookbooks"
config.chef_solo.provisioning_path = "/tmp/hobo-chef" config.chef.provisioning_path = "/tmp/hobo-chef"
config.chef_solo.json = { config.chef.json = {
:recipes => ["hobo_main"] :recipes => ["hobo_main"]
} }

View File

@ -10,21 +10,9 @@ class ChefSoloProvisionerTest < Test::Unit::TestCase
mock_config mock_config
end 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 context "shared folders" do
should "setup shared folder on VM for the cookbooks" 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") @action.expects(:cookbooks_path).returns("bar")
Vagrant.config.vm.expects(:share_folder).with("vagrant-chef-solo", "bar", "foo").once Vagrant.config.vm.expects(:share_folder).with("vagrant-chef-solo", "bar", "foo").once
@action.prepare @action.prepare
@ -33,62 +21,15 @@ class ChefSoloProvisionerTest < Test::Unit::TestCase
context "cookbooks path" do context "cookbooks path" do
should "return the proper cookbook 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 assert_equal cookbooks_path, @action.cookbooks_path
end end
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 context "generating and uploading chef solo configuration file" do
should "upload properly generate the configuration file using configuration data" do should "upload properly generate the configuration file using configuration data" do
expected_config = <<-config 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}" cookbook_path "#{@action.cookbooks_path}"
config config
@ -99,7 +40,7 @@ config
should "upload this file as solo.rb to the provisioning folder" do should "upload this file as solo.rb to the provisioning folder" do
@action.expects(:cookbooks_path).returns("cookbooks") @action.expects(:cookbooks_path).returns("cookbooks")
StringIO.expects(:new).returns("foo") 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 Vagrant::SSH.expects(:upload!).with("foo", "bar").once
@action.setup_solo_config @action.setup_solo_config
end end
@ -108,7 +49,7 @@ config
context "running chef solo" do context "running chef solo" do
should "cd into the provisioning directory and run chef solo" do should "cd into the provisioning directory and run chef solo" do
ssh = mock("ssh") 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) Vagrant::SSH.expects(:execute).yields(ssh)
@action.run_chef_solo @action.run_chef_solo
end end

View File

@ -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