Add the "Step" abstraction
This commit is contained in:
parent
833dbf8fc4
commit
49d299956f
|
@ -56,6 +56,7 @@ module Vagrant
|
|||
#
|
||||
class Action
|
||||
autoload :Environment, 'vagrant/action/environment'
|
||||
autoload :Step, 'vagrant/action/step'
|
||||
autoload :Warden, 'vagrant/action/warden'
|
||||
|
||||
include Util
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
module Vagrant
|
||||
class Action
|
||||
# A step is a callable action that Vagrant uses to build up
|
||||
# more complex actions sequences.
|
||||
#
|
||||
# A step is really just a reimplementation of a "function"
|
||||
# within the runtime of Ruby. A step advertises parameters
|
||||
# that it requires (inputs), as well as return values (outputs).
|
||||
# The `Step` class handles validating that all the required
|
||||
# parameters are set on the step as well as can validate the
|
||||
# return values.
|
||||
class Step
|
||||
# The keys given to this will be required by the step. Each key
|
||||
# should be a symbol.
|
||||
def self.input(*keys)
|
||||
inputs.concat(keys)
|
||||
end
|
||||
|
||||
# The values that this step provides. Each key should be a symbol.
|
||||
def self.output(*keys)
|
||||
outputs.concat(keys)
|
||||
end
|
||||
|
||||
# This is the array of required keys.
|
||||
def self.inputs
|
||||
@inputs ||= []
|
||||
end
|
||||
|
||||
# The array of keys that are provided by this Step.
|
||||
def self.outputs
|
||||
@outputs ||= []
|
||||
end
|
||||
|
||||
# Validates that the output matches the specification provided, and
|
||||
# raises a RuntimeError if it does not.
|
||||
def self.validate_output(value)
|
||||
missing = outputs - value.keys
|
||||
raise RuntimeError, "Missing output keys: #{missing}" if !missing.empty?
|
||||
end
|
||||
|
||||
# This calls the step with the given parameters, and returns a hash
|
||||
# of the outputs.
|
||||
#
|
||||
# Additional options may be provided via the options hash at the end.
|
||||
#
|
||||
# @param [Hash] params Parameters for the step.
|
||||
# @param [Hash] options Options hash
|
||||
# @option options [Symbol] :method Method to execute.
|
||||
# @option options [Boolean] :validate_output Whether to validate the
|
||||
# output or not.
|
||||
# @return [Hash] Output
|
||||
def call(params, options=nil)
|
||||
options = {
|
||||
:method => :execute,
|
||||
:validate_output => true
|
||||
}.merge(options || {})
|
||||
|
||||
# Set and validate the inputs
|
||||
set_inputs(params)
|
||||
|
||||
# Call the actual implementation
|
||||
results = send(options[:method])
|
||||
|
||||
# Validate the outputs
|
||||
self.class.validate_output(results) if options[:validate_output]
|
||||
|
||||
# Return the final results
|
||||
results
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Sets the parameters for the step.
|
||||
#
|
||||
# This will raise an exception if all the `requires` are not
|
||||
# properly met. Otherwise, the parameters are set as instance variables
|
||||
# on this instance.
|
||||
def set_inputs(params)
|
||||
inputs = self.class.inputs
|
||||
remaining = inputs - params.keys
|
||||
raise ArgumentError, "Missing parameters: #{remaining}" if !remaining.empty?
|
||||
|
||||
inputs.each do |key|
|
||||
instance_variable_set("@#{key}", params[key])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,57 @@
|
|||
require File.expand_path("../../../base", __FILE__)
|
||||
|
||||
describe Vagrant::Action::Step do
|
||||
it "provides the parameters as instance variables" do
|
||||
step_class = Class.new(described_class) do
|
||||
input :foo
|
||||
|
||||
def execute
|
||||
return :value => @foo
|
||||
end
|
||||
end
|
||||
|
||||
step_class.new.call(:foo => 12).should == { :value => 12 }
|
||||
end
|
||||
|
||||
it "raises an exception if not all required parameters are given" do
|
||||
step_class = Class.new(described_class) do
|
||||
input :foo
|
||||
end
|
||||
|
||||
expect { step_class.new.call({}) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "calls a custom method if given" do
|
||||
step_class = Class.new(described_class) do
|
||||
def prepare
|
||||
return :foo => 12
|
||||
end
|
||||
end
|
||||
|
||||
step_class.new.call({}, :method => :prepare).should == { :foo => 12 }
|
||||
end
|
||||
|
||||
it "raises an exception if missing outputs" do
|
||||
step_class = Class.new(described_class) do
|
||||
output :foo
|
||||
|
||||
def execute
|
||||
return :bar => 12
|
||||
end
|
||||
end
|
||||
|
||||
expect { step_class.new.call({}) }.to raise_error(RuntimeError)
|
||||
end
|
||||
|
||||
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
|
Loading…
Reference in New Issue