Add the "Step" abstraction

This commit is contained in:
Mitchell Hashimoto 2011-12-04 17:14:21 -08:00
parent 833dbf8fc4
commit 49d299956f
3 changed files with 147 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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