Implement multistep

This commit is contained in:
Mitchell Hashimoto 2011-12-04 18:09:18 -08:00
parent 49d299956f
commit 683bbdaa3c
5 changed files with 173 additions and 20 deletions

View File

@ -56,6 +56,7 @@ module Vagrant
#
class Action
autoload :Environment, 'vagrant/action/environment'
autoload :MultiStep, 'vagrant/action/multistep'
autoload :Step, 'vagrant/action/step'
autoload :Warden, 'vagrant/action/warden'

View File

@ -0,0 +1,51 @@
module Vagrant
class Action
class MultiStep < Step
def initialize
@steps = []
end
def step(step_class, *extra_inputs)
# Get the options hash and set the defaults
options = {}
options = extra_inputs.pop if extra_inputs.last.kind_of?(Hash)
# Append the step
@steps << [step_class, extra_inputs, options]
end
def call(params=nil)
params ||= {}
# Instantiate all the steps
instances = @steps.map { |s, inputs, options| [s.new, inputs, options] }
# For each step, call it with proper inputs, using the output
# of that call as inputs to the next.
instances.inject(params) do |inputs, data|
step, extra_inputs, options = data
# If there are extra inputs for this step, add them to the
# parameters based on the initial parameters.
extra_inputs.each do |extra_input|
inputs[extra_input] = params[extra_input]
end
# If we have inputs to remap, remap them.
if options[:map]
options[:map].each do |from, to|
# This sets the input to the new key while removing the
# the old key from the same hash. Kind of sneaky, but
# hopefully this comment makes it clear.
inputs[to] = inputs.delete(from)
end
end
# Call the actual step, using the results for the next
# iteration.
step.call(inputs)
end
end
end
end
end

View File

@ -33,9 +33,15 @@ module Vagrant
# Validates that the output matches the specification provided, and
# raises a RuntimeError if it does not.
def self.validate_output(value)
def self.process_output(value)
# The return value must be a Hash, so we just coerce it to that.
value = {} if !value.kind_of?(Hash)
# Verify that we have all the outputs
missing = outputs - value.keys
raise RuntimeError, "Missing output keys: #{missing}" if !missing.empty?
return value
end
# This calls the step with the given parameters, and returns a hash
@ -49,7 +55,7 @@ module Vagrant
# @option options [Boolean] :validate_output Whether to validate the
# output or not.
# @return [Hash] Output
def call(params, options=nil)
def call(params={}, options=nil)
options = {
:method => :execute,
:validate_output => true
@ -61,8 +67,9 @@ module Vagrant
# Call the actual implementation
results = send(options[:method])
# Validate the outputs
self.class.validate_output(results) if options[:validate_output]
# Validate the outputs if it is enabled and the list of configured
# outputs is not empty.
results = self.class.process_output(results) if options[:validate_output]
# Return the final results
results

View File

@ -0,0 +1,82 @@
require File.expand_path("../../../base", __FILE__)
describe Vagrant::Action::MultiStep do
it "should compose a series of steps" do
step_A = Class.new(Vagrant::Action::Step) do
input :obj
output :obj
def execute
@obj << "A"
return :obj => @obj
end
end
step_B = Class.new(Vagrant::Action::Step) do
input :obj
output :result
def execute
return :result => (@obj << "B")
end
end
obj = []
ms = described_class.new
ms.step step_A
ms.step step_B
ms.call(:obj => obj).should == { :result => ["A", "B"] }
end
it "should allow for custom inputs to pass to specific steps" do
step_A = Class.new(Vagrant::Action::Step) do
def execute
# Do nothing.
end
end
step_B = Class.new(Vagrant::Action::Step) do
input :obj
def execute
@obj << "B"
end
end
obj = []
ms = described_class.new
ms.step step_A
ms.step step_B, :obj
ms.call(:obj => obj)
obj.should == ["B"]
end
it "should be able to remap input names" do
step_A = Class.new(Vagrant::Action::Step) do
output :foo
def execute
return :foo => "A"
end
end
step_B = Class.new(Vagrant::Action::Step) do
input :from
output :value
def execute
return :value => @from
end
end
obj = []
ms = described_class.new
ms.step step_A
ms.step step_B, :map => { :foo => :from }
ms.call.should == { :value => "A" }
end
end

View File

@ -18,7 +18,7 @@ describe Vagrant::Action::Step do
input :foo
end
expect { step_class.new.call({}) }.to raise_error(ArgumentError)
expect { step_class.new.call }.to raise_error(ArgumentError)
end
it "calls a custom method if given" do
@ -31,6 +31,17 @@ describe Vagrant::Action::Step do
step_class.new.call({}, :method => :prepare).should == { :foo => 12 }
end
describe "outputs" do
it "return an empty hash if no outputs are specified" do
step_class = Class.new(described_class) do
def execute
return 12
end
end
step_class.new.call.should == {}
end
it "raises an exception if missing outputs" do
step_class = Class.new(described_class) do
output :foo
@ -40,7 +51,7 @@ describe Vagrant::Action::Step do
end
end
expect { step_class.new.call({}) }.to raise_error(RuntimeError)
expect { step_class.new.call }.to raise_error(RuntimeError)
end
it "does nothing if missing outputs but we disabled validating" do
@ -55,3 +66,4 @@ describe Vagrant::Action::Step do
step_class.new.call({}, :validate_output => false).should == { :bar => 12 }
end
end
end