Implement multistep
This commit is contained in:
parent
49d299956f
commit
683bbdaa3c
|
@ -56,6 +56,7 @@ module Vagrant
|
||||||
#
|
#
|
||||||
class Action
|
class Action
|
||||||
autoload :Environment, 'vagrant/action/environment'
|
autoload :Environment, 'vagrant/action/environment'
|
||||||
|
autoload :MultiStep, 'vagrant/action/multistep'
|
||||||
autoload :Step, 'vagrant/action/step'
|
autoload :Step, 'vagrant/action/step'
|
||||||
autoload :Warden, 'vagrant/action/warden'
|
autoload :Warden, 'vagrant/action/warden'
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -33,9 +33,15 @@ module Vagrant
|
||||||
|
|
||||||
# Validates that the output matches the specification provided, and
|
# Validates that the output matches the specification provided, and
|
||||||
# raises a RuntimeError if it does not.
|
# 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
|
missing = outputs - value.keys
|
||||||
raise RuntimeError, "Missing output keys: #{missing}" if !missing.empty?
|
raise RuntimeError, "Missing output keys: #{missing}" if !missing.empty?
|
||||||
|
|
||||||
|
return value
|
||||||
end
|
end
|
||||||
|
|
||||||
# This calls the step with the given parameters, and returns a hash
|
# 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
|
# @option options [Boolean] :validate_output Whether to validate the
|
||||||
# output or not.
|
# output or not.
|
||||||
# @return [Hash] Output
|
# @return [Hash] Output
|
||||||
def call(params, options=nil)
|
def call(params={}, options=nil)
|
||||||
options = {
|
options = {
|
||||||
:method => :execute,
|
:method => :execute,
|
||||||
:validate_output => true
|
:validate_output => true
|
||||||
|
@ -61,8 +67,9 @@ module Vagrant
|
||||||
# Call the actual implementation
|
# Call the actual implementation
|
||||||
results = send(options[:method])
|
results = send(options[:method])
|
||||||
|
|
||||||
# Validate the outputs
|
# Validate the outputs if it is enabled and the list of configured
|
||||||
self.class.validate_output(results) if options[:validate_output]
|
# outputs is not empty.
|
||||||
|
results = self.class.process_output(results) if options[:validate_output]
|
||||||
|
|
||||||
# Return the final results
|
# Return the final results
|
||||||
results
|
results
|
||||||
|
|
|
@ -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
|
|
@ -18,7 +18,7 @@ describe Vagrant::Action::Step do
|
||||||
input :foo
|
input :foo
|
||||||
end
|
end
|
||||||
|
|
||||||
expect { step_class.new.call({}) }.to raise_error(ArgumentError)
|
expect { step_class.new.call }.to raise_error(ArgumentError)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "calls a custom method if given" do
|
it "calls a custom method if given" do
|
||||||
|
@ -31,27 +31,39 @@ describe Vagrant::Action::Step do
|
||||||
step_class.new.call({}, :method => :prepare).should == { :foo => 12 }
|
step_class.new.call({}, :method => :prepare).should == { :foo => 12 }
|
||||||
end
|
end
|
||||||
|
|
||||||
it "raises an exception if missing outputs" do
|
describe "outputs" do
|
||||||
step_class = Class.new(described_class) do
|
it "return an empty hash if no outputs are specified" do
|
||||||
output :foo
|
step_class = Class.new(described_class) do
|
||||||
|
def execute
|
||||||
def execute
|
return 12
|
||||||
return :bar => 12
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
step_class.new.call.should == {}
|
||||||
end
|
end
|
||||||
|
|
||||||
expect { step_class.new.call({}) }.to raise_error(RuntimeError)
|
it "raises an exception if missing outputs" do
|
||||||
end
|
step_class = Class.new(described_class) do
|
||||||
|
output :foo
|
||||||
|
|
||||||
it "does nothing if missing outputs but we disabled validating" do
|
def execute
|
||||||
step_class = Class.new(described_class) do
|
return :bar => 12
|
||||||
output :foo
|
end
|
||||||
|
|
||||||
def execute
|
|
||||||
return :bar => 12
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
expect { step_class.new.call }.to raise_error(RuntimeError)
|
||||||
end
|
end
|
||||||
|
|
||||||
step_class.new.call({}, :validate_output => false).should == { :bar => 12 }
|
it "does nothing if missing outputs but we disabled validating" do
|
||||||
|
step_class = Class.new(described_class) do
|
||||||
|
output :foo
|
||||||
|
|
||||||
|
def execute
|
||||||
|
return :bar => 12
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
step_class.new.call({}, :validate_output => false).should == { :bar => 12 }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue