class Vagrant::Plugin::V2::Trigger

Attributes

config[R]

@return [Kernel_V2::Config::Trigger]

Public Class Methods

new(env, config, machine, ui) click to toggle source

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 @param [Vagrant::UI] ui Class for printing messages to user

# File lib/vagrant/plugin/v2/trigger.rb, line 24
def initialize(env, config, machine, ui)
  @env        = env
  @config     = config
  @machine    = machine
  @ui         = ui

  @logger = Log4r::Logger.new("vagrant::trigger::#{self.class.to_s.downcase}")
end

Public Instance Methods

find(name, stage, guest, type, all: false) click to toggle source

Find all triggers defined for the named type and guest.

@param [Symbol] name Name of ‘type` thing to fire trigger on @param [Symbol] stage :before or :after @param [String] guest The guest that invoked firing the triggers @param [Symbol] type Type of trigger to fire @return [Array]

# File lib/vagrant/plugin/v2/trigger.rb, line 69
def find(name, stage, guest, type, all: false)
  triggers = nil
  name = nameify(name)

  if stage == :before
    triggers = config.before_triggers.select do |t|
      (all && t.command.respond_to?(:to_sym) && t.command.to_sym == :all && !t.ignore.include?(name.to_sym)) ||
        (type == :hook && matched_hook?(t.command, name)) ||
        nameify(t.command) == name
    end
  elsif stage == :after
    triggers = config.after_triggers.select do |t|
      (all && t.command.respond_to?(:to_sym) && t.command.to_sym == :all && !t.ignore.include?(name.to_sym)) ||
        (type == :hook && matched_hook?(t.command, name)) ||
        nameify(t.command) == name
    end
  else
    raise Errors::TriggersNoStageGiven,
      name: name,
      stage: stage,
      type: type,
      guest_name: guest
  end

  filter_triggers(triggers, guest, type)
end
fire(name, stage, guest, type, all: false) click to toggle source

Fires all triggers, if any are defined for the named type and guest. Returns early and logs a warning if the community plugin ‘vagrant-triggers` is installed

@param [Symbol] name Name of ‘type` thing to fire trigger on @param [Symbol] stage :before or :after @param [String] guest The guest that invoked firing the triggers @param [Symbol] type Type of trigger to fire (:action, :hook, :command)

# File lib/vagrant/plugin/v2/trigger.rb, line 40
def fire(name, stage, guest, type, all: false)
  if community_plugin_detected?
    @logger.warn("Community plugin `vagrant-triggers detected, so core triggers will not fire")
    return
  end

  return @logger.warn("Name given is nil, no triggers will fire") if !name
  return @logger.warn("Name given cannot be symbolized, no triggers will fire") if
    !name.respond_to?(:to_sym)

  name = name.to_sym

  # get all triggers matching action
  triggers = find(name, stage, guest, type, all: all)

  if !triggers.empty?
    @logger.info("Firing trigger for #{type} #{name} on guest #{guest}")
    @ui.info(I18n.t("vagrant.trigger.start", type: type, stage: stage, name: name))
    execute(triggers)
  end
end

Protected Instance Methods

community_plugin_detected?() click to toggle source

Looks up if the community plugin ‘vagrant-triggers` is installed and also caches the result

@return [Boolean]

# File lib/vagrant/plugin/v2/trigger.rb, line 129
def community_plugin_detected?
  if !defined?(@_triggers_enabled)
    plugins = Vagrant::Plugin::Manager.instance.installed_plugins
    @_triggers_enabled = plugins.keys.include?("vagrant-triggers")
  end
  @_triggers_enabled
end
execute(triggers) click to toggle source

Execute all triggers in the given array

@param [Array] triggers An array of triggers to be fired

# File lib/vagrant/plugin/v2/trigger.rb, line 179
def execute(triggers)
  # ensure on_error is respected by exiting or continuing
  triggers.each do |trigger|
    @logger.debug("Running trigger #{trigger.id}...")

    if trigger.name
      @ui.info(I18n.t("vagrant.trigger.fire_with_name",
                              name: trigger.name))
    else
      @ui.info(I18n.t("vagrant.trigger.fire"))
    end

    if trigger.info
      info(trigger.info)
    end

    if trigger.warn
      warn(trigger.warn)
    end

    if trigger.abort
      trigger_abort(trigger.abort)
    end

    if trigger.run
      run(trigger.run, trigger.on_error, trigger.exit_codes)
    end

    if trigger.run_remote
      run_remote(trigger.run_remote, trigger.on_error, trigger.exit_codes)
    end

    if trigger.ruby_block
      execute_ruby(trigger.ruby_block)
    end
  end
end
execute_ruby(ruby_block) click to toggle source

Calls the given ruby block for execution

@param [Proc] ruby_block

# File lib/vagrant/plugin/v2/trigger.rb, line 358
def execute_ruby(ruby_block)
  ruby_block.call(@env, @machine)
end
filter_triggers(triggers, guest_name, type) click to toggle source

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 @param [Symbol] type The type of trigger (:command or :type) @return [Array] The filtered array of triggers

# File lib/vagrant/plugin/v2/trigger.rb, line 143
def filter_triggers(triggers, guest_name, type)
  # 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.to_s)
          # 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 trigger.type != type
      index = triggers.index(trigger)
    end

    if index
      @logger.debug("Trigger #{trigger.id} will be ignored for #{guest_name}")
      triggers.delete_at(index)
    end
  end

  return triggers
end
info(message) click to toggle source

Prints the given message at info level for a trigger

@param [String] message The string to be printed

# File lib/vagrant/plugin/v2/trigger.rb, line 220
def info(message)
  @ui.info(message)
end
matched_hook?(key, subject) click to toggle source

Generate all valid lookup keys for given action key

@param [Class, String] key Base key for generation @return [Array<String>] all valid keys

# File lib/vagrant/plugin/v2/trigger.rb, line 118
def matched_hook?(key, subject)
  subject = nameify(subject)
  Vagrant.plugin("2").manager.generate_hook_keys(key).any? do |k|
    k == subject
  end
end
nameify(object) click to toggle source

Convert object into name

@param [Object, Class] object Object to name @return [String]

# File lib/vagrant/plugin/v2/trigger.rb, line 102
def nameify(object)
  if object.is_a?(Class)
    object.name.to_s
  else
    object.to_s
  end
end
run(config, on_error, exit_codes) click to toggle source

Runs a script on a guest

@param [Provisioners::Shell::Config] config A Shell provisioner config

# File lib/vagrant/plugin/v2/trigger.rb, line 234
def run(config, on_error, exit_codes)
  if config.inline
    if Vagrant::Util::Platform.windows?
      cmd = config.inline
    else
      cmd = Shellwords.split(config.inline)
    end

    @ui.detail(I18n.t("vagrant.trigger.run.inline", command: config.inline))
  else
    cmd = File.expand_path(config.path, @env.root_path).shellescape
    args = Array(config.args)
    cmd << " #{args.join(' ')}" if !args.empty?
    cmd = Shellwords.split(cmd)

    @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

      @ui.detail(data, **options)
    end
    if !exit_codes.include?(result.exit_code)
      raise Errors::TriggersBadExitCodes,
        code: result.exit_code
    end
  rescue => e
    @ui.error(I18n.t("vagrant.errors.triggers_run_fail"))
    @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...")
      @ui.warn(I18n.t("vagrant.trigger.on_error_continue"))
    end
  end
end
run_remote(config, on_error, exit_codes) click to toggle source

Runs a script on the guest

@param [ShellProvisioner/Config] config A Shell provisioner config

# File lib/vagrant/plugin/v2/trigger.rb, line 297
def run_remote(config, on_error, exit_codes)
  if !@machine
    # machine doesn't even exist.
    if on_error == :halt
      raise Errors::TriggersGuestNotExist
    else
      @ui.warn(I18n.t("vagrant.errors.triggers_guest_not_exist"))
      @ui.warn(I18n.t("vagrant.trigger.on_error_continue"))
      return
    end
  elsif @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
trigger_abort(exit_code) click to toggle source

Exits Vagrant immediately

@param [Integer] code Code to exit Vagrant on

# File lib/vagrant/plugin/v2/trigger.rb, line 341
def trigger_abort(exit_code)
  if Thread.current[:batch_parallel_action]
    @ui.warn(I18n.t("vagrant.trigger.abort_threaded"))
    @logger.debug("Trigger abort within parallel batch action. " \
      "Setting exit code and terminating.")
    Thread.current[:exit_code] = exit_code
    Thread.current.terminate
  else
    @ui.warn(I18n.t("vagrant.trigger.abort"))
    @logger.debug("Trigger abort within non-parallel action, exiting directly")
    Process.exit!(exit_code)
  end
end
warn(message) click to toggle source

Prints the given message at warn level for a trigger

@param [String] message The string to be printed

# File lib/vagrant/plugin/v2/trigger.rb, line 227
def warn(message)
  @ui.warn(message)
end