diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 8d47d5e60..5c7c1fb01 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -53,7 +53,8 @@ require 'openssl' # Always make the version available require 'vagrant/version' -Log4r::Logger.new("vagrant::global").info("Vagrant version: #{Vagrant::VERSION}") +global_logger = Log4r::Logger.new("vagrant::global") +global_logger.info("Vagrant version: #{Vagrant::VERSION}") module Vagrant autoload :Action, 'vagrant/action' @@ -114,7 +115,7 @@ module Vagrant # @return [Class] def self.plugin(version) # We only support version 1 right now. - return Plugin::V1 if version == "1" + return Plugin::V1::Plugin if version == "1" # Raise an error that the plugin version is invalid raise ArgumentError, "Invalid plugin version API: #{version}" @@ -137,7 +138,7 @@ module Vagrant end end -# # Default I18n to load the en locale +# Default I18n to load the en locale I18n.load_path << File.expand_path("templates/locales/en.yml", Vagrant.source_root) # A lambda that knows how to load plugins from a single directory. @@ -149,6 +150,7 @@ plugin_load_proc = lambda do |directory| # that up. plugin_file = directory.join("plugin.rb") if plugin_file.file? + global_logger.debug("Loading core plugin: #{plugin_file}") load(plugin_file) next true end diff --git a/lib/vagrant/plugin/v1.rb b/lib/vagrant/plugin/v1.rb index 1030d80da..b6de22963 100644 --- a/lib/vagrant/plugin/v1.rb +++ b/lib/vagrant/plugin/v1.rb @@ -1,275 +1,11 @@ require "log4r" +require "vagrant/plugin/v1/errors" + module Vagrant module Plugin - # The superclass for version 1 plugins. - class V1 - # 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 - - # Special marker that can be used for action hooks that matches - # all action sequences. - ALL_ACTIONS = :__all_actions__ - - LOGGER = Log4r::Logger.new("vagrant::plugin::v1") - - # Returns a list of registered plugins for this version. - # - # @return [Array] - def self.registered - @registry || [] - 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 - register! 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 - - # The given block will be called when this plugin is activated. The - # activation block should be used to load any of the classes used by - # the plugin other than the plugin definition itself. Vagrant only - # guarantees that the plugin definition will remain backwards - # compatible, but not any other classes (such as command base classes - # or so on). Therefore, to avoid crashing future versions of Vagrant, - # these classes should be placed in separate files and loaded in the - # activation block. - def self.activated(&block) - data[:activation_block] = block if block_given? - data[:activation_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 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. - def self.config(name=UNSET_VALUE, &block) - data[:config] ||= Registry.new - - # Register a new config class only if a name was given. - data[:config].register(name.to_sym, &block) if name != UNSET_VALUE - - # 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 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 - - # Activates the current plugin. This shouldn't be called publicly. - def self.activate! - if !data[:activated_called] - LOGGER.info("Activating plugin: #{self.name}") - data[:activated_called] = true - - # Call the activation block - block = activated - block.call if block - end - end - - # Registers the plugin. This makes the plugin actually work with - # Vagrant. Prior to registering, the plugin is merely a skeleton. - # - # This shouldn't be called by the general public. Plugins are automatically - # registered when they are given a name. - def self.register!(plugin=nil) - plugin ||= self - - # Register only on the root class - return V1.register!(plugin) if self != V1 - - # Register it into the list - @registry ||= [] - if !@registry.include?(plugin) - LOGGER.info("Registered plugin: #{plugin.name}") - @registry << plugin - end - end - - # This unregisters the plugin. Note that to re-register the plugin - # you must call `register!` again. - def self.unregister!(plugin=nil) - plugin ||= self - - # Unregister only on the root class - return V1.unregister!(plugin) if self != V1 - - # Unregister it from the registry - @registry ||= [] - if @registry.include?(plugin) - LOGGER.info("Unregistered: #{plugin.name}") - @registry.delete(plugin) - end - end - - protected - - # Sentinel value denoting that a value has not been set. - UNSET_VALUE = Object.new - - # Returns the internal data associated with this plugin. - # - # @return [Hash] - def self.data - @data ||= {} - end - - # 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 + module V1 + autoload :Plugin, "vagrant/plugin/v1/plugin" end end end diff --git a/lib/vagrant/plugin/v1/errors.rb b/lib/vagrant/plugin/v1/errors.rb new file mode 100644 index 000000000..09feb5550 --- /dev/null +++ b/lib/vagrant/plugin/v1/errors.rb @@ -0,0 +1,18 @@ +# This file contains all the errors that the V1 plugin interface +# may throw. + +module Vagrant + module Plugin + module V1 + # 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 diff --git a/lib/vagrant/plugin/v1/plugin.rb b/lib/vagrant/plugin/v1/plugin.rb new file mode 100644 index 000000000..decc7aaae --- /dev/null +++ b/lib/vagrant/plugin/v1/plugin.rb @@ -0,0 +1,272 @@ +require "log4r" + +module Vagrant + module Plugin + module V1 + # This is the superclass for all V1 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::v1::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 + + # Returns a list of registered plugins for this version. + # + # @return [Array] + def self.registered + @registry || [] + 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 + register! 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 + + # The given block will be called when this plugin is activated. The + # activation block should be used to load any of the classes used by + # the plugin other than the plugin definition itself. Vagrant only + # guarantees that the plugin definition will remain backwards + # compatible, but not any other classes (such as command base classes + # or so on). Therefore, to avoid crashing future versions of Vagrant, + # these classes should be placed in separate files and loaded in the + # activation block. + def self.activated(&block) + data[:activation_block] = block if block_given? + data[:activation_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 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. + def self.config(name=UNSET_VALUE, &block) + data[:config] ||= Registry.new + + # Register a new config class only if a name was given. + data[:config].register(name.to_sym, &block) if name != UNSET_VALUE + + # 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 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 + + # Activates the current plugin. This shouldn't be called publicly. + def self.activate! + if !data[:activated_called] + LOGGER.info("Activating plugin: #{self.name}") + data[:activated_called] = true + + # Call the activation block + block = activated + block.call if block + end + end + + # Registers the plugin. This makes the plugin actually work with + # Vagrant. Prior to registering, the plugin is merely a skeleton. + # + # This shouldn't be called by the general public. Plugins are automatically + # registered when they are given a name. + def self.register!(plugin=nil) + plugin ||= self + + # Register only on the root class + return ROOT_CLASS.register!(plugin) if self != ROOT_CLASS + + # Register it into the list + @registry ||= [] + if !@registry.include?(plugin) + LOGGER.info("Registered plugin: #{plugin.name}") + @registry << plugin + end + end + + # This unregisters the plugin. Note that to re-register the plugin + # you must call `register!` again. + def self.unregister!(plugin=nil) + plugin ||= self + + # Unregister only on the root class + return ROOT_CLASS.unregister!(plugin) if self != ROOT_CLASS + + # Unregister it from the registry + @registry ||= [] + if @registry.include?(plugin) + LOGGER.info("Unregistered: #{plugin.name}") + @registry.delete(plugin) + end + end + + protected + + # Sentinel value denoting that a value has not been set. + UNSET_VALUE = Object.new + + # Returns the internal data associated with this plugin. + # + # @return [Hash] + def self.data + @data ||= {} + end + + # 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 diff --git a/test/unit/vagrant/plugin/v1_test.rb b/test/unit/vagrant/plugin/v1/plugin_test.rb similarity index 98% rename from test/unit/vagrant/plugin/v1_test.rb rename to test/unit/vagrant/plugin/v1/plugin_test.rb index e54757064..e79f75b9a 100644 --- a/test/unit/vagrant/plugin/v1_test.rb +++ b/test/unit/vagrant/plugin/v1/plugin_test.rb @@ -1,6 +1,6 @@ -require File.expand_path("../../../base", __FILE__) +require File.expand_path("../../../../base", __FILE__) -describe Vagrant::Plugin::V1 do +describe Vagrant::Plugin::V1::Plugin do after(:each) do # We want to make sure that the registered plugins remains empty # after each test. diff --git a/test/unit/vagrant_test.rb b/test/unit/vagrant_test.rb index 5dd502aa8..2afba68d2 100644 --- a/test/unit/vagrant_test.rb +++ b/test/unit/vagrant_test.rb @@ -7,7 +7,7 @@ describe Vagrant do describe "plugin superclass" do it "returns the proper class for version 1" do - described_class.plugin("1").should == Vagrant::Plugin::V1 + described_class.plugin("1").should == Vagrant::Plugin::V1::Plugin end it "raises an exception if an unsupported version is given" do