From a1cef830e32455f12a299e6955707f0c0255c990 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 8 Aug 2012 21:28:28 -0700 Subject: [PATCH] Add the Communicator plugin API. This allows communication protocols to be defined for the machine. This is how things like SSH will be implemented. --- lib/vagrant.rb | 18 ++-- lib/vagrant/machine.rb | 19 ++++ lib/vagrant/plugin/v1.rb | 1 + lib/vagrant/plugin/v1/communicator.rb | 89 +++++++++++++++++++ .../vagrant/plugin/v1/communicator_test.rb | 9 ++ test/unit/vagrant_test.rb | 1 + 6 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 lib/vagrant/plugin/v1/communicator.rb create mode 100644 test/unit/vagrant/plugin/v1/communicator_test.rb diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 24af56514..550df06b1 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -85,14 +85,16 @@ module Vagrant # These are the various plugin versions and their components in # a lazy loaded Hash-like structure. - c = PLUGIN_COMPONENTS = Registry.new - c.register(:"1") { Plugin::V1::Plugin } - c.register([:"1", :command]) { Plugin::V1::Command } - c.register([:"1", :config]) { Plugin::V1::Config } - c.register([:"1", :guest]) { Plugin::V1::Guest } - c.register([:"1", :host]) { Plugin::V1::Host } - c.register([:"1", :provider]) { Plugin::V1::Provider } - c.register([:"1", :provisioner]) { Plugin::V1::Provisioner } + PLUGIN_COMPONENTS = Registry.new.tap do |c| + c.register(:"1") { Plugin::V1::Plugin } + c.register([:"1", :command]) { Plugin::V1::Command } + c.register([:"1", :communicator]) { Plugin::V1::Communicator } + c.register([:"1", :config]) { Plugin::V1::Config } + c.register([:"1", :guest]) { Plugin::V1::Guest } + c.register([:"1", :host]) { Plugin::V1::Host } + c.register([:"1", :provider]) { Plugin::V1::Provider } + c.register([:"1", :provisioner]) { Plugin::V1::Provisioner } + end # The source root is the path to the root directory of # the Vagrant gem. diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index b8b0438ed..5aa505f69 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -92,6 +92,25 @@ module Vagrant @env.action_runner.run(callable, env) end + # Returns a communication object for executing commands on the remote + # machine. Note that the _exact_ semantics of this are up to the + # communication provider itself. Despite this, the semantics are expected + # to be consistent across operating systems. For example, all linux-based + # systems should have similar communication (usually a shell). All + # Windows systems should have similar communication as well. Therefore, + # prior to communicating with the machine, users of this method are + # expected to check the guest OS to determine their behavior. + # + # This method will _always_ return some valid communication object. + # The `ready?` API can be used on the object to check if communication + # is actually ready. + # + # @return [Object] + def communicate + # For now, we always return SSH. In the future, we'll abstract + # this and allow plugins to define new methods of communication. + end + # This sets the unique ID associated with this machine. This will # persist this ID so that in the future Vagrant will be able to find # this machine again. The unique ID must be absolutely unique to the diff --git a/lib/vagrant/plugin/v1.rb b/lib/vagrant/plugin/v1.rb index c43e8d687..55b23e5eb 100644 --- a/lib/vagrant/plugin/v1.rb +++ b/lib/vagrant/plugin/v1.rb @@ -6,6 +6,7 @@ module Vagrant module Plugin module V1 autoload :Command, "vagrant/plugin/v1/command" + autoload :Communicator, "vagrant/plugin/v1/communicator" autoload :Config, "vagrant/plugin/v1/config" autoload :Guest, "vagrant/plugin/v1/guest" autoload :Host, "vagrant/plugin/v1/host" diff --git a/lib/vagrant/plugin/v1/communicator.rb b/lib/vagrant/plugin/v1/communicator.rb new file mode 100644 index 000000000..517f3e417 --- /dev/null +++ b/lib/vagrant/plugin/v1/communicator.rb @@ -0,0 +1,89 @@ +module Vagrant + module Plugin + module V1 + # 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 + end + end + end +end diff --git a/test/unit/vagrant/plugin/v1/communicator_test.rb b/test/unit/vagrant/plugin/v1/communicator_test.rb new file mode 100644 index 000000000..a10e5a88d --- /dev/null +++ b/test/unit/vagrant/plugin/v1/communicator_test.rb @@ -0,0 +1,9 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Plugin::V1::Communicator do + let(:machine) { Object.new } + + it "should not match by default" do + described_class.match?(machine).should_not be + end +end diff --git a/test/unit/vagrant_test.rb b/test/unit/vagrant_test.rb index 6e555af1f..5c61a3af2 100644 --- a/test/unit/vagrant_test.rb +++ b/test/unit/vagrant_test.rb @@ -13,6 +13,7 @@ describe Vagrant do it "returns the proper components for version 1" do described_class.plugin("1", :command).should == Vagrant::Plugin::V1::Command + described_class.plugin("1", :communicator).should == Vagrant::Plugin::V1::Communicator described_class.plugin("1", :config).should == Vagrant::Plugin::V1::Config described_class.plugin("1", :guest).should == Vagrant::Plugin::V1::Guest described_class.plugin("1", :host).should == Vagrant::Plugin::V1::Host