Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate vagrant-triggers plugin functionality into core Vagrant #9713

Merged
merged 95 commits into from
Apr 24, 2018
Merged
Show file tree
Hide file tree
Changes from 93 commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
a194057
Add trigger config plugin
briancain Mar 8, 2018
813ffd0
Add more trigger config options
briancain Mar 8, 2018
0142722
Add run and run_remote scaffolding
briancain Mar 8, 2018
01b9040
Use proper command reference for command whitelist params
briancain Mar 9, 2018
7cccddc
Add basic scaffolding for command parsing
briancain Mar 9, 2018
bb2f3b3
Add basic unit test
briancain Mar 9, 2018
fdf1b58
Introduce ids for trigger blocks
briancain Mar 10, 2018
6f00eb5
Move trigger options into own plugin class
briancain Mar 10, 2018
e0a5b1d
Parse block trigger configs
briancain Mar 13, 2018
c10ae07
Store correct command for trigger
briancain Mar 13, 2018
50ecf45
Add more doc to create_trigger function
briancain Mar 13, 2018
3c5e4b2
Fix variable doc
briancain Mar 13, 2018
375e8d7
Remove old comments
briancain Mar 13, 2018
8e72ae5
Add basic validation for trigger config objects
briancain Mar 13, 2018
60ff248
Display warning if vagrant command for trigger is not found
briancain Mar 13, 2018
a96baad
Add comments for later dev
briancain Mar 13, 2018
b04f136
Move warning to logger rather than guest
briancain Mar 14, 2018
5ca1d1a
Finalize config in plugins finalize, improve docs
briancain Mar 14, 2018
d26a925
Update variable doc
briancain Mar 14, 2018
607368a
Introduce merge function to preserve internal state
briancain Mar 14, 2018
0cbf697
Update config class setting
briancain Mar 14, 2018
51e4118
Debug commit
briancain Mar 14, 2018
f0ec7c7
Potential solution for trigger validation when guest is defined
briancain Mar 15, 2018
1591ae9
Make run and run_remote settings shell provisioner configs
briancain Mar 15, 2018
d3dde73
Update trigger code docs
briancain Mar 15, 2018
e752878
Update finalize method for run and run_remote objects
briancain Mar 16, 2018
60c4ffa
Ensure run & run_remote are Hashes before updating to shell config
briancain Mar 16, 2018
d0d69e5
Update return doc string on validate
briancain Mar 16, 2018
42419bb
Fix rspec tests
briancain Mar 19, 2018
3dec686
Update trigger unit tests
briancain Mar 19, 2018
6cbb5d8
Add unit tests for config, fix type change bug with map!
briancain Mar 19, 2018
abb6d77
Move location of command validator
briancain Mar 19, 2018
7b1b044
Check for default on_error behavior setting
briancain Mar 19, 2018
e157362
Add run and run_remote expectations
briancain Mar 19, 2018
fc526a1
Add basic create_trigger rspec test
briancain Mar 19, 2018
7dae1ac
Add some "default" options to be validated
briancain Mar 20, 2018
956ed00
Update trigger config merge function
briancain Mar 20, 2018
e7274f1
Improve config rspec tests
briancain Mar 20, 2018
a5d8cc1
Update trigger classes with comments
briancain Mar 21, 2018
4ecf682
Add basic trigger plugin scaffold
briancain Mar 22, 2018
372a6a7
Initial trigger plugin scaffolding
briancain Mar 28, 2018
1462b3f
Simplify trigger command filtering
briancain Mar 28, 2018
0e5cd90
Add missing param
briancain Mar 28, 2018
76418b9
Simplify trigger selection
briancain Mar 28, 2018
6373441
Ensure guest name is a string
briancain Mar 28, 2018
09bb986
Add basic website pages for triggers
briancain Mar 29, 2018
eac2fcf
Filter triggers based on only_on restraint
briancain Mar 29, 2018
bbf4e3c
Reduce fire trigger methods to a single method
briancain Mar 29, 2018
93af398
Update params for NoStageGiven error
briancain Mar 29, 2018
48b7c68
Use active machine object
briancain Mar 29, 2018
616b0f9
Preserve returning environment after machine action gets called
briancain Mar 29, 2018
0953287
Relax only_on rspec tests
briancain Mar 29, 2018
fbad4c7
Add basic spec test for trigger class
briancain Mar 29, 2018
d750169
Add some logging around run and run_remote
briancain Mar 29, 2018
975b8e7
Get basic run triggers executing inline scripts
briancain Mar 29, 2018
59965f4
Properly filter triggers
briancain Mar 29, 2018
8b70abd
Send the correct config message to warn
briancain Mar 29, 2018
e078767
run now supports script files as well as inline scripts
briancain Mar 29, 2018
fd28783
Move exception warnings in run function
briancain Mar 29, 2018
2c30eab
Don't run local commands as sudo by default
briancain Mar 30, 2018
a4faaa1
Provision run_remote using the shell provisioner
briancain Mar 30, 2018
8b71c24
Match properly on guests with trigger filtering
briancain Mar 30, 2018
a857056
Update color for run_remote to match shell provisioner colors
briancain Mar 30, 2018
a1e4fe4
Align how local provisioner looks with shell provisioner
briancain Mar 30, 2018
600557f
Move strings to translation file
briancain Mar 30, 2018
85dedf2
Ensure that :all triggers don't get filtered out
briancain Mar 30, 2018
6ea7819
Update rspec tests for trigger plugin
briancain Mar 30, 2018
c832168
Update trigger config docs
briancain Mar 30, 2018
ca672eb
Trigger plugin cleanup
briancain Mar 30, 2018
3ad4f57
Add only_on validation for config
briancain Mar 30, 2018
693eb2f
Add machine level warning when privileged option is set for run
briancain Apr 2, 2018
767a315
Trigger cleanup
briancain Apr 2, 2018
e7d07cb
Add rspec tests for trigger plugin
briancain Apr 2, 2018
7cdb32f
Use inclunde? instead of any? for ruby 2.3
briancain Apr 3, 2018
a5fe84d
Fix string translation
briancain Apr 3, 2018
6bf0196
Fix run command for windows hosts
briancain Apr 4, 2018
0054c5a
Raise error if incomplete trigger block is defined
briancain Apr 4, 2018
2dadc04
Fix trigger doc sidebar
briancain Apr 4, 2018
a204045
Add trigger documentation
briancain Apr 4, 2018
e0806f6
Remove TODO
briancain Apr 4, 2018
c35e0a4
Update docs with some basic trigger examples
briancain Apr 4, 2018
8451cbf
Move up location of error message
briancain Apr 4, 2018
4e5e4d0
Use args setting for run option in trigger
briancain Apr 5, 2018
6f2b5a9
Update trigger docs
briancain Apr 5, 2018
5e9387d
Add basic triggers example
briancain Apr 5, 2018
12b1a3d
Update triggers based on code review
briancain Apr 5, 2018
188fd5d
Split out run methods with Powershell functions
briancain Apr 6, 2018
e25cb51
Properly print warnings on config settings that aren't supported
briancain Apr 6, 2018
bd133e1
Docs updates for triggers
briancain Apr 6, 2018
2000a11
Add tests for powershell run functions
briancain Apr 6, 2018
a1ca29c
Add warn and info validation translation strings
briancain Apr 17, 2018
daffd87
Use match over match? for Ruby 2.3
briancain Apr 20, 2018
1eabd09
Website updates based on feedback
briancain Apr 23, 2018
a9be56b
Code updates based on PR feedback
briancain Apr 24, 2018
afc074f
Document valid trigger commands
briancain Apr 24, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/vagrant/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,18 @@ class SyncedFolderUnusable < VagrantError
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
Expand Down
9 changes: 8 additions & 1 deletion lib/vagrant/machine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ def initialize(name, provider_name, provider_cls, provider_config, provider_opti
# 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
Expand All @@ -159,6 +161,7 @@ def initialize(name, provider_name, provider_cls, provider_config, provider_opti
# 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 ||= {}
Expand All @@ -185,7 +188,7 @@ def action(name, opts=nil)
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)

Expand All @@ -203,6 +206,10 @@ def action(name, opts=nil)
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,
Expand Down
1 change: 1 addition & 0 deletions lib/vagrant/plugin/v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module V2
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
2 changes: 1 addition & 1 deletion lib/vagrant/plugin/v2/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def execute
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
Expand Down
242 changes: 242 additions & 0 deletions lib/vagrant/plugin/v2/trigger.rb
Original file line number Diff line number Diff line change
@@ -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 [Object] env Vagrant environment
# @param [Object] config Trigger configuration
# @param [Object] machine Active Machine
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be updated with actual types expected

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.nil?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.nil?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
21 changes: 21 additions & 0 deletions lib/vagrant/util/powershell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ def self.execute_cmd(command)
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]
Expand Down
Loading