Move provisioners into plugins

This commit is contained in:
Mitchell Hashimoto 2012-04-18 21:53:19 -07:00
parent 0d7b9f84e2
commit 1cbac3167f
19 changed files with 908 additions and 811 deletions

View File

@ -124,14 +124,6 @@ module Vagrant
def self.hosts
@hosts ||= Registry.new
end
# Global registry of provisioners.
#
# This registry is used to look up the provisioners provided for
# `config.vm.provision`.
def self.provisioners
@provisioners ||= Registry.new
end
end
# # Default I18n to load the en locale
@ -175,10 +167,3 @@ Vagrant.hosts.register(:freebsd) { Vagrant::Hosts::FreeBSD }
Vagrant.hosts.register(:gentoo) { Vagrant::Hosts::Gentoo }
Vagrant.hosts.register(:linux) { Vagrant::Hosts::Linux }
Vagrant.hosts.register(:windows) { Vagrant::Hosts::Windows }
# Register the built-in provisioners
Vagrant.provisioners.register(:chef_solo) { Vagrant::Provisioners::ChefSolo }
Vagrant.provisioners.register(:chef_client) { Vagrant::Provisioners::ChefClient }
Vagrant.provisioners.register(:puppet) { Vagrant::Provisioners::Puppet }
Vagrant.provisioners.register(:puppet_server) { Vagrant::Provisioners::PuppetServer }
Vagrant.provisioners.register(:shell) { Vagrant::Provisioners::Shell }

View File

@ -72,6 +72,19 @@ module Vagrant
data[:guests]
end
# Registers additional provisioners to be available.
#
# @param [String] name Name of the provisioner.
def self.provisioner(name=UNSET_VALUE, &block)
data[:provisioners] ||= Registry.new
# Register a new provisioner class only if a name was given
data[:provisioners].register(name.to_sym, &block) if name != UNSET_VALUE
# Return the registry
data[:provisioners]
end
# Registers the plugin. This makes the plugin actually work with
# Vagrant. Prior to registering, the plugin is merely a skeleton.
def self.register!(plugin=nil)

View File

@ -1,168 +0,0 @@
require 'tempfile'
module Vagrant
module Provisioners
# This class is a base class where the common functionality 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
include Util::Counter
def initialize(env, config)
super
config.provisioning_path ||= "/tmp/vagrant-chef-#{get_and_update_counter(:provisioning_path)}"
end
def prepare
raise ChefError, :invalid_provisioner
end
def verify_binary(binary)
# Checks for the existence of chef binary and error if it
# doesn't exist.
env[:vm].channel.sudo("which #{binary}",
:error_class => ChefError,
:error_key => :chef_not_detected,
:binary => binary)
end
# Returns the path to the Chef binary, taking into account the
# `binary_path` configuration option.
def chef_binary_path(binary)
return binary if !config.binary_path
return File.join(config.binary_path, binary)
end
def chown_provisioning_folder
env[:vm].channel.sudo("mkdir -p #{config.provisioning_path}")
env[:vm].channel.sudo("chown #{env[:vm].config.ssh.username} #{config.provisioning_path}")
end
def setup_config(template, filename, template_vars)
config_file = TemplateRenderer.render(template, {
:log_level => config.log_level.to_sym,
:http_proxy => config.http_proxy,
:http_proxy_user => config.http_proxy_user,
:http_proxy_pass => config.http_proxy_pass,
:https_proxy => config.https_proxy,
:https_proxy_user => config.https_proxy_user,
:https_proxy_pass => config.https_proxy_pass,
:no_proxy => config.no_proxy
}.merge(template_vars))
# Create a temporary file to store the data so we
# can upload it
temp = Tempfile.new("vagrant")
temp.write(config_file)
temp.close
remote_file = File.join(config.provisioning_path, filename)
env[:vm].channel.sudo("rm #{remote_file}", :error_check => false)
env[:vm].channel.upload(temp.path, remote_file)
end
def setup_json
env[:ui].info I18n.t("vagrant.provisioners.chef.json")
# Set up our configuration that is passed to the attributes by default
data = { :config => env[:global_config].to_hash }
# Add our default share directory if it exists
default_share = env[:vm].config.vm.shared_folders["v-root"]
data[:directory] = default_share[:guestpath] if default_share
# 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!(config.merged_json)
json = data.to_json
# Create a temporary file to store the data so we
# can upload it
temp = Tempfile.new("vagrant")
temp.write(json)
temp.close
env[:vm].channel.upload(temp.path, File.join(config.provisioning_path, "dna.json"))
end
end
class Chef < Base
class ChefError < Errors::VagrantError
error_namespace("vagrant.provisioners.chef")
end
end
class Chef < Base
# This is the configuration which is available through `config.chef`
class Config < Vagrant::Config::Base
# Shared config
attr_accessor :node_name
attr_accessor :provisioning_path
attr_accessor :log_level
attr_accessor :json
attr_accessor :http_proxy
attr_accessor :http_proxy_user
attr_accessor :http_proxy_pass
attr_accessor :https_proxy
attr_accessor :https_proxy_user
attr_accessor :https_proxy_pass
attr_accessor :no_proxy
attr_accessor :binary_path
attr_accessor :binary_env
attr_accessor :attempts
attr_writer :run_list
# Provide defaults in such a way that they won't override the instance
# variable. This is so merging continues to work properly.
def attempts; @attempts || 1; end
def json; @json ||= {}; end
def log_level; @log_level || :info; end
# This returns the json that is merged with the defaults and the
# user set data.
def merged_json
original = { :instance_role => "vagrant" }
original[:run_list] = @run_list if @run_list
original.merge(json || {})
end
# Returns the run list, but also sets it up to be empty if it
# hasn't been defined already.
def run_list
@run_list ||= []
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 validate(env, errors)
super
errors.add(I18n.t("vagrant.config.chef.vagrant_as_json_key")) if json.has_key?(:vagrant)
end
def instance_variables_hash
# Overridden so that the 'json' key could be removed, since its just
# merged into the config anyways
result = super
result.delete("json")
result
end
end
end
end
end

View File

@ -1,132 +0,0 @@
require 'pathname'
require 'vagrant/provisioners/chef'
module Vagrant
module Provisioners
# This class implements provisioning via chef-client, allowing provisioning
# with a chef server.
class ChefClient < Chef
class Config < Chef::Config
attr_accessor :chef_server_url
attr_accessor :validation_key_path
attr_accessor :validation_client_name
attr_accessor :client_key_path
attr_accessor :file_cache_path
attr_accessor :file_backup_path
attr_accessor :environment
attr_accessor :encrypted_data_bag_secret_key_path
attr_accessor :encrypted_data_bag_secret
# Provide defaults in such a way that they won't override the instance
# variable. This is so merging continues to work properly.
def validation_client_name; @validation_client_name || "chef-validator"; end
def client_key_path; @client_key_path || "/etc/chef/client.pem"; end
def file_cache_path; @file_cache_path || "/srv/chef/file_store"; end
def file_backup_path; @file_backup_path || "/srv/chef/cache"; end
def encrypted_data_bag_secret; @encrypted_data_bag_secret || "/tmp/encrypted_data_bag_secret"; end
def validate(env, errors)
super
errors.add(I18n.t("vagrant.config.chef.server_url_empty")) if !chef_server_url || chef_server_url.strip == ""
errors.add(I18n.t("vagrant.config.chef.validation_key_path")) if !validation_key_path
errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if @run_list && @run_list.empty?
end
end
def self.config_class
Config
end
def prepare
raise ChefError, :server_validation_key_required if config.validation_key_path.nil?
raise ChefError, :server_validation_key_doesnt_exist if !File.file?(validation_key_path)
raise ChefError, :server_url_required if config.chef_server_url.nil?
end
def provision!
verify_binary(chef_binary_path("chef-client"))
chown_provisioning_folder
create_client_key_folder
upload_validation_key
upload_encrypted_data_bag_secret if config.encrypted_data_bag_secret_key_path
setup_json
setup_server_config
run_chef_client
end
def create_client_key_folder
env[:ui].info I18n.t("vagrant.provisioners.chef.client_key_folder")
path = Pathname.new(config.client_key_path)
env[:vm].channel.sudo("mkdir -p #{path.dirname}")
end
def upload_validation_key
env[:ui].info I18n.t("vagrant.provisioners.chef.upload_validation_key")
env[:vm].channel.upload(validation_key_path, guest_validation_key_path)
end
def upload_encrypted_data_bag_secret
env[:ui].info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key")
env[:vm].channel.upload(encrypted_data_bag_secret_key_path,
config.encrypted_data_bag_secret)
end
def setup_server_config
setup_config("provisioners/chef_client/client", "client.rb", {
:node_name => config.node_name,
:chef_server_url => config.chef_server_url,
:validation_client_name => config.validation_client_name,
:validation_key => guest_validation_key_path,
:client_key => config.client_key_path,
:file_cache_path => config.file_cache_path,
:file_backup_path => config.file_backup_path,
:environment => config.environment,
:encrypted_data_bag_secret => config.encrypted_data_bag_secret
})
end
def run_chef_client
command_env = config.binary_env ? "#{config.binary_env} " : ""
command = "#{command_env}#{chef_binary_path("chef-client")} -c #{config.provisioning_path}/client.rb -j #{config.provisioning_path}/dna.json"
config.attempts.times do |attempt|
if attempt == 0
env[:ui].info I18n.t("vagrant.provisioners.chef.running_client")
else
env[:ui].info I18n.t("vagrant.provisioners.chef.running_client_again")
end
exit_status = env[:vm].channel.sudo(command) do |type, data|
# Output the data with the proper color based on the stream.
color = type == :stdout ? :green : :red
# Note: Be sure to chomp the data to avoid the newlines that the
# Chef outputs.
env[:ui].info(data.chomp, :color => color, :prefix => false)
end
# There is no need to run Chef again if it converges
return if exit_status == 0
end
# If we reached this point then Chef never converged! Error.
raise ChefError, :no_convergence
end
def validation_key_path
File.expand_path(config.validation_key_path, env[:root_path])
end
def encrypted_data_bag_secret_key_path
File.expand_path(config.encrypted_data_bag_secret_key_path, env[:root_path])
end
def guest_validation_key_path
File.join(config.provisioning_path, "validation.pem")
end
end
end
end

View File

@ -1,234 +0,0 @@
require "log4r"
require 'vagrant/provisioners/chef'
module Vagrant
module Provisioners
# This class implements provisioning via chef-solo.
class ChefSolo < Chef
extend Util::Counter
include Util::Counter
class Config < Chef::Config
attr_accessor :cookbooks_path
attr_accessor :roles_path
attr_accessor :data_bags_path
attr_accessor :recipe_url
attr_accessor :nfs
attr_accessor :encrypted_data_bag_secret_key_path
attr_accessor :encrypted_data_bag_secret
def encrypted_data_bag_secret; @encrypted_data_bag_secret || "/tmp/encrypted_data_bag_secret"; end
def initialize
super
@__default = ["cookbooks", [:vm, "cookbooks"]]
end
# Provide defaults in such a way that they won't override the instance
# variable. This is so merging continues to work properly.
def cookbooks_path
@cookbooks_path || _default_cookbook_path
end
# This stores a reference to the default cookbook path which is used
# later. Do not use this publicly. I apologize for not making it
# "protected" but it has to be called by Vagrant internals later.
def _default_cookbook_path
@__default
end
def nfs
@nfs || false
end
def validate(env, errors)
super
errors.add(I18n.t("vagrant.config.chef.cookbooks_path_empty")) if !cookbooks_path || [cookbooks_path].flatten.empty?
errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if !run_list || run_list.empty?
end
end
attr_reader :cookbook_folders
attr_reader :role_folders
attr_reader :data_bags_folders
def self.config_class
Config
end
def initialize(env, config)
super
@logger = Log4r::Logger.new("vagrant::provisioners::chef_solo")
end
def prepare
@cookbook_folders = expanded_folders(config.cookbooks_path, "cookbooks")
@role_folders = expanded_folders(config.roles_path, "roles")
@data_bags_folders = expanded_folders(config.data_bags_path, "data_bags")
share_folders("csc", @cookbook_folders)
share_folders("csr", @role_folders)
share_folders("csdb", @data_bags_folders)
end
def provision!
# Verify that the proper shared folders exist.
check = []
[@cookbook_folders, @role_folders, @data_bags_folders].each do |folders|
folders.each do |type, local_path, remote_path|
# We only care about checking folders that have a local path, meaning
# they were shared from the local machine, rather than assumed to
# exist on the VM.
check << remote_path if local_path
end
end
verify_shared_folders(check)
verify_binary(chef_binary_path("chef-solo"))
chown_provisioning_folder
upload_encrypted_data_bag_secret if config.encrypted_data_bag_secret_key_path
setup_json
setup_solo_config
run_chef_solo
end
# Converts paths to a list of properly expanded paths with types.
def expanded_folders(paths, appended_folder=nil)
return [] if paths.nil?
# Convert the path to an array if it is a string or just a single
# path element which contains the folder location (:host or :vm)
paths = [paths] if paths.is_a?(String) || paths.first.is_a?(Symbol)
results = []
paths.each do |path|
path = [:host, path] if !path.is_a?(Array)
type, path = path
# Create the local/remote path based on whether this is a host
# or VM path.
local_path = nil
remote_path = nil
if type == :host
# Get the expanded path that the host path points to
local_path = File.expand_path(path, env[:root_path])
# Super hacky but if we're expanded the default cookbook paths,
# and one of the host paths doesn't exist, then just ignore it,
# because that is fine.
if paths.equal?(config._default_cookbook_path) && !File.directory?(local_path)
@logger.info("'cookbooks' folder doesn't exist on defaults. Ignoring.")
next
end
# Path exists on the host, setup the remote path
remote_path = "#{config.provisioning_path}/chef-solo-#{get_and_update_counter(:cookbooks_path)}"
else
# Path already exists on the virtual machine. Expand it
# relative to where we're provisioning.
remote_path = File.expand_path(path, config.provisioning_path)
# Remove drive letter if running on a windows host. This is a bit
# of a hack but is the most portable way I can think of at the moment
# to achieve this. Otherwise, Vagrant attempts to share at some crazy
# path like /home/vagrant/c:/foo/bar
remote_path = remote_path.gsub(/^[a-zA-Z]:/, "")
end
# If we have specified a folder name to append then append it
remote_path += "/#{appended_folder}" if appended_folder
# Append the result
results << [type, local_path, remote_path]
end
results
end
# Shares the given folders with the given prefix. The folders should
# be of the structure resulting from the `expanded_folders` function.
def share_folders(prefix, folders)
folders.each do |type, local_path, remote_path|
if type == :host
env[:vm].config.vm.share_folder("v-#{prefix}-#{self.class.get_and_update_counter(:shared_folder)}",
remote_path, local_path, :nfs => config.nfs)
end
end
end
def upload_encrypted_data_bag_secret
env[:ui].info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key")
env[:vm].channel.upload(encrypted_data_bag_secret_key_path,
config.encrypted_data_bag_secret)
end
def setup_solo_config
cookbooks_path = guest_paths(@cookbook_folders)
roles_path = guest_paths(@role_folders).first
data_bags_path = guest_paths(@data_bags_folders).first
setup_config("provisioners/chef_solo/solo", "solo.rb", {
:node_name => config.node_name,
:provisioning_path => config.provisioning_path,
:cookbooks_path => cookbooks_path,
:recipe_url => config.recipe_url,
:roles_path => roles_path,
:data_bags_path => data_bags_path,
:encrypted_data_bag_secret => config.encrypted_data_bag_secret,
})
end
def run_chef_solo
command_env = config.binary_env ? "#{config.binary_env} " : ""
command = "#{command_env}#{chef_binary_path("chef-solo")} -c #{config.provisioning_path}/solo.rb -j #{config.provisioning_path}/dna.json"
config.attempts.times do |attempt|
if attempt == 0
env[:ui].info I18n.t("vagrant.provisioners.chef.running_solo")
else
env[:ui].info I18n.t("vagrant.provisioners.chef.running_solo_again")
end
exit_status = env[:vm].channel.sudo(command, :error_check => false) do |type, data|
# Output the data with the proper color based on the stream.
color = type == :stdout ? :green : :red
# Note: Be sure to chomp the data to avoid the newlines that the
# Chef outputs.
env[:ui].info(data.chomp, :color => color, :prefix => false)
end
# There is no need to run Chef again if it converges
return if exit_status == 0
end
# If we reached this point then Chef never converged! Error.
raise ChefError, :no_convergence
end
def verify_shared_folders(folders)
folders.each do |folder|
@logger.debug("Checking for shared folder: #{folder}")
if !env[:vm].channel.test("test -d #{folder}")
raise ChefError, :missing_shared_folders
end
end
end
def encrypted_data_bag_secret_key_path
File.expand_path(config.encrypted_data_bag_secret_key_path, env[:root_path])
end
protected
# Extracts only the remote paths from a list of folders
def guest_paths(folders)
folders.map { |parts| parts[2] }
end
end
end
end

View File

@ -1,175 +0,0 @@
require "log4r"
module Vagrant
module Provisioners
class PuppetError < Vagrant::Errors::VagrantError
error_namespace("vagrant.provisioners.puppet")
end
class Puppet < Base
class Config < Vagrant::Config::Base
attr_accessor :manifest_file
attr_accessor :manifests_path
attr_accessor :module_path
attr_accessor :pp_path
attr_accessor :options
attr_accessor :facter
def manifest_file; @manifest_file || "default.pp"; end
def manifests_path; @manifests_path || "manifests"; end
def pp_path; @pp_path || "/tmp/vagrant-puppet"; end
def options; @options ||= []; end
def facter; @facter ||= {}; end
# Returns the manifests path expanded relative to the root path of the
# environment.
def expanded_manifests_path(root_path)
Pathname.new(manifests_path).expand_path(root_path)
end
# Returns the module paths as an array of paths expanded relative to the
# root path.
def expanded_module_paths(root_path)
return [] if !module_path
# Get all the paths and expand them relative to the root path, returning
# the array of expanded paths
paths = module_path
paths = [paths] if !paths.is_a?(Array)
paths.map do |path|
Pathname.new(path).expand_path(root_path)
end
end
def validate(env, errors)
# Calculate the manifests and module paths based on env
this_expanded_manifests_path = expanded_manifests_path(env.root_path)
this_expanded_module_paths = expanded_module_paths(env.root_path)
# Manifests path/file validation
if !this_expanded_manifests_path.directory?
errors.add(I18n.t("vagrant.provisioners.puppet.manifests_path_missing",
:path => this_expanded_manifests_path))
else
expanded_manifest_file = this_expanded_manifests_path.join(manifest_file)
if !expanded_manifest_file.file?
errors.add(I18n.t("vagrant.provisioners.puppet.manifest_missing",
:manifest => expanded_manifest_file.to_s))
end
end
# Module paths validation
this_expanded_module_paths.each do |path|
if !path.directory?
errors.add(I18n.t("vagrant.provisioners.puppet.module_path_missing", :path => path))
end
end
end
end
def self.config_class
Config
end
def initialize(env, config)
super
@logger = Log4r::Logger.new("vagrant::provisioners::puppet")
end
def prepare
# Calculate the paths we're going to use based on the environment
@expanded_manifests_path = config.expanded_manifests_path(env[:root_path])
@expanded_module_paths = config.expanded_module_paths(env[:root_path])
@manifest_file = File.join(manifests_guest_path, config.manifest_file)
set_module_paths
share_manifests
share_module_paths
end
def provision!
# Check that the shared folders are properly shared
check = [manifests_guest_path]
@module_paths.each do |host_path, guest_path|
check << guest_path
end
verify_shared_folders(check)
# Verify Puppet is installed and run it
verify_binary("puppet")
run_puppet_client
end
def share_manifests
env[:vm].config.vm.share_folder("manifests", manifests_guest_path, @expanded_manifests_path)
end
def share_module_paths
count = 0
@module_paths.each do |from, to|
# Sorry for the cryptic key here, but VirtualBox has a strange limit on
# maximum size for it and its something small (around 10)
env[:vm].config.vm.share_folder("v-pp-m#{count}", to, from)
count += 1
end
end
def set_module_paths
@module_paths = {}
@expanded_module_paths.each_with_index do |path, i|
@module_paths[path] = File.join(config.pp_path, "modules-#{i}")
end
end
def manifests_guest_path
File.join(config.pp_path, "manifests")
end
def verify_binary(binary)
env[:vm].channel.sudo("which #{binary}",
:error_class => PuppetError,
:error_key => :not_detected,
:binary => binary)
end
def run_puppet_client
options = [config.options].flatten
options << "--modulepath '#{@module_paths.values.join(':')}'" if !@module_paths.empty?
options << @manifest_file
options = options.join(" ")
# Build up the custom facts if we have any
facter = ""
if !config.facter.empty?
facts = []
config.facter.each do |key, value|
facts << "FACTER_#{key}='#{value}'"
end
facter = "#{facts.join(" ")} "
end
command = "cd #{manifests_guest_path} && #{facter}puppet apply #{options}"
env[:ui].info I18n.t("vagrant.provisioners.puppet.running_puppet",
:manifest => @manifest_file)
env[:vm].channel.sudo(command) do |type, data|
env[:ui].info(data.chomp, :prefix => false)
end
end
def verify_shared_folders(folders)
folders.each do |folder|
@logger.debug("Checking for shared folder: #{folder}")
if !env[:vm].channel.test("test -d #{folder}")
raise PuppetError, :missing_shared_folders
end
end
end
end
end
end

View File

@ -1,78 +0,0 @@
module Vagrant
module Provisioners
class PuppetServerError < Vagrant::Errors::VagrantError
error_namespace("vagrant.provisioners.puppet_server")
end
class PuppetServer < Base
class Config < Vagrant::Config::Base
attr_accessor :puppet_server
attr_accessor :puppet_node
attr_accessor :options
attr_accessor :facter
def facter; @facter ||= {}; end
def puppet_server; @puppet_server || "puppet"; end
def options; @options ||= []; end
end
def self.config_class
Config
end
def provision!
verify_binary("puppetd")
run_puppetd_client
end
def verify_binary(binary)
env[:vm].channel.sudo("which #{binary}",
:error_class => PuppetServerError,
:error_key => :not_detected,
:binary => binary)
end
def run_puppetd_client
options = config.options
options = [options] if !options.is_a?(Array)
# Intelligently set the puppet node cert name based on certain
# external parameters.
cn = nil
if config.puppet_node
# If a node name is given, we use that directly for the certname
cn = config.puppet_node
elsif env[:vm].config.vm.host_name
# If a host name is given, we explicitly set the certname to
# nil so that the hostname becomes the cert name.
cn = nil
else
# Otherwise, we default to the name of the box.
cn = env[:vm].config.vm.box
end
# Add the certname option if there is one
options += ["--certname", cn] if cn
options = options.join(" ")
# Build up the custom facts if we have any
facter = ""
if !config.facter.empty?
facts = []
config.facter.each do |key, value|
facts << "FACTER_#{key}='#{value}'"
end
facter = "#{facts.join(" ")} "
end
command = "#{facter}puppetd #{options} --server #{config.puppet_server}"
env[:ui].info I18n.t("vagrant.provisioners.puppet_server.running_puppetd")
env[:vm].channel.sudo(command) do |type, data|
env[:ui].info(data.chomp, :prefix => false)
end
end
end
end
end

21
plugins/chef/plugin.rb Normal file
View File

@ -0,0 +1,21 @@
require "vagrant"
module VagrantPlugins
module Chef
module Provisioner
autoload :ChefSolo, File.expand_path("../provisioner/chef_solo", __FILE__)
autoload :ChefClient, File.expand_path("../provisioner/chef_client", __FILE__)
end
class Plugin < Vagrant.plugin("1")
name "chef"
description <<-DESC
Provides support for provisioning your virtual machines with
Chef via `chef-solo` or `chef-client`.
DESC
provisioner("chef_solo") { Provisioner::ChefSolo }
provisioner("chef_client") { Provisioner::ChefClient }
end
end
end

View File

@ -0,0 +1,166 @@
require 'tempfile'
module VagrantPlugins
module Chef
module Provisioner
# This class is a base class where the common functionality shared between
# chef-solo and chef-client provisioning are stored. This is **not an actual
# provisioner**. Instead, {ChefSolo} or {ChefServer} should be used.
class Base < Vagrant::Provisioners::Base
include Vagrant::Util::Counter
def initialize(env, config)
super
config.provisioning_path ||= "/tmp/vagrant-chef-#{get_and_update_counter(:provisioning_path)}"
end
def prepare
raise ChefError, :invalid_provisioner
end
def verify_binary(binary)
# Checks for the existence of chef binary and error if it
# doesn't exist.
env[:vm].channel.sudo("which #{binary}",
:error_class => ChefError,
:error_key => :chef_not_detected,
:binary => binary)
end
# Returns the path to the Chef binary, taking into account the
# `binary_path` configuration option.
def chef_binary_path(binary)
return binary if !config.binary_path
return File.join(config.binary_path, binary)
end
def chown_provisioning_folder
env[:vm].channel.sudo("mkdir -p #{config.provisioning_path}")
env[:vm].channel.sudo("chown #{env[:vm].config.ssh.username} #{config.provisioning_path}")
end
def setup_config(template, filename, template_vars)
config_file = TemplateRenderer.render(template, {
:log_level => config.log_level.to_sym,
:http_proxy => config.http_proxy,
:http_proxy_user => config.http_proxy_user,
:http_proxy_pass => config.http_proxy_pass,
:https_proxy => config.https_proxy,
:https_proxy_user => config.https_proxy_user,
:https_proxy_pass => config.https_proxy_pass,
:no_proxy => config.no_proxy
}.merge(template_vars))
# Create a temporary file to store the data so we
# can upload it
temp = Tempfile.new("vagrant")
temp.write(config_file)
temp.close
remote_file = File.join(config.provisioning_path, filename)
env[:vm].channel.sudo("rm #{remote_file}", :error_check => false)
env[:vm].channel.upload(temp.path, remote_file)
end
def setup_json
env[:ui].info I18n.t("vagrant.provisioners.chef.json")
# Set up our configuration that is passed to the attributes by default
data = { :config => env[:global_config].to_hash }
# Add our default share directory if it exists
default_share = env[:vm].config.vm.shared_folders["v-root"]
data[:directory] = default_share[:guestpath] if default_share
# 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!(config.merged_json)
json = data.to_json
# Create a temporary file to store the data so we
# can upload it
temp = Tempfile.new("vagrant")
temp.write(json)
temp.close
env[:vm].channel.upload(temp.path, File.join(config.provisioning_path, "dna.json"))
end
class ChefError < Errors::VagrantError
error_namespace("vagrant.provisioners.chef")
end
# This is the configuration which is available through `config.chef`
class Config < Vagrant::Config::Base
# Shared config
attr_accessor :node_name
attr_accessor :provisioning_path
attr_accessor :log_level
attr_accessor :json
attr_accessor :http_proxy
attr_accessor :http_proxy_user
attr_accessor :http_proxy_pass
attr_accessor :https_proxy
attr_accessor :https_proxy_user
attr_accessor :https_proxy_pass
attr_accessor :no_proxy
attr_accessor :binary_path
attr_accessor :binary_env
attr_accessor :attempts
attr_writer :run_list
# Provide defaults in such a way that they won't override the instance
# variable. This is so merging continues to work properly.
def attempts; @attempts || 1; end
def json; @json ||= {}; end
def log_level; @log_level || :info; end
# This returns the json that is merged with the defaults and the
# user set data.
def merged_json
original = { :instance_role => "vagrant" }
original[:run_list] = @run_list if @run_list
original.merge(json || {})
end
# Returns the run list, but also sets it up to be empty if it
# hasn't been defined already.
def run_list
@run_list ||= []
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 validate(env, errors)
super
errors.add(I18n.t("vagrant.config.chef.vagrant_as_json_key")) if json.has_key?(:vagrant)
end
def instance_variables_hash
# Overridden so that the 'json' key could be removed, since its just
# merged into the config anyways
result = super
result.delete("json")
result
end
end
end
end
end
end

View File

@ -0,0 +1,134 @@
require 'pathname'
require File.expand_path("../base", __FILE__)
module VagrantPlugins
module Chef
module Provisioner
# This class implements provisioning via chef-client, allowing provisioning
# with a chef server.
class ChefClient < Base
class Config < Base::Config
attr_accessor :chef_server_url
attr_accessor :validation_key_path
attr_accessor :validation_client_name
attr_accessor :client_key_path
attr_accessor :file_cache_path
attr_accessor :file_backup_path
attr_accessor :environment
attr_accessor :encrypted_data_bag_secret_key_path
attr_accessor :encrypted_data_bag_secret
# Provide defaults in such a way that they won't override the instance
# variable. This is so merging continues to work properly.
def validation_client_name; @validation_client_name || "chef-validator"; end
def client_key_path; @client_key_path || "/etc/chef/client.pem"; end
def file_cache_path; @file_cache_path || "/srv/chef/file_store"; end
def file_backup_path; @file_backup_path || "/srv/chef/cache"; end
def encrypted_data_bag_secret; @encrypted_data_bag_secret || "/tmp/encrypted_data_bag_secret"; end
def validate(env, errors)
super
errors.add(I18n.t("vagrant.config.chef.server_url_empty")) if !chef_server_url || chef_server_url.strip == ""
errors.add(I18n.t("vagrant.config.chef.validation_key_path")) if !validation_key_path
errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if @run_list && @run_list.empty?
end
end
def self.config_class
Config
end
def prepare
raise ChefError, :server_validation_key_required if config.validation_key_path.nil?
raise ChefError, :server_validation_key_doesnt_exist if !File.file?(validation_key_path)
raise ChefError, :server_url_required if config.chef_server_url.nil?
end
def provision!
verify_binary(chef_binary_path("chef-client"))
chown_provisioning_folder
create_client_key_folder
upload_validation_key
upload_encrypted_data_bag_secret if config.encrypted_data_bag_secret_key_path
setup_json
setup_server_config
run_chef_client
end
def create_client_key_folder
env[:ui].info I18n.t("vagrant.provisioners.chef.client_key_folder")
path = Pathname.new(config.client_key_path)
env[:vm].channel.sudo("mkdir -p #{path.dirname}")
end
def upload_validation_key
env[:ui].info I18n.t("vagrant.provisioners.chef.upload_validation_key")
env[:vm].channel.upload(validation_key_path, guest_validation_key_path)
end
def upload_encrypted_data_bag_secret
env[:ui].info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key")
env[:vm].channel.upload(encrypted_data_bag_secret_key_path,
config.encrypted_data_bag_secret)
end
def setup_server_config
setup_config("provisioners/chef_client/client", "client.rb", {
:node_name => config.node_name,
:chef_server_url => config.chef_server_url,
:validation_client_name => config.validation_client_name,
:validation_key => guest_validation_key_path,
:client_key => config.client_key_path,
:file_cache_path => config.file_cache_path,
:file_backup_path => config.file_backup_path,
:environment => config.environment,
:encrypted_data_bag_secret => config.encrypted_data_bag_secret
})
end
def run_chef_client
command_env = config.binary_env ? "#{config.binary_env} " : ""
command = "#{command_env}#{chef_binary_path("chef-client")} -c #{config.provisioning_path}/client.rb -j #{config.provisioning_path}/dna.json"
config.attempts.times do |attempt|
if attempt == 0
env[:ui].info I18n.t("vagrant.provisioners.chef.running_client")
else
env[:ui].info I18n.t("vagrant.provisioners.chef.running_client_again")
end
exit_status = env[:vm].channel.sudo(command) do |type, data|
# Output the data with the proper color based on the stream.
color = type == :stdout ? :green : :red
# Note: Be sure to chomp the data to avoid the newlines that the
# Chef outputs.
env[:ui].info(data.chomp, :color => color, :prefix => false)
end
# There is no need to run Chef again if it converges
return if exit_status == 0
end
# If we reached this point then Chef never converged! Error.
raise ChefError, :no_convergence
end
def validation_key_path
File.expand_path(config.validation_key_path, env[:root_path])
end
def encrypted_data_bag_secret_key_path
File.expand_path(config.encrypted_data_bag_secret_key_path, env[:root_path])
end
def guest_validation_key_path
File.join(config.provisioning_path, "validation.pem")
end
end
end
end
end

View File

@ -0,0 +1,236 @@
require "log4r"
require File.expand_path("../base", __FILE__)
module VagrantPlugins
module Chef
module Provisioner
# This class implements provisioning via chef-solo.
class ChefSolo < Base
extend Vagrant::Util::Counter
include Vagrant::Util::Counter
class Config < Base::Config
attr_accessor :cookbooks_path
attr_accessor :roles_path
attr_accessor :data_bags_path
attr_accessor :recipe_url
attr_accessor :nfs
attr_accessor :encrypted_data_bag_secret_key_path
attr_accessor :encrypted_data_bag_secret
def encrypted_data_bag_secret; @encrypted_data_bag_secret || "/tmp/encrypted_data_bag_secret"; end
def initialize
super
@__default = ["cookbooks", [:vm, "cookbooks"]]
end
# Provide defaults in such a way that they won't override the instance
# variable. This is so merging continues to work properly.
def cookbooks_path
@cookbooks_path || _default_cookbook_path
end
# This stores a reference to the default cookbook path which is used
# later. Do not use this publicly. I apologize for not making it
# "protected" but it has to be called by Vagrant internals later.
def _default_cookbook_path
@__default
end
def nfs
@nfs || false
end
def validate(env, errors)
super
errors.add(I18n.t("vagrant.config.chef.cookbooks_path_empty")) if !cookbooks_path || [cookbooks_path].flatten.empty?
errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if !run_list || run_list.empty?
end
end
attr_reader :cookbook_folders
attr_reader :role_folders
attr_reader :data_bags_folders
def self.config_class
Config
end
def initialize(env, config)
super
@logger = Log4r::Logger.new("vagrant::provisioners::chef_solo")
end
def prepare
@cookbook_folders = expanded_folders(config.cookbooks_path, "cookbooks")
@role_folders = expanded_folders(config.roles_path, "roles")
@data_bags_folders = expanded_folders(config.data_bags_path, "data_bags")
share_folders("csc", @cookbook_folders)
share_folders("csr", @role_folders)
share_folders("csdb", @data_bags_folders)
end
def provision!
# Verify that the proper shared folders exist.
check = []
[@cookbook_folders, @role_folders, @data_bags_folders].each do |folders|
folders.each do |type, local_path, remote_path|
# We only care about checking folders that have a local path, meaning
# they were shared from the local machine, rather than assumed to
# exist on the VM.
check << remote_path if local_path
end
end
verify_shared_folders(check)
verify_binary(chef_binary_path("chef-solo"))
chown_provisioning_folder
upload_encrypted_data_bag_secret if config.encrypted_data_bag_secret_key_path
setup_json
setup_solo_config
run_chef_solo
end
# Converts paths to a list of properly expanded paths with types.
def expanded_folders(paths, appended_folder=nil)
return [] if paths.nil?
# Convert the path to an array if it is a string or just a single
# path element which contains the folder location (:host or :vm)
paths = [paths] if paths.is_a?(String) || paths.first.is_a?(Symbol)
results = []
paths.each do |path|
path = [:host, path] if !path.is_a?(Array)
type, path = path
# Create the local/remote path based on whether this is a host
# or VM path.
local_path = nil
remote_path = nil
if type == :host
# Get the expanded path that the host path points to
local_path = File.expand_path(path, env[:root_path])
# Super hacky but if we're expanded the default cookbook paths,
# and one of the host paths doesn't exist, then just ignore it,
# because that is fine.
if paths.equal?(config._default_cookbook_path) && !File.directory?(local_path)
@logger.info("'cookbooks' folder doesn't exist on defaults. Ignoring.")
next
end
# Path exists on the host, setup the remote path
remote_path = "#{config.provisioning_path}/chef-solo-#{get_and_update_counter(:cookbooks_path)}"
else
# Path already exists on the virtual machine. Expand it
# relative to where we're provisioning.
remote_path = File.expand_path(path, config.provisioning_path)
# Remove drive letter if running on a windows host. This is a bit
# of a hack but is the most portable way I can think of at the moment
# to achieve this. Otherwise, Vagrant attempts to share at some crazy
# path like /home/vagrant/c:/foo/bar
remote_path = remote_path.gsub(/^[a-zA-Z]:/, "")
end
# If we have specified a folder name to append then append it
remote_path += "/#{appended_folder}" if appended_folder
# Append the result
results << [type, local_path, remote_path]
end
results
end
# Shares the given folders with the given prefix. The folders should
# be of the structure resulting from the `expanded_folders` function.
def share_folders(prefix, folders)
folders.each do |type, local_path, remote_path|
if type == :host
env[:vm].config.vm.share_folder("v-#{prefix}-#{self.class.get_and_update_counter(:shared_folder)}",
remote_path, local_path, :nfs => config.nfs)
end
end
end
def upload_encrypted_data_bag_secret
env[:ui].info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key")
env[:vm].channel.upload(encrypted_data_bag_secret_key_path,
config.encrypted_data_bag_secret)
end
def setup_solo_config
cookbooks_path = guest_paths(@cookbook_folders)
roles_path = guest_paths(@role_folders).first
data_bags_path = guest_paths(@data_bags_folders).first
setup_config("provisioners/chef_solo/solo", "solo.rb", {
:node_name => config.node_name,
:provisioning_path => config.provisioning_path,
:cookbooks_path => cookbooks_path,
:recipe_url => config.recipe_url,
:roles_path => roles_path,
:data_bags_path => data_bags_path,
:encrypted_data_bag_secret => config.encrypted_data_bag_secret,
})
end
def run_chef_solo
command_env = config.binary_env ? "#{config.binary_env} " : ""
command = "#{command_env}#{chef_binary_path("chef-solo")} -c #{config.provisioning_path}/solo.rb -j #{config.provisioning_path}/dna.json"
config.attempts.times do |attempt|
if attempt == 0
env[:ui].info I18n.t("vagrant.provisioners.chef.running_solo")
else
env[:ui].info I18n.t("vagrant.provisioners.chef.running_solo_again")
end
exit_status = env[:vm].channel.sudo(command, :error_check => false) do |type, data|
# Output the data with the proper color based on the stream.
color = type == :stdout ? :green : :red
# Note: Be sure to chomp the data to avoid the newlines that the
# Chef outputs.
env[:ui].info(data.chomp, :color => color, :prefix => false)
end
# There is no need to run Chef again if it converges
return if exit_status == 0
end
# If we reached this point then Chef never converged! Error.
raise ChefError, :no_convergence
end
def verify_shared_folders(folders)
folders.each do |folder|
@logger.debug("Checking for shared folder: #{folder}")
if !env[:vm].channel.test("test -d #{folder}")
raise ChefError, :missing_shared_folders
end
end
end
def encrypted_data_bag_secret_key_path
File.expand_path(config.encrypted_data_bag_secret_key_path, env[:root_path])
end
protected
# Extracts only the remote paths from a list of folders
def guest_paths(folders)
folders.map { |parts| parts[2] }
end
end
end
end
end

View File

@ -13,9 +13,19 @@ module VagrantPlugins
@logger.debug("Provisioner config: #{shortcut}")
@shortcut = shortcut
@provisioner = shortcut
@provisioner = Vagrant.provisioners.get(shortcut) if shortcut.is_a?(Symbol)
@config = nil
# If the shorcut is a symbol, we look through the registered
# plugins to see if any define a provisioner we want.
if shortcut.is_a?(Symbol)
Vagrant.plugin("1").registered.each do |plugin|
if plugin.provisioner.has_key?(shortcut)
@provisioner = plugin.provisioner[shortcut]
break
end
end
end
@logger.info("Provisioner class: #{provisioner}")
configure(options, &block) if @provisioner
end

21
plugins/puppet/plugin.rb Normal file
View File

@ -0,0 +1,21 @@
require "vagrant"
module VagrantPlugins
module Pupppet
module Provisioner
autoload :Puppet, File.expand_path("../provisioner/puppet", __FILE__)
autoload :PuppetServer, File.expand_path("../provisioner/puppet_server", __FILE__)
end
class Plugin < Vagrant.plugin("1")
name "puppet"
description <<-DESC
Provides support for provisioning your virtual machines with
Puppet either using `puppet apply` or a Puppet server.
DESC
provisioner("puppet") { Provisioner::Puppet }
provisioner("puppet_server") { Provisioner::PuppetServer }
end
end
end

View File

@ -0,0 +1,176 @@
require "log4r"
module VagrantPlugins
module Puppet
module Provisioner
class PuppetError < Vagrant::Errors::VagrantError
error_namespace("vagrant.provisioners.puppet")
end
class Puppet < Vagrant::Provisioners::Base
class Config < Vagrant::Config::Base
attr_accessor :manifest_file
attr_accessor :manifests_path
attr_accessor :module_path
attr_accessor :pp_path
attr_accessor :options
attr_accessor :facter
def manifest_file; @manifest_file || "default.pp"; end
def manifests_path; @manifests_path || "manifests"; end
def pp_path; @pp_path || "/tmp/vagrant-puppet"; end
def options; @options ||= []; end
def facter; @facter ||= {}; end
# Returns the manifests path expanded relative to the root path of the
# environment.
def expanded_manifests_path(root_path)
Pathname.new(manifests_path).expand_path(root_path)
end
# Returns the module paths as an array of paths expanded relative to the
# root path.
def expanded_module_paths(root_path)
return [] if !module_path
# Get all the paths and expand them relative to the root path, returning
# the array of expanded paths
paths = module_path
paths = [paths] if !paths.is_a?(Array)
paths.map do |path|
Pathname.new(path).expand_path(root_path)
end
end
def validate(env, errors)
# Calculate the manifests and module paths based on env
this_expanded_manifests_path = expanded_manifests_path(env.root_path)
this_expanded_module_paths = expanded_module_paths(env.root_path)
# Manifests path/file validation
if !this_expanded_manifests_path.directory?
errors.add(I18n.t("vagrant.provisioners.puppet.manifests_path_missing",
:path => this_expanded_manifests_path))
else
expanded_manifest_file = this_expanded_manifests_path.join(manifest_file)
if !expanded_manifest_file.file?
errors.add(I18n.t("vagrant.provisioners.puppet.manifest_missing",
:manifest => expanded_manifest_file.to_s))
end
end
# Module paths validation
this_expanded_module_paths.each do |path|
if !path.directory?
errors.add(I18n.t("vagrant.provisioners.puppet.module_path_missing", :path => path))
end
end
end
end
def self.config_class
Config
end
def initialize(env, config)
super
@logger = Log4r::Logger.new("vagrant::provisioners::puppet")
end
def prepare
# Calculate the paths we're going to use based on the environment
@expanded_manifests_path = config.expanded_manifests_path(env[:root_path])
@expanded_module_paths = config.expanded_module_paths(env[:root_path])
@manifest_file = File.join(manifests_guest_path, config.manifest_file)
set_module_paths
share_manifests
share_module_paths
end
def provision!
# Check that the shared folders are properly shared
check = [manifests_guest_path]
@module_paths.each do |host_path, guest_path|
check << guest_path
end
verify_shared_folders(check)
# Verify Puppet is installed and run it
verify_binary("puppet")
run_puppet_client
end
def share_manifests
env[:vm].config.vm.share_folder("manifests", manifests_guest_path, @expanded_manifests_path)
end
def share_module_paths
count = 0
@module_paths.each do |from, to|
# Sorry for the cryptic key here, but VirtualBox has a strange limit on
# maximum size for it and its something small (around 10)
env[:vm].config.vm.share_folder("v-pp-m#{count}", to, from)
count += 1
end
end
def set_module_paths
@module_paths = {}
@expanded_module_paths.each_with_index do |path, i|
@module_paths[path] = File.join(config.pp_path, "modules-#{i}")
end
end
def manifests_guest_path
File.join(config.pp_path, "manifests")
end
def verify_binary(binary)
env[:vm].channel.sudo("which #{binary}",
:error_class => PuppetError,
:error_key => :not_detected,
:binary => binary)
end
def run_puppet_client
options = [config.options].flatten
options << "--modulepath '#{@module_paths.values.join(':')}'" if !@module_paths.empty?
options << @manifest_file
options = options.join(" ")
# Build up the custom facts if we have any
facter = ""
if !config.facter.empty?
facts = []
config.facter.each do |key, value|
facts << "FACTER_#{key}='#{value}'"
end
facter = "#{facts.join(" ")} "
end
command = "cd #{manifests_guest_path} && #{facter}puppet apply #{options}"
env[:ui].info I18n.t("vagrant.provisioners.puppet.running_puppet",
:manifest => @manifest_file)
env[:vm].channel.sudo(command) do |type, data|
env[:ui].info(data.chomp, :prefix => false)
end
end
def verify_shared_folders(folders)
folders.each do |folder|
@logger.debug("Checking for shared folder: #{folder}")
if !env[:vm].channel.test("test -d #{folder}")
raise PuppetError, :missing_shared_folders
end
end
end
end
end
end
end

View File

@ -0,0 +1,80 @@
module VagrantPlugins
module Puppet
module Provisioner
class PuppetServerError < Vagrant::Errors::VagrantError
error_namespace("vagrant.provisioners.puppet_server")
end
class PuppetServer < Base
class Config < Vagrant::Config::Base
attr_accessor :puppet_server
attr_accessor :puppet_node
attr_accessor :options
attr_accessor :facter
def facter; @facter ||= {}; end
def puppet_server; @puppet_server || "puppet"; end
def options; @options ||= []; end
end
def self.config_class
Config
end
def provision!
verify_binary("puppetd")
run_puppetd_client
end
def verify_binary(binary)
env[:vm].channel.sudo("which #{binary}",
:error_class => PuppetServerError,
:error_key => :not_detected,
:binary => binary)
end
def run_puppetd_client
options = config.options
options = [options] if !options.is_a?(Array)
# Intelligently set the puppet node cert name based on certain
# external parameters.
cn = nil
if config.puppet_node
# If a node name is given, we use that directly for the certname
cn = config.puppet_node
elsif env[:vm].config.vm.host_name
# If a host name is given, we explicitly set the certname to
# nil so that the hostname becomes the cert name.
cn = nil
else
# Otherwise, we default to the name of the box.
cn = env[:vm].config.vm.box
end
# Add the certname option if there is one
options += ["--certname", cn] if cn
options = options.join(" ")
# Build up the custom facts if we have any
facter = ""
if !config.facter.empty?
facts = []
config.facter.each do |key, value|
facts << "FACTER_#{key}='#{value}'"
end
facter = "#{facts.join(" ")} "
end
command = "#{facter}puppetd #{options} --server #{config.puppet_server}"
env[:ui].info I18n.t("vagrant.provisioners.puppet_server.running_puppetd")
env[:vm].channel.sudo(command) do |type, data|
env[:ui].info(data.chomp, :prefix => false)
end
end
end
end
end
end

17
plugins/shell/plugin.rb Normal file
View File

@ -0,0 +1,17 @@
require "vagrant"
module VagrantPlugins
module Shell
autoload :Provisioner, File.expand_path("../provisioner", __FILE__)
class Plugin < Vagrant.plugin("1")
name "shell"
description <<-DESC
Provides support for provisioning your virtual machines with
shell scripts.
DESC
provisioner("shell") { Provisioner }
end
end
end

View File

@ -1,8 +1,9 @@
require 'tempfile'
require "pathname"
require "tempfile"
module Vagrant
module Provisioners
class Shell < Base
module VagrantPlugins
module Shell
class Provisioner < Vagrant::Provisioners::Base
class Config < Vagrant::Config::Base
attr_accessor :inline
attr_accessor :path

View File

@ -79,6 +79,34 @@ describe Vagrant::Plugin::V1 do
end
end
describe "provisioners" do
it "should register provisioner classes" do
plugin = Class.new(described_class) do
provisioner("foo") { "bar" }
end
plugin.provisioner[:foo].should == "bar"
end
it "should lazily register provisioner classes" do
# Below would raise an error if the value of the config class was
# evaluated immediately. By asserting that this does not raise an
# error, we verify that the value is actually lazily loaded
plugin = nil
expect {
plugin = Class.new(described_class) do
provisioner("foo") { raise StandardError, "FAIL!" }
end
}.to_not raise_error
# Now verify when we actually get the configuration key that
# a proper error is raised.
expect {
plugin.provisioner[:foo]
}.to raise_error(StandardError)
end
end
describe "plugin registration" do
it "should have no registered plugins" do
described_class.registered.should be_empty

View File

@ -13,10 +13,6 @@ describe Vagrant do
described_class.hosts.should be_a(Vagrant::Registry)
end
it "has a registry for provisioners" do
described_class.provisioners.should be_a(Vagrant::Registry)
end
describe "plugin superclass" do
it "returns the proper class for version 1" do
described_class.plugin("1").should == Vagrant::Plugin::V1