Build the v2 interface, which is just a copy of V1 for now.
This commit is contained in:
parent
efa0a6b8c2
commit
be294e002a
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,5 +1,6 @@
|
|||
module Vagrant
|
||||
module Plugin
|
||||
autoload :V1, "vagrant/plugin/v1"
|
||||
autoload :V2, "vagrant/plugin/v2"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue