Merge pull request #9713 from briancain/vagrant-triggers-config
Integrate vagrant-triggers plugin functionality into core Vagrant
This commit is contained in:
commit
5643ba0c7d
|
@ -776,6 +776,18 @@ module Vagrant
|
|||
error_key(:synced_folder_unusable)
|
||||
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
|
||||
error_key(:ui_expects_tty)
|
||||
end
|
||||
|
|
|
@ -149,6 +149,8 @@ module Vagrant
|
|||
# Output a bunch of information about this machine in
|
||||
# machine-readable format in case someone is listening.
|
||||
@ui.machine("metadata", "provider", provider_name)
|
||||
|
||||
@triggers = Vagrant::Plugin::V2::Trigger.new(@env, @config.trigger, self)
|
||||
end
|
||||
|
||||
# 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
|
||||
# runner.
|
||||
def action(name, opts=nil)
|
||||
@triggers.fire_triggers(name, :before, @name.to_s)
|
||||
@logger.info("Calling action: #{name} on provider #{@provider}")
|
||||
|
||||
opts ||= {}
|
||||
|
@ -185,7 +188,7 @@ module Vagrant
|
|||
locker = @env.method(:lock) if lock && !name.to_s.start_with?("ssh")
|
||||
|
||||
# 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.
|
||||
callable = @provider.action(name)
|
||||
|
||||
|
@ -203,6 +206,10 @@ module Vagrant
|
|||
ui.machine("action", name.to_s, "end")
|
||||
action_result
|
||||
end
|
||||
|
||||
@triggers.fire_triggers(name, :after, @name.to_s)
|
||||
# preserve returning environment after machine action runs
|
||||
return return_env
|
||||
rescue Errors::EnvironmentLockedError
|
||||
raise Errors::MachineActionLockedError,
|
||||
action: name,
|
||||
|
|
|
@ -19,6 +19,7 @@ module Vagrant
|
|||
autoload :Push, "vagrant/plugin/v2/push"
|
||||
autoload :Provisioner, "vagrant/plugin/v2/provisioner"
|
||||
autoload :SyncedFolder, "vagrant/plugin/v2/synced_folder"
|
||||
autoload :Trigger, "vagrant/plugin/v2/trigger"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,7 +45,7 @@ module Vagrant
|
|||
def parse_options(opts=nil)
|
||||
# make sure optparse doesn't use POSIXLY_CORRECT parsing
|
||||
ENV["POSIXLY_CORRECT"] = nil
|
||||
|
||||
|
||||
# Creating a shallow copy of the arguments so the OptionParser
|
||||
# doesn't destroy the originals.
|
||||
argv = @argv.dup
|
||||
|
|
|
@ -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
|
|
@ -87,6 +87,27 @@ module Vagrant
|
|||
return r.stdout.chomp
|
||||
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.
|
||||
#
|
||||
# @return [String]
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -39,6 +39,11 @@ module VagrantPlugins
|
|||
require File.expand_path("../config/vm", __FILE__)
|
||||
VMConfig
|
||||
end
|
||||
|
||||
config("trigger") do
|
||||
require File.expand_path("../config/trigger", __FILE__)
|
||||
TriggerConfig
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -279,6 +279,24 @@ en:
|
|||
up some disk space.
|
||||
|
||||
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: |-
|
||||
Installed Version: %{version}
|
||||
version_latest: |-
|
||||
|
@ -1392,6 +1410,22 @@ en:
|
|||
The synced folder type '%{type}' is reporting as unusable for
|
||||
your current setup. Please verify you have all the proper
|
||||
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: |-
|
||||
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
|
||||
|
@ -1677,6 +1711,33 @@ en:
|
|||
paranoid_deprecated: |-
|
||||
The key `paranoid` is deprecated. Please use `verify_host_key`. Supported
|
||||
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:
|
||||
bad_version: |-
|
||||
Invalid box version constraints: %{version}
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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).
|
|
@ -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.
|
|
@ -207,6 +207,14 @@
|
|||
</ul>
|
||||
</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") %>>
|
||||
<a href="/docs/other/">Other</a>
|
||||
<ul class="nav">
|
||||
|
|
Loading…
Reference in New Issue