From 90517a0f9bc28b70c3819f2d6d86596e9779f0ad Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Jul 2012 23:56:47 -0700 Subject: [PATCH] The `Call` built-in middleware allows for conditional MW sequences. Read the documentation for more information. --- lib/vagrant/action.rb | 7 +- lib/vagrant/action/builtin/call.rb | 54 +++++++++++++++ lib/vagrant/action/runner.rb | 4 ++ plugins/providers/virtualbox/action.rb | 16 ++++- .../providers/virtualbox/action/created.rb | 20 ++++++ test/unit/vagrant/action/builtin/call_test.rb | 66 +++++++++++++++++++ test/unit/vagrant/action/runner_test.rb | 12 ++++ 7 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 lib/vagrant/action/builtin/call.rb create mode 100644 plugins/providers/virtualbox/action/created.rb create mode 100644 test/unit/vagrant/action/builtin/call_test.rb diff --git a/lib/vagrant/action.rb b/lib/vagrant/action.rb index 8b816ae3d..aeb44ce29 100644 --- a/lib/vagrant/action.rb +++ b/lib/vagrant/action.rb @@ -2,7 +2,6 @@ require 'vagrant/action/builder' module Vagrant module Action - autoload :Builtin, 'vagrant/action/builtin' autoload :Environment, 'vagrant/action/environment' autoload :Runner, 'vagrant/action/runner' autoload :Warden, 'vagrant/action/warden' @@ -13,6 +12,12 @@ module Vagrant autoload :Verify, 'vagrant/action/box/verify' end + # Builtin contains middleware classes that are shipped with Vagrant-core + # and are thus available to all plugins as a "standard library" of sorts. + module Builtin + autoload :Call, "vagrant/action/builtin/call" + end + module Env autoload :Set, 'vagrant/action/env/set' end diff --git a/lib/vagrant/action/builtin/call.rb b/lib/vagrant/action/builtin/call.rb new file mode 100644 index 000000000..1b7065be7 --- /dev/null +++ b/lib/vagrant/action/builtin/call.rb @@ -0,0 +1,54 @@ +module Vagrant + module Action + module Builtin + # This middleware class allows a sort of "conditional" run within + # a single middlware sequence. It takes another middleware runnable, + # runs it with the same environment, then yields the result to a block, + # allowing that block to determine the next course of action in the + # middleware sequence. + # + # The first argument to this middleware sequence is anywhere middleware + # runnable, whether it be a class, lambda, or something else that + # responds to `call`. This middleware runnable is run with the same + # environment as this class. The "result" of the run is expected to be + # placed in `env[:result]`. + # + # After running, {Call} takes `env[:result]` and yields it to a block + # given to initialize the class, along with an instance of {Builder}. + # The result is used to build up a new sequence on the given builder. + # This builder is then run. + class Call + # For documentation, read the description of the {Call} class. + # + # @param [Object] callable A valid middleware runnable object. This + # can be a class, a lambda, or an object that responds to `call`. + # @yield [result, builder] This block is expected to build on `builder` + # which is the next middleware sequence that will be run. + def initialize(app, env, callable, &block) + raise ArgumentError, "A block must be given to Call" if !block + + @app = app + @callable = callable + @block = block + end + + def call(env) + runner = Runner.new + + # Run our callable with our environment + new_env = runner.run(@callable, env) + + # Build our new builder based on the result + builder = Builder.new + @block.call(new_env[:result], builder) + + # Run the result with our new environment + final_env = runner.run(builder, new_env) + + # Call the next step using our final environment + @app.call(final_env) + end + end + end + end +end diff --git a/lib/vagrant/action/runner.rb b/lib/vagrant/action/runner.rb index 832a38872..b25796b38 100644 --- a/lib/vagrant/action/runner.rb +++ b/lib/vagrant/action/runner.rb @@ -45,6 +45,10 @@ module Vagrant # We place a process lock around every action that is called @logger.info("Running action: #{callable_id}") Util::Busy.busy(int_callback) { callable.call(environment) } + + # Return the environment in case there are things in there that + # the caller wants to use. + environment end end end diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index 29b5e7a54..701868529 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -5,14 +5,26 @@ module VagrantPlugins module Action autoload :CheckAccessible, File.expand_path("../action/check_accessible", __FILE__) autoload :CheckVirtualbox, File.expand_path("../action/check_virtualbox", __FILE__) + autoload :Created, File.expand_path("../action/created", __FILE__) + + # Include the built-in modules so that we can use them as top-level + # things. + include Vagrant::Action::Builtin # This is the action that is primarily responsible for completely # freeing the resources of the underlying virtual machine. def self.action_destroy Vagrant::Action::Builder.new.tap do |b| b.use CheckVirtualbox - b.use Vagrant::Action::General::Validate - b.use CheckAccessible + b.use Call, Created do |result, b2| + # `result` is a boolean true/false of whether the VM is created or + # not. So if the VM _is_ created, then we continue with the + # destruction. + if result + b2.use Vagrant::Action::General::Validate + b2.use CheckAccessible + end + end end end end diff --git a/plugins/providers/virtualbox/action/created.rb b/plugins/providers/virtualbox/action/created.rb new file mode 100644 index 000000000..804801459 --- /dev/null +++ b/plugins/providers/virtualbox/action/created.rb @@ -0,0 +1,20 @@ +module VagrantPlugins + module ProviderVirtualBox + module Action + class Created + def initialize(app, env) + @app = app + end + + def call(env) + # Set the result to be true if the machine is created. + env[:result] = env[:machine].state != :not_created + + # Call the next if we have one (but we shouldn't, since this + # middleware is built to run with the Call-type middlewares) + @app.call(env) + end + end + end + end +end diff --git a/test/unit/vagrant/action/builtin/call_test.rb b/test/unit/vagrant/action/builtin/call_test.rb new file mode 100644 index 000000000..1dd8ea1cd --- /dev/null +++ b/test/unit/vagrant/action/builtin/call_test.rb @@ -0,0 +1,66 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Action::Builtin::Call do + let(:app) { lambda { |env| } } + let(:env) { {} } + + it "should yield the result to the block" do + received = nil + + callable = lambda do |env| + env[:result] = "value" + end + + described_class.new(app, env, callable) do |result, builder| + received = result + end.call({}) + + received.should == "value" + end + + it "should give a nil result if no result is given" do + received = 42 + callable = lambda { |env| } + + described_class.new(app, env, callable) do |result, builder| + received = result + end.call({}) + + received.should be_nil + end + + it "should call the callable with the original environment" do + received = nil + callable = lambda { |env| received = env[:foo] } + + described_class.new(app, env, callable) do |result, builder| + # Nothing. + end.call({ :foo => :bar }) + + received.should == :bar + end + + it "should call the next builder" do + received = nil + callable = lambda { |env| } + next_step = lambda { |env| received = "value" } + + described_class.new(app, env, callable) do |result, builder| + builder.use next_step + end.call({}) + + received.should == "value" + end + + it "should call the next builder with the original environment" do + received = nil + callable = lambda { |env| } + next_step = lambda { |env| received = env[:foo] } + + described_class.new(app, env, callable) do |result, builder| + builder.use next_step + end.call({ :foo => :bar }) + + received.should == :bar + end +end diff --git a/test/unit/vagrant/action/runner_test.rb b/test/unit/vagrant/action/runner_test.rb index 3d4a6af13..2f5ab95dd 100644 --- a/test/unit/vagrant/action/runner_test.rb +++ b/test/unit/vagrant/action/runner_test.rb @@ -25,6 +25,18 @@ describe Vagrant::Action::Runner do expect { instance.run(callable) }.to raise_error(Exception, "BOOM") end + it "should return the resulting environment" do + callable = lambda do |env| + env[:data] = "value" + + # Return nil so we can make sure it isn't using this return value + nil + end + + result = instance.run(callable) + result[:data].should == "value" + end + it "should pass options into hash given to callable" do result = nil callable = lambda do |env|