Build the v2 interface, which is just a copy of V1 for now.

This commit is contained in:
Mitchell Hashimoto 2012-11-06 20:51:03 -08:00
parent efa0a6b8c2
commit be294e002a
17 changed files with 1270 additions and 1 deletions

View File

@ -9,6 +9,7 @@ module Vagrant
autoload :VersionBase, 'vagrant/config/version_base'
autoload :V1, 'vagrant/config/v1'
autoload :V2, 'vagrant/config/v2'
# This is a mutex used to guarantee that only one thread can load
# procs at any given time.
@ -19,12 +20,13 @@ module Vagrant
# `Vagrant.configure` calls.
VERSIONS = Registry.new
VERSIONS.register("1") { V1::Loader }
VERSIONS.register("2") { V2::Loader }
# This is the order of versions. This is used by the loader to figure out
# how to "upgrade" versions up to the desired (current) version. The
# current version is always considered to be the last version in this
# list.
VERSIONS_ORDER = ["1"]
VERSIONS_ORDER = ["1", "2"]
CURRENT_VERSION = VERSIONS_ORDER.last
# This is the method which is called by all Vagrantfiles to configure Vagrant.

8
lib/vagrant/config/v2.rb Normal file
View File

@ -0,0 +1,8 @@
module Vagrant
module Config
module V2
autoload :Loader, "vagrant/config/v2/loader"
autoload :Root, "vagrant/config/v2/root"
end
end
end

View File

@ -0,0 +1,114 @@
require "vagrant/config/v2/root"
module Vagrant
module Config
module V2
# This is the loader that handles configuration loading for V2
# configurations.
class Loader < VersionBase
# Returns a bare empty configuration object.
#
# @return [V2::Root]
def self.init
new_root_object
end
# Finalizes the configuration by making sure there is at least
# one VM defined in it.
def self.finalize(config)
# Call the `#finalize` method on each of the configuration keys.
# They're expected to modify themselves in our case.
config.finalize!
# Return the object
config
end
# Loads the configuration for the given proc and returns a configuration
# object.
#
# @param [Proc] config_proc
# @return [Object]
def self.load(config_proc)
# Create a root configuration object
root = new_root_object
# Call the proc with the root
config_proc.call(root)
# Return the root object, which doubles as the configuration object
# we actually use for accessing as well.
root
end
# Merges two configuration objects.
#
# @param [V2::Root] old The older root config.
# @param [V2::Root] new The newer root config.
# @return [V2::Root]
def self.merge(old, new)
# Grab the internal states, we use these heavily throughout the process
old_state = old.__internal_state
new_state = new.__internal_state
# The config map for the new object is the old one merged with the
# new one.
config_map = old_state["config_map"].merge(new_state["config_map"])
# Merge the keys.
old_keys = old_state["keys"]
new_keys = new_state["keys"]
keys = {}
old_keys.each do |key, old_value|
if new_keys.has_key?(key)
# We need to do a merge, which we expect to be available
# on the config class itself.
keys[key] = old_value.merge(new_keys[key])
else
# We just take the old value, but dup it so that we can modify.
keys[key] = old_value.dup
end
end
new_keys.each do |key, new_value|
# Add in the keys that the new class has that we haven't merged.
if !keys.has_key?(key)
keys[key] = new_value.dup
end
end
# Return the final root object
V2::Root.new(config_map, keys)
end
# Upgrade a V1 configuration to a V2 configuration.
#
# @param [V1::Root] old
# @return [Array] A 3-tuple result.
def self.upgrade(old)
# TODO: Actually do an upgrade. For now we just return V1.
[old, [], []]
end
protected
def self.new_root_object
# Get all the registered configuration objects and use them. If
# we're currently on version 1, then we load all the config objects,
# otherwise we load only the upgrade safe ones, since we're
# obviously being loaded for an upgrade.
config_map = nil
plugin_manager = Vagrant.plugin("1").manager
if Config::CURRENT_VERSION == "1"
config_map = plugin_manager.config
else
config_map = plugin_manager.config_upgrade_safe
end
# Create the configuration root object
V2::Root.new(config_map)
end
end
end
end
end

View File

@ -0,0 +1,75 @@
module Vagrant
module Config
module V2
# This is the root configuration class. An instance of this is what
# is passed into version 1 Vagrant configuration blocks.
class Root
# Initializes a root object that maps the given keys to specific
# configuration classes.
#
# @param [Hash] config_map Map of key to config class.
def initialize(config_map, keys=nil)
@keys = keys || {}
@config_map = config_map
end
# We use method_missing as a way to get the configuration that is
# used for Vagrant and load the proper configuration classes for
# each.
def method_missing(name, *args)
return @keys[name] if @keys.has_key?(name)
config_klass = @config_map[name.to_sym]
if config_klass
# Instantiate the class and return the instance
@keys[name] = config_klass.new
return @keys[name]
else
# Super it up to probably raise a NoMethodError
super
end
end
# Called to finalize this object just prior to it being used by
# the Vagrant system. The "!" signifies that this is expected to
# mutate itself.
def finalize!
@keys.each do |_key, instance|
instance.finalize!
end
end
# Validates the configuration classes of this instance and raises an
# exception if they are invalid. If you are implementing a custom configuration
# class, the method you want to implement is {Base#validate}. This is
# the method that checks all the validation, not one which defines
# validation rules.
def validate!(env)
# Validate each of the configured classes and store the results into
# a hash.
errors = @keys.inject({}) do |container, data|
key, instance = data
recorder = ErrorRecorder.new
instance.validate(env, recorder)
container[key.to_sym] = recorder if !recorder.errors.empty?
container
end
return if errors.empty?
raise Errors::ConfigValidationFailed, :messages => Util::TemplateRenderer.render("config/validation_failed", :errors => errors)
end
# Returns the internal state of the root object. This is used
# by outside classes when merging, and shouldn't be called directly.
# Note the strange method name is to attempt to avoid any name
# clashes with potential configuration keys.
def __internal_state
{
"config_map" => @config_map,
"keys" => @keys
}
end
end
end
end
end

View File

@ -1,5 +1,6 @@
module Vagrant
module Plugin
autoload :V1, "vagrant/plugin/v1"
autoload :V2, "vagrant/plugin/v2"
end
end

19
lib/vagrant/plugin/v2.rb Normal file
View File

@ -0,0 +1,19 @@
require "log4r"
require "vagrant/plugin/v2/errors"
module Vagrant
module Plugin
module V2
autoload :Command, "vagrant/plugin/v2/command"
autoload :Communicator, "vagrant/plugin/v2/communicator"
autoload :Config, "vagrant/plugin/v2/config"
autoload :Guest, "vagrant/plugin/v2/guest"
autoload :Host, "vagrant/plugin/v2/host"
autoload :Manager, "vagrant/plugin/v2/manager"
autoload :Plugin, "vagrant/plugin/v2/plugin"
autoload :Provider, "vagrant/plugin/v2/provider"
autoload :Provisioner, "vagrant/plugin/v2/provisioner"
end
end
end

View File

@ -0,0 +1,169 @@
require 'log4r'
require "vagrant/util/safe_puts"
module Vagrant
module Plugin
module V2
# This is the base class for a CLI command.
class Command
include Util::SafePuts
def initialize(argv, env)
@argv = argv
@env = env
@logger = Log4r::Logger.new("vagrant::command::#{self.class.to_s.downcase}")
end
# This is what is called on the class to actually execute it. Any
# subclasses should implement this method and do any option parsing
# and validation here.
def execute
end
protected
# Parses the options given an OptionParser instance.
#
# This is a convenience method that properly handles duping the
# originally argv array so that it is not destroyed.
#
# This method will also automatically detect "-h" and "--help"
# and print help. And if any invalid options are detected, the help
# will be printed, as well.
#
# If this method returns `nil`, then you should assume that help
# was printed and parsing failed.
def parse_options(opts=nil)
# Creating a shallow copy of the arguments so the OptionParser
# doesn't destroy the originals.
argv = @argv.dup
# Default opts to a blank optionparser if none is given
opts ||= OptionParser.new
# Add the help option, which must be on every command.
opts.on_tail("-h", "--help", "Print this help") do
safe_puts(opts.help)
return nil
end
opts.parse!(argv)
return argv
rescue OptionParser::InvalidOption
raise Errors::CLIInvalidOptions, :help => opts.help.chomp
end
# Yields a VM for each target VM for the command.
#
# This is a convenience method for easily implementing methods that
# take a target VM (in the case of multi-VM) or every VM if no
# specific VM name is specified.
#
# @param [String] name The name of the VM. Nil if every VM.
# @param [Boolean] single_target If true, then an exception will be
# raised if more than one target is found.
def with_target_vms(names=nil, options=nil)
# Using VMs requires a Vagrant environment to be properly setup
raise Errors::NoEnvironmentError if !@env.root_path
# Setup the options hash
options ||= {}
# Require that names be an array
names ||= []
names = [names] if !names.is_a?(Array)
# First determine the proper array of VMs.
vms = []
if names.length > 0
names.each do |name|
if pattern = name[/^\/(.+?)\/$/, 1]
# This is a regular expression name, so we convert to a regular
# expression and allow that sort of matching.
regex = Regexp.new(pattern)
@env.vms.each do |vm_name, vm|
vms << vm if vm_name =~ regex
end
raise Errors::VMNoMatchError if vms.empty?
else
# String name, just look for a specific VM
vms << @env.vms[name.to_sym]
raise Errors::VMNotFoundError, :name => name if !vms[0]
end
end
else
vms = @env.vms_ordered
end
# Make sure we're only working with one VM if single target
if options[:single_target] && vms.length != 1
vm = @env.primary_vm
raise Errors::MultiVMTargetRequired if !vm
vms = [vm]
end
# If we asked for reversed ordering, then reverse it
vms.reverse! if options[:reverse]
# Go through each VM and yield it!
vms.each do |old_vm|
# We get a new VM from the environment here to avoid potentially
# stale VMs (if there was a config reload on the environment
# or something).
vm = @env.vms[old_vm.name]
yield vm
end
end
# This method will split the argv given into three parts: the
# flags to this command, the subcommand, and the flags to the
# subcommand. For example:
#
# -v status -h -v
#
# The above would yield 3 parts:
#
# ["-v"]
# "status"
# ["-h", "-v"]
#
# These parts are useful because the first is a list of arguments
# given to the current command, the second is a subcommand, and the
# third are the commands given to the subcommand.
#
# @return [Array] The three parts.
def split_main_and_subcommand(argv)
# Initialize return variables
main_args = nil
sub_command = nil
sub_args = []
# We split the arguments into two: One set containing any
# flags before a word, and then the rest. The rest are what
# get actually sent on to the subcommand.
argv.each_index do |i|
if !argv[i].start_with?("-")
# We found the beginning of the sub command. Split the
# args up.
main_args = argv[0, i]
sub_command = argv[i]
sub_args = argv[i + 1, argv.length - i + 1]
# Break so we don't find the next non flag and shift our
# main args.
break
end
end
# Handle the case that argv was empty or didn't contain any subcommand
main_args = argv.dup if main_args.nil?
return [main_args, sub_command, sub_args]
end
end
end
end
end

View File

@ -0,0 +1,98 @@
module Vagrant
module Plugin
module V2
# Base class for a communicator in Vagrant. A communicator is
# responsible for communicating with a machine in some way. There
# are various stages of Vagrant that require things such as uploading
# files to the machine, executing shell commands, etc. Implementors
# of this class are expected to provide this functionality in some
# way.
#
# Note that a communicator must provide **all** of the methods
# in this base class. There is currently no way for one communicator
# to provide say a more efficient way of uploading a file, but not
# provide shell execution. This sort of thing will come in a future
# version.
class Communicator
# This returns true/false depending on if the given machine
# can be communicated with using this communicator. If this returns
# `true`, then this class will be used as the primary communication
# method for the machine.
#
# @return [Boolean]
def self.match?(machine)
false
end
# Initializes the communicator with the machine that we will be
# communicating with. This base method does nothing (it doesn't
# even store the machine in an instance variable for you), so you're
# expected to override this and do something with the machine if
# you care about it.
#
# @param [Machine] machine The machine this instance is expected to
# communicate with.
def initialize(machine)
end
# Checks if the target machine is ready for communication. If this
# returns true, then all the other methods for communicating with
# the machine are expected to be functional.
#
# @return [Boolean]
def ready?
false
end
# Download a file from the remote machine to the local machine.
#
# @param [String] from Path of the file on the remote machine.
# @param [String] to Path of where to save the file locally.
def download(from, to)
end
# Upload a file to the remote machine.
#
# @param [String] from Path of the file locally to upload.
# @param [String] to Path of where to save the file on the remote
# machine.
def upload(from, to)
end
# Execute a command on the remote machine. The exact semantics
# of this method are up to the implementor, but in general the
# users of this class will expect this to be a shell.
#
# This method gives you no way to write data back to the remote
# machine, so only execute commands that don't expect input.
#
# @param [String] command Command to execute.
# @yield [type, data] Realtime output of the command being executed.
# @yieldparam [String] type Type of the output. This can be
# `:stdout`, `:stderr`, etc. The exact types are up to the
# implementor.
# @yieldparam [String] data Data for the given output.
# @return [Integer] Exit code of the command.
def execute(command, opts=nil)
end
# Executes a command on the remote machine with administrative
# privileges. See {#execute} for documentation, as the API is the
# same.
#
# @see #execute
def sudo(command, opts=nil)
end
# Executes a command and returns true if the command succeeded,
# and false otherwise. By default, this executes as a normal user,
# and it is up to the communicator implementation if they expose an
# option for running tests as an administrator.
#
# @see #execute
def test(command, opts=nil)
end
end
end
end
end

View File

@ -0,0 +1,94 @@
module Vagrant
module Plugin
module V2
# This is the base class for a configuration key defined for
# V1. Any configuration key plugins for V1 should inherit from this
# class.
class Config
# This is called as a last-minute hook that allows the configuration
# object to finalize itself before it will be put into use. This is
# a useful place to do some defaults in the case the user didn't
# configure something or so on.
#
# An example of where this sort of thing is used or has been used:
# the "vm" configuration key uses this to make sure that at least
# one sub-VM has been defined: the default VM.
#
# The configuration object is expected to mutate itself.
def finalize!
# Default implementation is to do nothing.
end
# Merge another configuration object into this one. This assumes that
# the other object is the same class as this one. This should not
# mutate this object, but instead should return a new, merged object.
#
# The default implementation will simply iterate over the instance
# variables and merge them together, with this object overriding
# any conflicting instance variables of the older object. Instance
# variables starting with "__" (double underscores) will be ignored.
# This lets you set some sort of instance-specific state on your
# configuration keys without them being merged together later.
#
# @param [Object] other The other configuration object to merge from,
# this must be the same type of object as this one.
# @return [Object] The merged object.
def merge(other)
result = self.class.new
# Set all of our instance variables on the new class
[self, other].each do |obj|
obj.instance_variables.each do |key|
# Ignore keys that start with a double underscore. This allows
# configuration classes to still hold around internal state
# that isn't propagated.
if !key.to_s.start_with?("@__")
result.instance_variable_set(key, obj.instance_variable_get(key))
end
end
end
result
end
# Allows setting options from a hash. By default this simply calls
# the `#{key}=` method on the config class with the value, which is
# the expected behavior most of the time.
#
# This is expected to mutate itself.
#
# @param [Hash] options A hash of options to set on this configuration
# key.
def set_options(options)
options.each do |key, value|
send("#{key}=", value)
end
end
# Converts this configuration object to JSON.
def to_json(*a)
instance_variables_hash.to_json(*a)
end
# Returns the instance variables as a hash of key-value pairs.
def instance_variables_hash
instance_variables.inject({}) do |acc, iv|
acc[iv.to_s[1..-1]] = instance_variable_get(iv)
acc
end
end
# Called after the configuration is finalized and loaded to validate
# this object.
#
# @param [Environment] env Vagrant::Environment object of the
# environment that this configuration has been loaded into. This
# gives you convenient access to things like the the root path
# and so on.
# @param [ErrorRecorder] errors
def validate(env, errors)
end
end
end
end
end

View File

@ -0,0 +1,18 @@
# This file contains all the errors that the V2 plugin interface
# may throw.
module Vagrant
module Plugin
module V2
# Exceptions that can be thrown within the plugin interface all
# inherit from this parent exception.
class Error < StandardError; end
# This is thrown when a command name given is invalid.
class InvalidCommandName < Error; end
# This is thrown when a hook "position" is invalid.
class InvalidEasyHookPosition < Error; end
end
end
end

View File

@ -0,0 +1,92 @@
module Vagrant
module Plugin
module V2
# The base class for a guest. A guest represents an installed system
# within a machine that Vagrant manages. There are some portions of
# Vagrant which are OS-specific such as mountaing shared folders and
# halting the machine, and this abstraction allows the implementation
# for these to be seperate from the core of Vagrant.
class Guest
class BaseError < Errors::VagrantError
error_namespace("vagrant.guest.base")
end
include Vagrant::Util
# The VM which this system is tied to.
attr_reader :vm
# Initializes the system. Any subclasses MUST make sure this
# method is called on the parent. Therefore, if a subclass overrides
# `initialize`, then you must call `super`.
def initialize(vm)
@vm = vm
end
# This method is automatically called when the system is available (when
# Vagrant can successfully SSH into the machine) to give the system a chance
# to determine the distro and return a distro-specific system.
#
# If this method returns nil, then this instance is assumed to be
# the most specific guest implementation.
def distro_dispatch
end
# Halt the machine. This method should gracefully shut down the
# operating system. This method will cause `vagrant halt` and associated
# commands to _block_, meaning that if the machine doesn't halt
# in a reasonable amount of time, this method should just return.
#
# If when this method returns, the machine's state isn't "powered_off,"
# Vagrant will proceed to forcefully shut the machine down.
def halt
raise BaseError, :_key => :unsupported_halt
end
# Mounts a shared folder.
#
# This method should create, mount, and properly set permissions
# on the shared folder. This method should also properly
# adhere to any configuration values such as `shared_folder_uid`
# on `config.vm`.
#
# @param [String] name The name of the shared folder.
# @param [String] guestpath The path on the machine which the user
# wants the folder mounted.
# @param [Hash] options Additional options for the shared folder
# which can be honored.
def mount_shared_folder(name, guestpath, options)
raise BaseError, :_key => :unsupported_shared_folder
end
# Mounts a shared folder via NFS. This assumes that the exports
# via the host are already done.
def mount_nfs(ip, folders)
raise BaseError, :_key => :unsupported_nfs
end
# Configures the given list of networks on the virtual machine.
#
# The networks parameter will be an array of hashes where the hashes
# represent the configuration of a network interface. The structure
# of the hash will be roughly the following:
#
# {
# :type => :static,
# :ip => "192.168.33.10",
# :netmask => "255.255.255.0",
# :interface => 1
# }
#
def configure_networks(networks)
raise BaseError, :_key => :unsupported_configure_networks
end
# Called to change the hostname of the virtual machine.
def change_host_name(name)
raise BaseError, :_key => :unsupported_host_name
end
end
end
end
end

View File

@ -0,0 +1,66 @@
module Vagrant
module Plugin
module V2
# Base class for a host in Vagrant. A host class contains functionality
# that is specific to a specific OS that is running Vagrant. This
# abstraction is done becauase there is some host-specific logic that
# Vagrant must do in some cases.
class Host
# This returns true/false depending on if the current running system
# matches the host class.
#
# @return [Boolean]
def self.match?
nil
end
# The precedence of the host when checking for matches. This is to
# allow certain host such as generic OS's ("Linux", "BSD", etc.)
# to be specified last.
#
# The hosts with the higher numbers will be checked first.
#
# If you're implementing a basic host, you can probably ignore this.
def self.precedence
5
end
# Initializes a new host class.
#
# The only required parameter is a UI object so that the host
# objects have some way to communicate with the outside world.
#
# @param [UI] ui UI for the hosts to output to.
def initialize(ui)
@ui = ui
end
# Returns true of false denoting whether or not this host supports
# NFS shared folder setup. This method ideally should verify that
# NFS is installed.
#
# @return [Boolean]
def nfs?
false
end
# Exports the given hash of folders via NFS.
#
# @param [String] id A unique ID that is guaranteed to be unique to
# match these sets of folders.
# @param [String] ip IP of the guest machine.
# @param [Hash] folders Shared folders to sync.
def nfs_export(id, ip, folders)
end
# Prunes any NFS exports made by Vagrant which aren't in the set
# of valid ids given.
#
# @param [Array<String>] valid_ids Valid IDs that should not be
# pruned.
def nfs_prune(valid_ids)
end
end
end
end
end

View File

@ -0,0 +1,131 @@
require "log4r"
module Vagrant
module Plugin
module V2
# This class maintains a list of all the registered plugins as well
# as provides methods that allow querying all registered components of
# those plugins as a single unit.
class Manager
attr_reader :registered
def initialize
@logger = Log4r::Logger.new("vagrant::plugin::v2::manager")
@registered = []
end
# This returns all the registered communicators.
#
# @return [Hash]
def communicators
result = {}
@registered.each do |plugin|
result.merge!(plugin.communicator.to_hash)
end
result
end
# This returns all the registered configuration classes.
#
# @return [Hash]
def config
result = {}
@registered.each do |plugin|
plugin.config.each do |key, klass|
result[key] = klass
end
end
result
end
# This returns all the registered configuration classes that were
# marked as "upgrade safe."
#
# @return [Hash]
def config_upgrade_safe
result = {}
@registered.each do |plugin|
configs = plugin.data[:config_upgrade_safe]
if configs
configs.each do |key|
result[key] = plugin.config.get(key)
end
end
end
result
end
# This returns all the registered guests.
#
# @return [Hash]
def guests
result = {}
@registered.each do |plugin|
result.merge!(plugin.guest.to_hash)
end
result
end
# This returns all registered host classes.
#
# @return [Hash]
def hosts
hosts = {}
@registered.each do |plugin|
hosts.merge!(plugin.host.to_hash)
end
hosts
end
# This returns all registered providers.
#
# @return [Hash]
def providers
providers = {}
@registered.each do |plugin|
providers.merge!(plugin.provider.to_hash)
end
providers
end
# This registers a plugin. This should _NEVER_ be called by the public
# and should only be called from within Vagrant. Vagrant will
# automatically register V2 plugins when a name is set on the
# plugin.
def register(plugin)
if !@registered.include?(plugin)
@logger.info("Registered plugin: #{plugin.name}")
@registered << plugin
end
end
# This clears out all the registered plugins. This is only used by
# unit tests and should not be called directly.
def reset!
@registered.clear
end
# This unregisters a plugin so that its components will no longer
# be used. Note that this should only be used for testing purposes.
def unregister(plugin)
if @registered.include?(plugin)
@logger.info("Unregistered: #{plugin.name}")
@registered.delete(plugin)
end
end
end
end
end
end

View File

@ -0,0 +1,259 @@
require "log4r"
module Vagrant
module Plugin
module V2
# This is the superclass for all V2 plugins.
class Plugin
# Special marker that can be used for action hooks that matches
# all action sequences.
ALL_ACTIONS = :__all_actions__
# The logger for this class.
LOGGER = Log4r::Logger.new("vagrant::plugin::v2::plugin")
# Set the root class up to be ourself, so that we can reference this
# from within methods which are probably in subclasses.
ROOT_CLASS = self
# This returns the manager for all V2 plugins.
#
# @return [V2::Manager]
def self.manager
@manager ||= Manager.new
end
# Set the name of the plugin. The moment that this is called, the
# plugin will be registered and available. Before this is called, a
# plugin does not exist. The name must be unique among all installed
# plugins.
#
# @param [String] name Name of the plugin.
# @return [String] The name of the plugin.
def self.name(name=UNSET_VALUE)
# Get or set the value first, so we have a name for logging when
# we register.
result = get_or_set(:name, name)
# The plugin should be registered if we're setting a real name on it
Plugin.manager.register(self) if name != UNSET_VALUE
# Return the result
result
end
# Sets a human-friendly descrition of the plugin.
#
# @param [String] value Description of the plugin.
# @return [String] Description of the plugin.
def self.description(value=UNSET_VALUE)
get_or_set(:description, value)
end
# Registers a callback to be called when a specific action sequence
# is run. This allows plugin authors to hook into things like VM
# bootup, VM provisioning, etc.
#
# @param [Symbol] name Name of the action.
# @return [Array] List of the hooks for the given action.
def self.action_hook(name, &block)
# Get the list of hooks for the given hook name
data[:action_hooks] ||= {}
hooks = data[:action_hooks][name.to_sym] ||= []
# Return the list if we don't have a block
return hooks if !block_given?
# Otherwise add the block to the list of hooks for this action.
hooks << block
end
# Defines additional command line commands available by key. The key
# becomes the subcommand, so if you register a command "foo" then
# "vagrant foo" becomes available.
#
# @param [String] name Subcommand key.
def self.command(name=UNSET_VALUE, &block)
data[:command] ||= Registry.new
if name != UNSET_VALUE
# Validate the name of the command
if name.to_s !~ /^[-a-z0-9]+$/i
raise InvalidCommandName, "Commands can only contain letters, numbers, and hyphens"
end
# Register a new command class only if a name was given.
data[:command].register(name.to_sym, &block)
end
# Return the registry
data[:command]
end
# Defines additional communicators to be available. Communicators
# should be returned by a block passed to this method. This is done
# to ensure that the class is lazy loaded, so if your class inherits
# from or uses any Vagrant internals specific to Vagrant 1.0, then
# the plugin can still be defined without breaking anything in future
# versions of Vagrant.
#
# @param [String] name Communicator name.
def self.communicator(name=UNSET_VALUE, &block)
data[:communicator] ||= Registry.new
# Register a new communicator class only if a name was given.
data[:communicator].register(name.to_sym, &block) if name != UNSET_VALUE
# Return the registry
data[:communicator]
end
# Defines additional configuration keys to be available in the
# Vagrantfile. The configuration class should be returned by a
# block passed to this method. This is done to ensure that the class
# is lazy loaded, so if your class inherits from any classes that
# are specific to Vagrant 1.0, then the plugin can still be defined
# without breaking anything in future versions of Vagrant.
#
# @param [String] name Configuration key.
# @param [Boolean] upgrade_safe If this is true, then this configuration
# key is safe to load during an upgrade, meaning that it depends
# on NO Vagrant internal classes. Do _not_ set this to true unless
# you really know what you're doing, since you can cause Vagrant
# to crash (although Vagrant will output a user-friendly error
# message if this were to happen).
def self.config(name=UNSET_VALUE, upgrade_safe=false, &block)
data[:config] ||= Registry.new
# Register a new config class only if a name was given.
if name != UNSET_VALUE
data[:config].register(name.to_sym, &block)
# If we were told this is an upgrade safe configuration class
# then we add it to the set.
if upgrade_safe
data[:config_upgrade_safe] ||= Set.new
data[:config_upgrade_safe].add(name.to_sym)
end
end
# Return the registry
data[:config]
end
# Defines an "easy hook," which gives an easier interface to hook
# into action sequences.
def self.easy_hook(position, name, &block)
if ![:before, :after].include?(position)
raise InvalidEasyHookPosition, "must be :before, :after"
end
# This is the command sent to sequences to insert
insert_method = "insert_#{position}".to_sym
# Create the hook
hook = Easy.create_hook(&block)
# Define an action hook that listens to all actions and inserts
# the hook properly if the sequence contains what we're looking for
action_hook(ALL_ACTIONS) do |seq|
index = seq.index(name)
seq.send(insert_method, index, hook) if index
end
end
# Defines an "easy command," which is a command with limited
# functionality but far less boilerplate required over traditional
# commands. Easy commands let you make basic commands quickly and
# easily.
#
# @param [String] name Name of the command, how it will be invoked
# on the command line.
def self.easy_command(name, &block)
command(name) { Easy.create_command(name, &block) }
end
# Defines an additionally available guest implementation with
# the given key.
#
# @param [String] name Name of the guest.
def self.guest(name=UNSET_VALUE, &block)
data[:guests] ||= Registry.new
# Register a new guest class only if a name was given
data[:guests].register(name.to_sym, &block) if name != UNSET_VALUE
# Return the registry
data[:guests]
end
# Defines an additionally available host implementation with
# the given key.
#
# @param [String] name Name of the host.
def self.host(name=UNSET_VALUE, &block)
data[:hosts] ||= Registry.new
# Register a new host class only if a name was given
data[:hosts].register(name.to_sym, &block) if name != UNSET_VALUE
# Return the registry
data[:hosts]
end
# Registers additional providers to be available.
#
# @param [Symbol] name Name of the provider.
def self.provider(name=UNSET_VALUE, &block)
data[:providers] ||= Registry.new
# Register a new provider class only if a name was given
data[:providers].register(name.to_sym, &block) if name != UNSET_VALUE
# Return the registry
data[:providers]
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
# Returns the internal data associated with this plugin. This
# should NOT be called by the general public.
#
# @return [Hash]
def self.data
@data ||= {}
end
protected
# Sentinel value denoting that a value has not been set.
UNSET_VALUE = Object.new
# Helper method that will set a value if a value is given, or otherwise
# return the already set value.
#
# @param [Symbol] key Key for the data
# @param [Object] value Value to store.
# @return [Object] Stored value.
def self.get_or_set(key, value=UNSET_VALUE)
# If no value is to be set, then return the value we have already set
return data[key] if value.eql?(UNSET_VALUE)
# Otherwise set the value
data[key] = value
end
end
end
end
end

View File

@ -0,0 +1,68 @@
module Vagrant
module Plugin
module V2
# This is the base class for a provider for the V2 API. A provider
# is responsible for creating compute resources to match the needs
# of a Vagrant-configured system.
class Provider
# Initialize the provider to represent the given machine.
#
# @param [Vagrant::Machine] machine The machine that this provider
# is responsible for.
def initialize(machine)
end
# This should return an action callable for the given name.
#
# @param [Symbol] name Name of the action.
# @return [Object] A callable action sequence object, whether it
# is a proc, object, etc.
def action(name)
nil
end
# This method is called if the underying machine ID changes. Providers
# can use this method to load in new data for the actual backing
# machine or to realize that the machine is now gone (the ID can
# become `nil`). No parameters are given, since the underlying machine
# is simply the machine instance given to this object. And no
# return value is necessary.
def machine_id_changed
end
# This should return a hash of information that explains how to
# SSH into the machine. If the machine is not at a point where
# SSH is even possible, then `nil` should be returned.
#
# The general structure of this returned hash should be the
# following:
#
# {
# :host => "1.2.3.4",
# :port => "22",
# :username => "mitchellh",
# :private_key_path => "/path/to/my/key"
# }
#
# **Note:** Vagrant only supports private key based authentication,
# mainly for the reason that there is no easy way to exec into an
# `ssh` prompt with a password, whereas we can pass a private key
# via commandline.
#
# @return [Hash] SSH information. For the structure of this hash
# read the accompanying documentation for this method.
def ssh_info
nil
end
# This should return the state of the machine within this provider.
# The state can be any symbol.
#
# @return [Symbol]
def state
nil
end
end
end
end
end

View File

@ -0,0 +1,50 @@
module Vagrant
module Plugin
module V2
# This is the base class for a provisioner for the V2 API. A provisioner
# is primarily responsible for installing software on a Vagrant guest.
class Provisioner
# The environment which provisioner is running in. This is the
# action environment, not a Vagrant::Environment.
attr_reader :env
# The configuration for this provisioner. This will be an instance of
# the `Config` class which is part of the provisioner.
attr_reader :config
def initialize(env, config)
@env = env
@config = config
end
# This method is expected to return a class that is used for
# configuring the provisioner. This return value is expected to be
# a subclass of {Config}.
#
# @return [Config]
def self.config_class
end
# 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.
#
# No return value is expected.
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
# This is the method called to when the system is being destroyed
# and allows the provisioners to engage in any cleanup tasks necessary.
def cleanup
end
end
end
end
end

View File

@ -5,6 +5,11 @@ require File.expand_path("../../../../base", __FILE__)
describe Vagrant::Config::V1::Loader do
include_context "unit"
before(:each) do
# Force the V1 loader to believe that we are in V1
stub_const("Vagrant::Config::CURRENT_VERSION", "1")
end
describe "empty" do
it "returns an empty configuration object" do
result = described_class.init