Add the "Step" abstraction
This commit is contained in:
parent
833dbf8fc4
commit
49d299956f
|
@ -56,6 +56,7 @@ module Vagrant
|
||||||
#
|
#
|
||||||
class Action
|
class Action
|
||||||
autoload :Environment, 'vagrant/action/environment'
|
autoload :Environment, 'vagrant/action/environment'
|
||||||
|
autoload :Step, 'vagrant/action/step'
|
||||||
autoload :Warden, 'vagrant/action/warden'
|
autoload :Warden, 'vagrant/action/warden'
|
||||||
|
|
||||||
include Util
|
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