Merge pull request #9713 from briancain/vagrant-triggers-config

Integrate vagrant-triggers plugin functionality into core Vagrant
This commit is contained in:
Brian Cain 2018-04-24 11:35:04 -07:00 committed by GitHub
commit 5643ba0c7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1634 additions and 2 deletions

View File

@ -776,6 +776,18 @@ module Vagrant
error_key(:synced_folder_unusable) error_key(:synced_folder_unusable)
end end
class TriggersGuestNotRunning < VagrantError
error_key(:triggers_guest_not_running)
end
class TriggersNoBlockGiven < VagrantError
error_key(:triggers_no_block_given)
end
class TriggersNoStageGiven < VagrantError
error_key(:triggers_no_stage_given)
end
class UIExpectsTTY < VagrantError class UIExpectsTTY < VagrantError
error_key(:ui_expects_tty) error_key(:ui_expects_tty)
end end

View File

@ -149,6 +149,8 @@ module Vagrant
# Output a bunch of information about this machine in # Output a bunch of information about this machine in
# machine-readable format in case someone is listening. # machine-readable format in case someone is listening.
@ui.machine("metadata", "provider", provider_name) @ui.machine("metadata", "provider", provider_name)
@triggers = Vagrant::Plugin::V2::Trigger.new(@env, @config.trigger, self)
end end
# This calls an action on the provider. The provider may or may not # This calls an action on the provider. The provider may or may not
@ -159,6 +161,7 @@ module Vagrant
# as extra data set on the environment hash for the middleware # as extra data set on the environment hash for the middleware
# runner. # runner.
def action(name, opts=nil) def action(name, opts=nil)
@triggers.fire_triggers(name, :before, @name.to_s)
@logger.info("Calling action: #{name} on provider #{@provider}") @logger.info("Calling action: #{name} on provider #{@provider}")
opts ||= {} opts ||= {}
@ -185,7 +188,7 @@ module Vagrant
locker = @env.method(:lock) if lock && !name.to_s.start_with?("ssh") locker = @env.method(:lock) if lock && !name.to_s.start_with?("ssh")
# Lock this machine for the duration of this action # Lock this machine for the duration of this action
locker.call("machine-action-#{id}") do return_env = locker.call("machine-action-#{id}") do
# Get the callable from the provider. # Get the callable from the provider.
callable = @provider.action(name) callable = @provider.action(name)
@ -203,6 +206,10 @@ module Vagrant
ui.machine("action", name.to_s, "end") ui.machine("action", name.to_s, "end")
action_result action_result
end end
@triggers.fire_triggers(name, :after, @name.to_s)
# preserve returning environment after machine action runs
return return_env
rescue Errors::EnvironmentLockedError rescue Errors::EnvironmentLockedError
raise Errors::MachineActionLockedError, raise Errors::MachineActionLockedError,
action: name, action: name,

View File

@ -19,6 +19,7 @@ module Vagrant
autoload :Push, "vagrant/plugin/v2/push" autoload :Push, "vagrant/plugin/v2/push"
autoload :Provisioner, "vagrant/plugin/v2/provisioner" autoload :Provisioner, "vagrant/plugin/v2/provisioner"
autoload :SyncedFolder, "vagrant/plugin/v2/synced_folder" autoload :SyncedFolder, "vagrant/plugin/v2/synced_folder"
autoload :Trigger, "vagrant/plugin/v2/trigger"
end end
end end
end end

View File

@ -45,7 +45,7 @@ module Vagrant
def parse_options(opts=nil) def parse_options(opts=nil)
# make sure optparse doesn't use POSIXLY_CORRECT parsing # make sure optparse doesn't use POSIXLY_CORRECT parsing
ENV["POSIXLY_CORRECT"] = nil ENV["POSIXLY_CORRECT"] = nil
# Creating a shallow copy of the arguments so the OptionParser # Creating a shallow copy of the arguments so the OptionParser
# doesn't destroy the originals. # doesn't destroy the originals.
argv = @argv.dup argv = @argv.dup

View File

@ -0,0 +1,242 @@
require 'fileutils'
require 'log4r'
require 'shellwords'
require Vagrant.source_root.join("plugins/provisioners/shell/provisioner")
require "vagrant/util/subprocess"
require "vagrant/util/platform"
require "vagrant/util/powershell"
module Vagrant
module Plugin
module V2
class Trigger
# @return [Kernel_V2::Config::Trigger]
attr_reader :config
# This class is responsible for setting up basic triggers that were
# defined inside a Vagrantfile.
#
# @param [Vagrant::Environment] env Vagrant environment
# @param [Kernel_V2::TriggerConfig] config Trigger configuration
# @param [Vagrant::Machine] machine Active Machine
def initialize(env, config, machine)
@env = env
@config = config
@machine = machine
@logger = Log4r::Logger.new("vagrant::trigger::#{self.class.to_s.downcase}")
end
# Fires all triggers, if any are defined for the action and guest
#
# @param [Symbol] action Vagrant command to fire trigger on
# @param [Symbol] stage :before or :after
# @param [String] guest_name The guest that invoked firing the triggers
def fire_triggers(action, stage, guest_name)
# get all triggers matching action
triggers = []
if stage == :before
triggers = config.before_triggers.select do |t|
t.command == action || (t.command == :all && !t.ignore.include?(action))
end
elsif stage == :after
triggers = config.after_triggers.select do |t|
t.command == action || (t.command == :all && !t.ignore.include?(action))
end
else
raise Errors::TriggersNoStageGiven,
action: action,
stage: stage,
guest_name: guest_name
end
triggers = filter_triggers(triggers, guest_name)
if !triggers.empty?
@logger.info("Firing trigger for action #{action} on guest #{guest_name}")
@machine.ui.info(I18n.t("vagrant.trigger.start", stage: stage, action: action))
fire(triggers, guest_name)
end
end
protected
#-------------------------------------------------------------------
# Internal methods, don't call these.
#-------------------------------------------------------------------
# Filters triggers to be fired based on configured restraints
#
# @param [Array] triggers An array of triggers to be filtered
# @param [String] guest_name The name of the current guest
# @return [Array] The filtered array of triggers
def filter_triggers(triggers, guest_name)
# look for only_on trigger constraint and if it doesn't match guest
# name, throw it away also be sure to preserve order
filter = triggers.dup
filter.each do |trigger|
index = nil
match = false
if trigger.only_on
trigger.only_on.each do |o|
if o.match(guest_name)
# trigger matches on current guest, so we're fine to use it
match = true
break
end
end
# no matches found, so don't use trigger for guest
index = triggers.index(trigger) unless match == true
end
if index
@logger.debug("Trigger #{trigger.id} will be ignored for #{guest_name}")
triggers.delete_at(index)
end
end
return triggers
end
# Fires off all triggers in the given array
#
# @param [Array] triggers An array of triggers to be fired
def fire(triggers, guest_name)
# ensure on_error is respected by exiting or continuing
triggers.each do |trigger|
@logger.debug("Running trigger #{trigger.id}...")
if trigger.name
@machine.ui.info(I18n.t("vagrant.trigger.fire_with_name",
name: trigger.name))
else
@machine.ui.info(I18n.t("vagrant.trigger.fire"))
end
if trigger.info
info(trigger.info)
end
if trigger.warn
warn(trigger.warn)
end
if trigger.run
run(trigger.run, trigger.on_error)
end
if trigger.run_remote
run_remote(trigger.run_remote, trigger.on_error)
end
end
end
# Prints the given message at info level for a trigger
#
# @param [String] message The string to be printed
def info(message)
@machine.ui.info(message)
end
# Prints the given message at warn level for a trigger
#
# @param [String] message The string to be printed
def warn(message)
@machine.ui.warn(message)
end
# Runs a script on a guest
#
# @param [Provisioners::Shell::Config] config A Shell provisioner config
def run(config, on_error)
if config.inline
cmd = Shellwords.split(config.inline)
@machine.ui.detail(I18n.t("vagrant.trigger.run.inline", command: config.inline))
else
cmd = File.expand_path(config.path, @env.root_path)
cmd << " #{config.args.join(' ' )}" if config.args
cmd = Shellwords.split(cmd)
@machine.ui.detail(I18n.t("vagrant.trigger.run.script", path: config.path))
end
# Pick an execution method to run the script or inline string with
# Default to Subprocess::Execute
exec_method = Vagrant::Util::Subprocess.method(:execute)
if Vagrant::Util::Platform.windows?
if config.inline
exec_method = Vagrant::Util::PowerShell.method(:execute_inline)
else
exec_method = Vagrant::Util::PowerShell.method(:execute)
end
end
begin
result = exec_method.call(*cmd, :notify => [:stdout, :stderr]) do |type,data|
options = {}
case type
when :stdout
options[:color] = :green if !config.keep_color
when :stderr
options[:color] = :red if !config.keep_color
end
@machine.ui.detail(data, options)
end
rescue => e
@machine.ui.error(I18n.t("vagrant.errors.triggers_run_fail"))
@machine.ui.error(e.message)
if on_error == :halt
@logger.debug("Trigger run encountered an error. Halting on error...")
raise e
else
@logger.debug("Trigger run encountered an error. Continuing on anyway...")
@machine.ui.warn(I18n.t("vagrant.trigger.on_error_continue"))
end
end
end
# Runs a script on the guest
#
# @param [ShellProvisioner/Config] config A Shell provisioner config
def run_remote(config, on_error)
unless @machine.state.id == :running
if on_error == :halt
raise Errors::TriggersGuestNotRunning,
machine_name: @machine.name,
state: @machine.state.id
else
@machine.ui.error(I18n.t("vagrant.errors.triggers_guest_not_running",
machine_name: @machine.name,
state: @machine.state.id))
@machine.ui.warn(I18n.t("vagrant.trigger.on_error_continue"))
return
end
end
prov = VagrantPlugins::Shell::Provisioner.new(@machine, config)
begin
prov.provision
rescue => e
@machine.ui.error(I18n.t("vagrant.errors.triggers_run_fail"))
if on_error == :halt
@logger.debug("Trigger run encountered an error. Halting on error...")
raise e
else
@logger.debug("Trigger run encountered an error. Continuing on anyway...")
@machine.ui.error(e.message)
end
end
end
end
end
end
end

View File

@ -87,6 +87,27 @@ module Vagrant
return r.stdout.chomp return r.stdout.chomp
end end
# Execute a powershell command and return a result
#
# @param [String] command PowerShell command to execute.
# @param [Hash] opts A collection of options for subprocess::execute
# @param [Block] block Ruby block
def self.execute_inline(*command, **opts, &block)
validate_install!
c = [
executable,
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command",
command
].flatten.compact
c << opts
Subprocess.execute(*c, &block)
end
# Returns the version of PowerShell that is installed. # Returns the version of PowerShell that is installed.
# #
# @return [String] # @return [String]

View File

@ -0,0 +1,201 @@
require "vagrant"
require File.expand_path("../vm_trigger", __FILE__)
module VagrantPlugins
module Kernel_V2
class TriggerConfig < Vagrant.plugin("2", :config)
# The TriggerConfig class is what gets called when a user
# defines a new trigger in their Vagrantfile. The two entry points are
# either `config.trigger.before` or `config.trigger.after`.
def initialize
@logger = Log4r::Logger.new("vagrant::config::trigger")
# Internal State
@_before_triggers = [] # An array of VagrantConfigTrigger objects
@_after_triggers = [] # An array of VagrantConfigTrigger objects
end
#-------------------------------------------------------------------
# Trigger before/after functions
#-------------------------------------------------------------------
#
# Commands are expected to be ether:
# - splat
# + config.trigger.before :up, :destroy, :halt do |trigger|....
# - array
# + config.trigger.before [:up, :destroy, :halt] do |trigger|....
#
# Config is expected to be given as a block, or the last parameter as a hash
#
# - block
# + config.trigger.before :up, :destroy, :halt do |trigger|
# trigger.option = "option"
# end
# - hash
# + config.trigger.before :up, :destroy, :halt, options: "option"
# Reads in and parses Vagrant command whitelist and settings for a defined
# trigger
#
# @param [Symbol] command Vagrant command to create trigger on
# @param [Block] block The defined before block
def before(*command, &block)
command.flatten!
blk = block
if !block_given? && command.last.is_a?(Hash)
# We were given a hash rather than a block,
# so the last element should be the "config block"
# and the rest are commands for the trigger
blk = command.pop
elsif !block_given?
raise Vagrant::Errors::TriggersNoBlockGiven,
command: command
end
command.each do |cmd|
trigger = create_trigger(cmd, blk)
@_before_triggers << trigger
end
end
# Reads in and parses Vagrant command whitelist and settings for a defined
# trigger
#
# @param [Symbol] command Vagrant command to create trigger on
# @param [Block] block The defined after block
def after(*command, &block)
command.flatten!
blk = block
if !block_given? && command.last.is_a?(Hash)
# We were given a hash rather than a block,
# so the last element should be the "config block"
# and the rest are commands for the trigger
blk = command.pop
elsif !block_given?
raise Vagrant::Errors::TriggersNoBlockGiven,
command: command
end
command.each do |cmd|
trigger = create_trigger(cmd, blk)
@_after_triggers << trigger
end
end
#-------------------------------------------------------------------
# Internal methods, don't call these.
#-------------------------------------------------------------------
# Creates a new trigger config. If a block is given, parse that block
# by calling it with the created trigger. Otherwise set the options if it's
# a hash.
#
# @param [Symbol] command Vagrant command to create trigger on
# @param [Block] block The defined config block
# @return [VagrantConfigTrigger]
def create_trigger(command, block)
trigger = VagrantConfigTrigger.new(command)
if block.is_a?(Hash)
trigger.set_options(block)
else
block.call(trigger, VagrantConfigTrigger)
end
return trigger
end
def merge(other)
super.tap do |result|
new_before_triggers = []
new_after_triggers = []
other_defined_before_triggers = other.instance_variable_get(:@_before_triggers)
other_defined_after_triggers = other.instance_variable_get(:@_after_triggers)
@_before_triggers.each do |bt|
other_bft = other_defined_before_triggers.find { |o| bt.id == o.id }
if other_bft
# Override, take it
other_bft = bt.merge(other_bft)
# Preserve order, always
bt = other_bft
other_defined_before_triggers.delete(other_bft)
end
new_before_triggers << bt.dup
end
other_defined_before_triggers.each do |obt|
new_before_triggers << obt.dup
end
result.instance_variable_set(:@_before_triggers, new_before_triggers)
@_after_triggers.each do |at|
other_aft = other_defined_after_triggers.find { |o| at.id == o.id }
if other_aft
# Override, take it
other_aft = at.merge(other_aft)
# Preserve order, always
at = other_aft
other_defined_after_triggers.delete(other_aft)
end
new_after_triggers << at.dup
end
other_defined_after_triggers.each do |oat|
new_after_triggers << oat.dup
end
result.instance_variable_set(:@_after_triggers, new_after_triggers)
end
end
# Iterates over all defined triggers and finalizes their config objects
def finalize!
if !@_before_triggers.empty?
@_before_triggers.map { |t| t.finalize! }
end
if !@_after_triggers.empty?
@_after_triggers.map { |t| t.finalize! }
end
end
# Validate Trigger Arrays
def validate(machine)
errors = _detected_errors
@_before_triggers.each do |bt|
error = bt.validate(machine)
errors.concat error if !error.empty?
end
@_after_triggers.each do |at|
error = at.validate(machine)
errors.concat error if !error.empty?
end
{"trigger" => errors}
end
# return [Array]
def before_triggers
@_before_triggers
end
# return [Array]
def after_triggers
@_after_triggers
end
# The String representation of this Trigger.
#
# @return [String]
def to_s
"trigger"
end
end
end
end

View File

@ -0,0 +1,204 @@
require 'log4r'
require Vagrant.source_root.join('plugins/provisioners/shell/config')
module VagrantPlugins
module Kernel_V2
# Represents a single configured provisioner for a VM.
class VagrantConfigTrigger < Vagrant.plugin("2", :config)
# Defaults
DEFAULT_ON_ERROR = :halt
#-------------------------------------------------------------------
# Config class for a given Trigger
#-------------------------------------------------------------------
# Internal unique name for this trigger
#
# Note: This is for internal use only.
#
# @return [String]
attr_reader :id
# Name for the given Trigger. Defaults to nil.
#
# @return [String]
attr_accessor :name
# Command to fire the trigger on
#
# @return [Symbol]
attr_reader :command
# A string to print at the WARN level
#
# @return [String]
attr_accessor :info
# A string to print at the WARN level
#
# @return [String]
attr_accessor :warn
# Determines what how a Trigger should behave if it runs into an error.
# Defaults to :halt, otherwise can only be set to :continue.
#
# @return [Symbol]
attr_accessor :on_error
# If set, will not run trigger for the configured Vagrant commands.
#
# @return [Symbol, Array]
attr_accessor :ignore
# If set, will only run trigger for guests that match keys for this parameter.
#
# @return [String, Regex, Array]
attr_accessor :only_on
# A local inline or file script to execute for the trigger
#
# @return [Hash]
attr_accessor :run
# A remote inline or file script to execute for the trigger
#
# @return [Hash]
attr_accessor :run_remote
def initialize(command)
@logger = Log4r::Logger.new("vagrant::config::vm::trigger::config")
@name = UNSET_VALUE
@info = UNSET_VALUE
@warn = UNSET_VALUE
@on_error = UNSET_VALUE
@ignore = UNSET_VALUE
@only_on = UNSET_VALUE
@run = UNSET_VALUE
@run_remote = UNSET_VALUE
# Internal options
@id = SecureRandom.uuid
@command = command.to_sym
@logger.debug("Trigger defined for command: #{command}")
end
def finalize!
# Ensure all config options are set to nil or default value if untouched
# by user
@name = nil if @name == UNSET_VALUE
@info = nil if @info == UNSET_VALUE
@warn = nil if @warn == UNSET_VALUE
@on_error = DEFAULT_ON_ERROR if @on_error == UNSET_VALUE
@ignore = [] if @ignore == UNSET_VALUE
@run = nil if @run == UNSET_VALUE
@run_remote = nil if @run_remote == UNSET_VALUE
@only_on = nil if @only_on == UNSET_VALUE
# these values are expected to always be an Array internally,
# but can be set as a single String or Symbol
#
# Guests are stored internally as strings
if @only_on
@only_on = Array(@only_on)
end
# Commands must be stored internally as symbols
if @ignore
@ignore = Array(@ignore)
@ignore.map! { |i| i.to_sym }
end
# Convert @run and @run_remote to be a "Shell provisioner" config
if @run && @run.is_a?(Hash)
# Powershell args and privileged for run commands is currently not supported
# so by default use empty string or false if unset. This helps the validate
# function determine if the setting was purposefully set, to print a warning
if !@run.key?(:powershell_args)
@run[:powershell_args] = ""
end
if !@run.key?(:privileged)
@run[:privileged] = false
end
new_run = VagrantPlugins::Shell::Config.new
new_run.set_options(@run)
new_run.finalize!
@run = new_run
end
if @run_remote && @run_remote.is_a?(Hash)
new_run = VagrantPlugins::Shell::Config.new
new_run.set_options(@run_remote)
new_run.finalize!
@run_remote = new_run
end
end
# @return [Array] array of strings of error messages from config option validation
def validate(machine)
errors = _detected_errors
commands = []
Vagrant.plugin("2").manager.commands.each do |key,data|
commands.push(key)
end
if !commands.include?(@command) && @command != :all
machine.ui.warn(I18n.t("vagrant.config.triggers.bad_command_warning",
cmd: @command))
end
if @run
errorz = @run.validate(machine)
errors.concat errorz["shell provisioner"] if !errorz.empty?
if @run.privileged == true
machine.ui.warn(I18n.t("vagrant.config.triggers.privileged_ignored",
command: @command))
end
if @run.powershell_args != ""
machine.ui.warn(I18n.t("vagrant.config.triggers.powershell_args_ignored"))
end
end
if @run_remote
errorz = @run_remote.validate(machine)
errors.concat errorz["shell provisioner"] if !errorz.empty?
end
if @name && !@name.is_a?(String)
errors << I18n.t("vagrant.config.triggers.name_bad_type", cmd: @command)
end
if @info && !@info.is_a?(String)
errors << I18n.t("vagrant.config.triggers.info_bad_type", cmd: @command)
end
if @warn && !@warn.is_a?(String)
errors << I18n.t("vagrant.config.triggers.warn_bad_type", cmd: @command)
end
if @on_error != :halt
if @on_error != :continue
errors << I18n.t("vagrant.config.triggers.on_error_bad_type", cmd: @command)
end
end
errors
end
# The String representation of this Trigger.
#
# @return [String]
def to_s
"trigger config"
end
end
end
end

View File

@ -39,6 +39,11 @@ module VagrantPlugins
require File.expand_path("../config/vm", __FILE__) require File.expand_path("../config/vm", __FILE__)
VMConfig VMConfig
end end
config("trigger") do
require File.expand_path("../config/trigger", __FILE__)
TriggerConfig
end
end end
end end
end end

View File

@ -279,6 +279,24 @@ en:
up some disk space. up some disk space.
Press the Enter or Return key to continue. Press the Enter or Return key to continue.
trigger:
on_error_continue: |-
Trigger configured to continue on error...
start: |-
Running triggers %{stage} %{action} ...
fire_with_name: |-
Running trigger: %{name}...
fire: |-
Running trigger...
run:
inline: |-
Running local: Inline script
%{command}
script: |-
Running local script: %{path}
version_current: |- version_current: |-
Installed Version: %{version} Installed Version: %{version}
version_latest: |- version_latest: |-
@ -1392,6 +1410,22 @@ en:
The synced folder type '%{type}' is reporting as unusable for The synced folder type '%{type}' is reporting as unusable for
your current setup. Please verify you have all the proper your current setup. Please verify you have all the proper
prerequisites for using this shared folder type and try again. prerequisites for using this shared folder type and try again.
triggers_run_fail: |-
Trigger run failed
triggers_guest_not_running: |-
Could not run remote script on %{machine_name} because its state is %{state}
triggers_no_block_given: |-
There was an error parsing the Vagrantfile:
No config was given for the trigger(s) %{command}.
triggers_no_stage_given: |-
The incorrect stage was given to the trigger plugin:
Guest: %{guest_name}
Action: %{action}
Stage: %{stage}
This is an internal error that should be reported as a bug.
ui_expects_tty: |- ui_expects_tty: |-
Vagrant is attempting to interface with the UI in a way that requires Vagrant is attempting to interface with the UI in a way that requires
a TTY. Most actions in Vagrant that require a TTY have configuration a TTY. Most actions in Vagrant that require a TTY have configuration
@ -1677,6 +1711,33 @@ en:
paranoid_deprecated: |- paranoid_deprecated: |-
The key `paranoid` is deprecated. Please use `verify_host_key`. Supported The key `paranoid` is deprecated. Please use `verify_host_key`. Supported
values are exactly the same, only the name of the option has changed. values are exactly the same, only the name of the option has changed.
triggers:
bad_command_warning: |-
The command '%{cmd}' was not found for this trigger.
name_bad_type: |-
Invalid type set for `name` on trigger for command '%{cmd}'. `name` should be a String.
info_bad_type: |-
Invalid type set for `info` on trigger for command '%{cmd}'. `info` should be a String.
warn_bad_type: |-
Invalid type set for `warn` on trigger for command '%{cmd}'. `warn` should be a String.
on_error_bad_type: |-
Invalid type set for `on_error` on trigger for command '%{cmd}'. `on_error` can
only be `:halt` (default) or `:continue`.
only_on_bad_type: |-
Invalid type found for `only_on`. All values must be a `String` or `Regexp`.
privileged_ignored: |-
The `privileged` setting for option `run` for trigger command '%{command}' will be ignored and set to false.
powershell_args_ignored: |-
The setting `powershell_args` is not supported for the trigger option `run` and will be ignored.
run:
bad_type: |-
Invalid type set for `run` on trigger for command '%{cmd}'. `run`
must be a Hash.
run_remote:
bad_type: |-
Invalid type set for `run` on trigger for command '%{cmd}'. `run`
must be a Hash.
vm: vm:
bad_version: |- bad_version: |-
Invalid box version constraints: %{version} Invalid box version constraints: %{version}

View File

@ -0,0 +1,194 @@
require File.expand_path("../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/kernel_v2/config/trigger")
describe VagrantPlugins::Kernel_V2::TriggerConfig do
include_context "unit"
subject { described_class.new }
let(:machine) { double("machine") }
def assert_invalid
errors = subject.validate(machine)
if !errors.values.any? { |v| !v.empty? }
raise "No errors: #{errors.inspect}"
end
end
def assert_valid
errors = subject.validate(machine)
if !errors.values.all? { |v| v.empty? }
raise "Errors: #{errors.inspect}"
end
end
before do
env = double("env")
allow(env).to receive(:root_path).and_return(nil)
allow(machine).to receive(:env).and_return(env)
allow(machine).to receive(:provider_config).and_return(nil)
allow(machine).to receive(:provider_options).and_return({})
end
it "is valid with test defaults" do
subject.finalize!
assert_valid
end
let (:hash_block) { {info: "hi", run: {inline: "echo 'hi'"}} }
let (:splat) { [:up, :destroy, :halt] }
let (:arr) { [[:up, :destroy, :halt]] }
describe "creating a before trigger" do
it "creates a trigger with the splat syntax" do
subject.before(:up, hash_block)
bf_trigger = subject.instance_variable_get(:@_before_triggers)
expect(bf_trigger.size).to eq(1)
expect(bf_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)
end
it "creates a trigger with the array syntax" do
subject.before([:up], hash_block)
bf_trigger = subject.instance_variable_get(:@_before_triggers)
expect(bf_trigger.size).to eq(1)
expect(bf_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)
end
it "creates a trigger with the block syntax" do
subject.before :up do |trigger|
trigger.name = "rspec"
end
bf_trigger = subject.instance_variable_get(:@_before_triggers)
expect(bf_trigger.size).to eq(1)
expect(bf_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)
end
it "creates multiple triggers with the splat syntax" do
subject.before(splat, hash_block)
bf_trigger = subject.instance_variable_get(:@_before_triggers)
expect(bf_trigger.size).to eq(3)
bf_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }
end
it "creates multiple triggers with the block syntax" do
subject.before splat do |trigger|
trigger.name = "rspec"
end
bf_trigger = subject.instance_variable_get(:@_before_triggers)
expect(bf_trigger.size).to eq(3)
bf_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }
end
it "creates multiple triggers with the array syntax" do
subject.before(arr, hash_block)
bf_trigger = subject.instance_variable_get(:@_before_triggers)
expect(bf_trigger.size).to eq(3)
bf_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }
end
end
describe "creating an after trigger" do
it "creates a trigger with the splat syntax" do
subject.after(:up, hash_block)
af_trigger = subject.instance_variable_get(:@_after_triggers)
expect(af_trigger.size).to eq(1)
expect(af_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)
end
it "creates a trigger with the array syntax" do
subject.after([:up], hash_block)
af_trigger = subject.instance_variable_get(:@_after_triggers)
expect(af_trigger.size).to eq(1)
expect(af_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)
end
it "creates a trigger with the block syntax" do
subject.after :up do |trigger|
trigger.name = "rspec"
end
af_trigger = subject.instance_variable_get(:@_after_triggers)
expect(af_trigger.size).to eq(1)
expect(af_trigger.first).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)
end
it "creates multiple triggers with the splat syntax" do
subject.after(splat, hash_block)
af_trigger = subject.instance_variable_get(:@_after_triggers)
expect(af_trigger.size).to eq(3)
af_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }
end
it "creates multiple triggers with the block syntax" do
subject.after splat do |trigger|
trigger.name = "rspec"
end
af_trigger = subject.instance_variable_get(:@_after_triggers)
expect(af_trigger.size).to eq(3)
af_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }
end
it "creates multiple triggers with the array syntax" do
subject.after(arr, hash_block)
af_trigger = subject.instance_variable_get(:@_after_triggers)
expect(af_trigger.size).to eq(3)
af_trigger.map { |t| expect(t).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger) }
end
end
describe "#create_trigger" do
let(:command) { :up }
let(:hash_block) { {info: "hi", run: {inline: "echo 'hi'"}} }
it "returns a new VagrantConfigTrigger object if given a hash" do
trigger = subject.create_trigger(command, hash_block)
expect(trigger).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)
end
it "returns a new VagrantConfigTrigger object if given a block" do
block = Proc.new { |b| b.info = "test"}
trigger = subject.create_trigger(command, block)
expect(trigger).to be_a(VagrantPlugins::Kernel_V2::VagrantConfigTrigger)
end
end
describe "#merge" do
it "merges defined triggers" do
a = described_class.new()
b = described_class.new()
a.before(splat, hash_block)
a.after(arr, hash_block)
b.before(splat, hash_block)
b.after(arr, hash_block)
result = a.merge(b)
bf_trigger = result.instance_variable_get(:@_before_triggers)
af_trigger = result.instance_variable_get(:@_after_triggers)
expect(bf_trigger).to be_a(Array)
expect(af_trigger).to be_a(Array)
expect(bf_trigger.size).to eq(6)
expect(af_trigger.size).to eq(6)
end
it "merges the other triggers if a class is empty" do
a = described_class.new()
b = described_class.new()
a.before(splat, hash_block)
a.after(arr, hash_block)
b_bf_trigger = b.instance_variable_get(:@_before_triggers)
b_af_trigger = b.instance_variable_get(:@_after_triggers)
result = a.merge(b)
bf_trigger = result.instance_variable_get(:@_before_triggers)
af_trigger = result.instance_variable_get(:@_after_triggers)
expect(bf_trigger.size).to eq(3)
expect(af_trigger.size).to eq(3)
end
end
end

View File

@ -0,0 +1,124 @@
require File.expand_path("../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/kernel_v2/config/vm_trigger")
describe VagrantPlugins::Kernel_V2::VagrantConfigTrigger do
include_context "unit"
let(:command) { :up }
subject { described_class.new(command) }
let(:machine) { double("machine") }
def assert_invalid
errors = subject.validate(machine)
if !errors.empty? { |v| !v.empty? }
raise "No errors: #{errors.inspect}"
end
end
def assert_valid
errors = subject.validate(machine)
if !errors.empty? { |v| v.empty? }
raise "Errors: #{errors.inspect}"
end
end
before do
env = double("env")
allow(env).to receive(:root_path).and_return(nil)
allow(machine).to receive(:env).and_return(env)
allow(machine).to receive(:provider_config).and_return(nil)
allow(machine).to receive(:provider_options).and_return({})
subject.name = "foo"
subject.info = "Hello there"
subject.warn = "Warning!!"
subject.ignore = :up
subject.only_on = "guest"
subject.run = {inline: "apt-get update"}
subject.run_remote = {inline: "apt-get update", env: {"VAR"=>"VAL"}}
end
describe "with defaults" do
it "is valid with test defaults" do
subject.finalize!
assert_valid
end
it "sets a command" do
subject.finalize!
expect(subject.command).to eq(command)
end
it "uses default error behavior" do
subject.finalize!
expect(subject.on_error).to eq(:halt)
end
end
describe "defining a new config that needs to match internal restraints" do
let(:cmd) { :destroy }
let(:cfg) { described_class.new(cmd) }
let(:arr_cfg) { described_class.new(cmd) }
before do
cfg.only_on = :guest
cfg.ignore = "up"
arr_cfg.only_on = ["guest", /other/]
arr_cfg.ignore = ["up", "destroy"]
end
it "ensures only_on is an array" do
cfg.finalize!
arr_cfg.finalize!
expect(cfg.only_on).to be_a(Array)
expect(arr_cfg.only_on).to be_a(Array)
end
it "ensures ignore is an array of symbols" do
cfg.finalize!
arr_cfg.finalize!
expect(cfg.ignore).to be_a(Array)
expect(arr_cfg.ignore).to be_a(Array)
cfg.ignore.each do |a|
expect(a).to be_a(Symbol)
end
arr_cfg.ignore.each do |a|
expect(a).to be_a(Symbol)
end
end
end
describe "defining a basic trigger config" do
let(:cmd) { :up }
let(:cfg) { described_class.new(cmd) }
before do
cfg.info = "Hello there"
cfg.warn = "Warning!!"
cfg.on_error = :continue
cfg.ignore = :up
cfg.only_on = "guest"
cfg.run = {inline: "apt-get update"}
cfg.run_remote = {inline: "apt-get update", env: {"VAR"=>"VAL"}}
end
it "sets the options" do
cfg.finalize!
expect(cfg.info).to eq("Hello there")
expect(cfg.warn).to eq("Warning!!")
expect(cfg.on_error).to eq(:continue)
expect(cfg.ignore).to eq([:up])
expect(cfg.only_on).to eq(["guest"])
expect(cfg.run).to be_a(VagrantPlugins::Shell::Config)
expect(cfg.run_remote).to be_a(VagrantPlugins::Shell::Config)
end
end
end

View File

@ -0,0 +1,319 @@
require File.expand_path("../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/kernel_v2/config/trigger")
describe Vagrant::Plugin::V2::Trigger do
include_context "unit"
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
isolated_environment.tap do |env|
env.vagrantfile("")
end
end
let(:iso_vagrant_env) { iso_env.create_vagrant_env }
let(:state) { double("state", id: :running) }
let(:machine) do
iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy).tap do |m|
allow(m).to receive(:state).and_return(state)
end
end
let(:env) { {
machine: machine,
ui: Vagrant::UI::Silent.new,
} }
let(:triggers) { VagrantPlugins::Kernel_V2::TriggerConfig.new }
let(:hash_block) { {info: "hi", run: {inline: "echo 'hi'"}} }
let(:hash_block_two) { {warn: "WARNING!!", run_remote: {inline: "echo 'hi'"}} }
before do
triggers.before(:up, hash_block)
triggers.before(:destroy, hash_block)
triggers.before(:halt, hash_block_two)
triggers.after(:up, hash_block)
triggers.after(:destroy, hash_block)
triggers.finalize!
end
let(:subject) { described_class.new(env, triggers, machine) }
context "#fire_triggers" do
it "raises an error if an inproper stage is given" do
expect{ subject.fire_triggers(:up, :not_real, "guest") }.
to raise_error(Vagrant::Errors::TriggersNoStageGiven)
end
end
context "#filter_triggers" do
it "returns all triggers if no constraints" do
before_triggers = triggers.before_triggers
filtered_triggers = subject.send(:filter_triggers, before_triggers, "guest")
expect(filtered_triggers).to eq(before_triggers)
end
it "filters a trigger if it doesn't match guest_name" do
trigger_config = {info: "no", only_on: "notrealguest"}
triggers.after(:up, trigger_config)
triggers.finalize!
after_triggers = triggers.after_triggers
expect(after_triggers.size).to eq(3)
subject.send(:filter_triggers, after_triggers, "ubuntu")
expect(after_triggers.size).to eq(2)
end
it "keeps a trigger that has a restraint that matches guest name" do
trigger_config = {info: "no", only_on: /guest/}
triggers.after(:up, trigger_config)
triggers.finalize!
after_triggers = triggers.after_triggers
expect(after_triggers.size).to eq(3)
subject.send(:filter_triggers, after_triggers, "ubuntu-guest")
expect(after_triggers.size).to eq(3)
end
it "keeps a trigger that has multiple restraints that matches guest name" do
trigger_config = {info: "no", only_on: ["debian", /guest/]}
triggers.after(:up, trigger_config)
triggers.finalize!
after_triggers = triggers.after_triggers
expect(after_triggers.size).to eq(3)
subject.send(:filter_triggers, after_triggers, "ubuntu-guest")
expect(after_triggers.size).to eq(3)
end
end
context "#fire" do
it "calls the corresponding trigger methods if options set" do
expect(subject).to receive(:info).twice
expect(subject).to receive(:warn).once
expect(subject).to receive(:run).twice
expect(subject).to receive(:run_remote).once
subject.send(:fire, triggers.before_triggers, "guest")
end
end
context "#info" do
let(:message) { "Printing some info" }
it "prints messages at INFO" do
output = ""
allow(machine.ui).to receive(:info) do |data|
output << data
end
subject.send(:info, message)
expect(output).to include(message)
end
end
context "#warn" do
let(:message) { "Printing some warnings" }
it "prints messages at WARN" do
output = ""
allow(machine.ui).to receive(:warn) do |data|
output << data
end
subject.send(:warn, message)
expect(output).to include(message)
end
end
context "#run" do
let(:trigger_run) { VagrantPlugins::Kernel_V2::TriggerConfig.new }
let(:shell_block) { {info: "hi", run: {inline: "echo 'hi'", env: {"KEY"=>"VALUE"}}} }
let(:path_block) { {warn: "bye",
run: {path: "script.sh", env: {"KEY"=>"VALUE"}},
on_error: :continue} }
let(:path_block_ps1) { {warn: "bye",
run: {path: "script.ps1", env: {"KEY"=>"VALUE"}},
on_error: :continue} }
let(:exit_code) { 0 }
let(:options) { {:notify=>[:stdout, :stderr]} }
let(:subprocess_result) do
double("subprocess_result").tap do |result|
allow(result).to receive(:exit_code).and_return(exit_code)
allow(result).to receive(:stderr).and_return("")
end
end
before do
trigger_run.after(:up, shell_block)
trigger_run.before(:destroy, path_block)
trigger_run.before(:destroy, path_block_ps1)
trigger_run.finalize!
end
it "executes an inline script with powershell if windows" do
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
allow(Vagrant::Util::PowerShell).to receive(:execute_inline).
and_return(subprocess_result)
trigger = trigger_run.after_triggers.first
shell_config = trigger.run
on_error = trigger.on_error
expect(Vagrant::Util::PowerShell).to receive(:execute_inline).
with("echo", "hi", options)
subject.send(:run, shell_config, on_error)
end
it "executes an path script with powershell if windows" do
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
allow(Vagrant::Util::PowerShell).to receive(:execute).
and_return(subprocess_result)
allow(env).to receive(:root_path).and_return("/vagrant/home")
trigger = trigger_run.before_triggers[1]
shell_config = trigger.run
on_error = trigger.on_error
expect(Vagrant::Util::PowerShell).to receive(:execute).
with("/vagrant/home/script.ps1", options)
subject.send(:run, shell_config, on_error)
end
it "executes an inline script" do
allow(Vagrant::Util::Subprocess).to receive(:execute).
and_return(subprocess_result)
trigger = trigger_run.after_triggers.first
shell_config = trigger.run
on_error = trigger.on_error
expect(Vagrant::Util::Subprocess).to receive(:execute).
with("echo", "hi", options)
subject.send(:run, shell_config, on_error)
end
it "executes an path script" do
allow(Vagrant::Util::Subprocess).to receive(:execute).
and_return(subprocess_result)
allow(env).to receive(:root_path).and_return("/vagrant/home")
allow(FileUtils).to receive(:chmod).and_return(true)
trigger = trigger_run.before_triggers.first
shell_config = trigger.run
on_error = trigger.on_error
expect(Vagrant::Util::Subprocess).to receive(:execute).
with("/vagrant/home/script.sh", options)
subject.send(:run, shell_config, on_error)
end
it "continues on error" do
allow(Vagrant::Util::Subprocess).to receive(:execute).
and_raise("Fail!")
allow(env).to receive(:root_path).and_return("/vagrant/home")
allow(FileUtils).to receive(:chmod).and_return(true)
trigger = trigger_run.before_triggers.first
shell_config = trigger.run
on_error = trigger.on_error
expect(Vagrant::Util::Subprocess).to receive(:execute).
with("/vagrant/home/script.sh", options)
subject.send(:run, shell_config, on_error)
end
it "halts on error" do
allow(Vagrant::Util::Subprocess).to receive(:execute).
and_raise("Fail!")
trigger = trigger_run.after_triggers.first
shell_config = trigger.run
on_error = trigger.on_error
expect(Vagrant::Util::Subprocess).to receive(:execute).
with("echo", "hi", options)
expect { subject.send(:run, shell_config, on_error) }.to raise_error("Fail!")
end
end
context "#run_remote" do
let (:trigger_run) { VagrantPlugins::Kernel_V2::TriggerConfig.new }
let (:shell_block) { {info: "hi", run_remote: {inline: "echo 'hi'", env: {"KEY"=>"VALUE"}}} }
let (:path_block) { {warn: "bye",
run_remote: {path: "script.sh", env: {"KEY"=>"VALUE"}},
on_error: :continue} }
let(:provision) { double("provision") }
before do
trigger_run.after(:up, shell_block)
trigger_run.before(:destroy, path_block)
trigger_run.finalize!
end
it "raises an error and halts if guest is not running" do
allow(machine.state).to receive(:id).and_return(:not_running)
trigger = trigger_run.after_triggers.first
shell_config = trigger.run_remote
on_error = trigger.on_error
expect { subject.send(:run_remote, shell_config, on_error) }.
to raise_error(Vagrant::Errors::TriggersGuestNotRunning)
end
it "continues on if guest is not running but is configured to continue on error" do
allow(machine.state).to receive(:id).and_return(:not_running)
allow(env).to receive(:root_path).and_return("/vagrant/home")
allow(FileUtils).to receive(:chmod).and_return(true)
trigger = trigger_run.before_triggers.first
shell_config = trigger.run_remote
on_error = trigger.on_error
subject.send(:run_remote, shell_config, on_error)
end
it "calls the provision function on the shell provisioner" do
allow(machine.state).to receive(:id).and_return(:running)
allow(provision).to receive(:provision).and_return("Provision!")
allow(VagrantPlugins::Shell::Provisioner).to receive(:new).
and_return(provision)
trigger = trigger_run.after_triggers.first
shell_config = trigger.run_remote
on_error = trigger.on_error
subject.send(:run_remote, shell_config, on_error)
end
it "continues on if provision fails" do
allow(machine.state).to receive(:id).and_return(:running)
allow(provision).to receive(:provision).and_raise("Nope!")
allow(VagrantPlugins::Shell::Provisioner).to receive(:new).
and_return(provision)
trigger = trigger_run.before_triggers.first
shell_config = trigger.run_remote
on_error = trigger.on_error
subject.send(:run_remote, shell_config, on_error)
end
it "fails if it encounters an error" do
allow(machine.state).to receive(:id).and_return(:running)
allow(provision).to receive(:provision).and_raise("Nope!")
allow(VagrantPlugins::Shell::Provisioner).to receive(:new).
and_return(provision)
trigger = trigger_run.after_triggers.first
shell_config = trigger.run_remote
on_error = trigger.on_error
expect { subject.send(:run_remote, shell_config, on_error) }.
to raise_error("Nope!")
end
end
end

View File

@ -0,0 +1,43 @@
---
layout: "docs"
page_title: "Vagrant Triggers Configuration"
sidebar_current: "triggers-configuration"
description: |-
Documentation of various configuration options for Vagrant Triggers
---
# Configuration
Vagrant Triggers has a few options to define trigger behavior.
## Options
The trigger class takes various options.
* `action` (symbol, array) - Expected to be a single symbol value, an array of symbols, or a _splat_ of symbols. The first argument that comes after either __before__ or __after__ when defining a new trigger. Can be any valid Vagrant command. It also accepts a special value `:all` which will make the trigger fire for every action. An action can be ignored with the `ignore` setting if desired. These are the valid action commands for triggers:
- `destroy`
- `halt`
- `provision`
- `reload`
- `resume`
- `up`
* `ignore` (symbol, array) - Symbol or array of symbols corresponding to the action that a trigger should not fire on.
* `info` (string) - A message that will be printed at the beginning of a trigger.
* `name` (string) - The name of the trigger. If set, the name will be displayed when firing the trigger.
* `on_error` (symbol) - Defines how the trigger should behave if it encounters an error. By default this will be `:halt`, but can be configured to ignore failures and continue on with `:continue`.
* `only_on` (string, regex, array) - Guest or guests to be ignored on the defined trigger. Values can be a string or regex that matches a guest name.
* `run_remote` (hash) - A collection of settings to run a inline or remote script with on the guest. These settings correspond to the [shell provosioner](/docs/provisioning/shell.html).
* `run` (hash) - A collection of settings to run a inline or remote script with on the host. These settings correspond to the [shell provosioner](/docs/provisioning/shell.html). However, at the moment the only settings `run` takes advantage of are:
+ `args`
+ `inline`
+ `path`
* `warn` (string) - A warning message that will be printed at the beginning of a trigger.

View File

@ -0,0 +1,95 @@
---
layout: "docs"
page_title: "Vagrant Triggers"
sidebar_current: "triggers"
description: |-
Introduction to Vagrant Triggers
---
# Vagrant Triggers
As of version 2.1.0, Vagrant is capable of executing machine triggers _before_ or
_after_ Vagrant commands.
Each trigger is expected to be given a command key for when it should be fired
during the Vagrant command lifecycle. These could be defined as a single key or
an array which acts like a _whitelist_ for the defined trigger.
```ruby
# single command trigger
config.trigger.after :up do |trigger|
...
end
# multiple commands for this trigger
config.trigger.before [:up, :destroy, :halt, :package] do |trigger|
...
end
# or defined as a splat list
config.trigger.before :up, :destroy, :halt, :package do |trigger|
...
end
```
Alternatively, the key `:all` could be given which would run the trigger before
or after every Vagrant command. If there is a command you don't want the trigger
to run on, you can ignore that command with the `ignore` option.
```ruby
# single command trigger
config.trigger.before :all do |trigger|
trigger.info = "Running a before trigger!"
trigger.ignore = [:destroy, :halt]
end
```
__Note:__ _If a trigger is defined on a command that does not exist, a warning
will be displayed._
Triggers can be defined as a block or hash in a Vagrantfile. The example below
will result in the same trigger:
```ruby
config.trigger.after :up do |trigger|
trigger.name = "Finished Message"
trigger.info = "Machine is up!"
end
config.trigger.after :up,
name: "Finished Message",
info: "Machine is up!"
```
Triggers can also be defined within the scope of guests in a Vagrantfile. These
triggers will only run on the configured guest. An example of a guest only trigger:
```ruby
config.vm.define "ubuntu" do |ubuntu|
ubuntu.vm.box = "ubuntu"
ubuntu.trigger.before :destroy do |trigger|
trigger.warn = "Dumping database to /vagrant/outfile"
trigger.run_remote {inline: "pg_dump dbname > /vagrant/outfile"}
end
end
```
Global and machine-scoped triggers will execute in the order that they are
defined within a Vagrantfile. Take for example an abstracted Vagrantfile:
```
Vagrantfile
global trigger 1
global trigger 2
machine defined
machine trigger 3
global trigger 4
end
```
In this generic case, the triggers would fire in the order: 1 -> 2 -> 3 -> 4
For more information about what options are available for triggers, see the
[configuration section](/docs/triggers/configuration.html).

View File

@ -0,0 +1,95 @@
---
layout: "docs"
page_title: "Vagrant Triggers Usage"
sidebar_current: "triggers-usage"
description: |-
Various Vagrant Triggers examples
---
# Basic Usage
Below are some very simple examples of how to use Vagrant Triggers.
## Examples
The following is a basic example of two global triggers. One that runs _before_
the `:up` command and one that runs _after_ the `:up` command:
```ruby
Vagrant.configure("2") do |config|
config.trigger.before :up do |trigger|
trigger.name = "Hello world"
trigger.info = "I am running before vagrant up!!"
end
config.trigger.before :up do |trigger|
trigger.name = "Hello world"
trigger.info = "I am running after vagrant up!!"
end
config.vm.define "ubuntu" do |ubuntu|
ubuntu.vm.box = "ubuntu"
end
end
```
These will run before and after each defined guest in the Vagrantfile.
Running a remote script to save a database on your host before __destroy__ing a
guest:
```ruby
Vagrant.configure("2") do |config|
config.vm.define "ubuntu" do |ubuntu|
ubuntu.vm.box = "ubuntu"
ubuntu.trigger.before :destroy do |trigger|
trigger.warn = "Dumping database to /vagrant/outfile"
trigger.run_remote = {inline: "pg_dump dbname > /vagrant/outfile"}
end
end
end
```
Now that the trigger is defined, running the __destroy__ command will fire off
the defined trigger before Vagrant destroys the machine.
```shell
$ vagrant destroy ubuntu
```
An example of defining three triggers that start and stop tinyproxy on your host
machine using homebrew:
```shell
#/bin/bash
# start-tinyproxy.sh
brew services start tinyproxy
```
```shell
#/bin/bash
# stop-tinyproxy.sh
brew services stop tinyproxy
```
```ruby
Vagrant.configure("2") do |config|
config.vm.define "ubuntu" do |ubuntu|
ubuntu.vm.box = "ubuntu"
ubuntu.trigger.before :up do |trigger|
trigger.info = "Starting tinyproxy..."
trigger.run = {path: "start-tinyproxy.sh"}
end
ubuntu.trigger.after :destroy, :halt do |trigger|
trigger.info = "Stopping tinyproxy..."
trigger.run = {path: "stop-tinyproxy.sh"}
end
end
end
```
Running `vagrant up` would fire the before trigger to start tinyproxy, where as
running either `vagrant destroy` or `vagrant halt` would stop tinyproxy.

View File

@ -207,6 +207,14 @@
</ul> </ul>
</li> </li>
<li<%= sidebar_current("triggers") %>>
<a href="/docs/triggers/">Triggers</a>
<ul class="nav">
<li<%= sidebar_current("triggers-configuration") %>><a href="/docs/triggers/configuration.html">Configuration</a></li>
<li<%= sidebar_current("triggers-usage") %>><a href="/docs/triggers/usage.html">Usage</a></li>
</ul>
</li>
<li<%= sidebar_current("other") %>> <li<%= sidebar_current("other") %>>
<a href="/docs/other/">Other</a> <a href="/docs/other/">Other</a>
<ul class="nav"> <ul class="nav">