Retain original trigger behavior

These updates allow the after trigger to behave the same as the
original with regards to the execution location of the trigger
within the execution stack.
This commit is contained in:
Chris Roberts 2020-04-03 15:47:00 -07:00
parent 217f2530db
commit 5d70cc3bf2
5 changed files with 64 additions and 4 deletions

View File

@ -16,6 +16,7 @@ module Vagrant
autoload :CleanupDisks, "vagrant/action/builtin/cleanup_disks"
autoload :Confirm, "vagrant/action/builtin/confirm"
autoload :ConfigValidate, "vagrant/action/builtin/config_validate"
autoload :Delayed, "vagrant/action/builtin/delayed"
autoload :DestroyConfirm, "vagrant/action/builtin/destroy_confirm"
autoload :Disk, "vagrant/action/builtin/disk"
autoload :EnvSet, "vagrant/action/builtin/env_set"

View File

@ -247,15 +247,24 @@ module Vagrant
hook_proc.call(hook)
end
# Finally load any action triggers defined
# Finally load any action triggers defined. The action triggers
# are the originally implemented trigger style. They run before
# and after specific provider actions (like :up, :halt, etc) and
# are different from true action triggers
if env[:triggers]
if !env[:triggers].find(env[:raw_action_name], :before, machine_name, :action).empty?
hook.prepend(Vagrant::Action::Builtin::Trigger,
env[:raw_action_name], env[:triggers], :before, :action)
end
if !env[:triggers].find(env[:raw_action_name], :after, machine_name, :action).empty?
hook.append(Vagrant::Action::Builtin::Trigger,
# NOTE: These after triggers need to be delayed before running to
# allow the rest of the call stack to complete before being
# run. The delayed action is prepended to the stack (not appended)
# to ensure it is called first, which results in it properly
# waiting for everything to finish before itself completing.
builder = self.class.build(Vagrant::Action::Builtin::Trigger,
env[:raw_action_name], env[:triggers], :after, :action)
hook.prepend(Vagrant::Action::Builtin::Delayed, builder)
end
end

View File

@ -0,0 +1,26 @@
module Vagrant
module Action
module Builtin
# This class is used to delay execution until the end of
# a configured stack
class Delayed
# @param [Object] callable The object to call (must respond to #call)
def initialize(app, env, callable)
if !callable.respond_to?(:call)
raise TypeError, "Callable argument is expected to respond to `#call`"
end
@app = app
@env = env
@callable = callable
end
def call(env)
# Allow the rest of the call stack to execute
@app.call(env)
# Now call our delayed stack
@callable.call(env)
end
end
end
end
end

View File

@ -700,12 +700,14 @@ describe Vagrant::Action::Builder do
it "should add a trigger action to the end of the stack" do
subject.apply_action_name(env)
expect(subject.stack.last.first).to eq(Vagrant::Action::Builtin::Trigger)
expect(subject.stack.first.first).to eq(Vagrant::Action::Builtin::Delayed)
end
it "should include arguments to the trigger action" do
subject.apply_action_name(env)
args = subject.stack.last[1]
builder = subject.stack.first[1]&.first
expect(builder).not_to be_nil
args = builder.stack.first[1]
expect(args).to include(raw_action_name)
expect(args).to include(timing)
expect(args).to include(:action)

View File

@ -0,0 +1,22 @@
require File.expand_path("../../../../base", __FILE__)
describe Vagrant::Action::Builtin::Delayed do
let(:app) { lambda {|*_|} }
let(:env) { {} }
it "should raise error when callable does not provide #call" do
expect { described_class.new(app, env, true) }.
to raise_error(TypeError)
end
it "should delay executing action to end of stack" do
result = []
one = proc{ |*_| result << :one }
two = proc{ |*_| result << :two }
builder = Vagrant::Action::Builder.build(described_class, two)
builder.use(one)
builder.call(env)
expect(result.first).to eq(:one)
expect(result.last).to eq(:two)
end
end