├── lib ├── vagrant-triggers │ ├── version.rb │ ├── config.rb │ ├── action.rb │ ├── config │ │ ├── provisioner.rb │ │ └── trigger.rb │ ├── errors.rb │ ├── provisioner.rb │ ├── plugin.rb │ ├── action │ │ └── trigger.rb │ └── dsl.rb └── vagrant-triggers.rb ├── Rakefile ├── .gitignore ├── spec ├── vagrant-triggers │ ├── vagrant_spec.rb │ ├── config │ │ ├── provisioner_spec.rb │ │ └── trigger_spec.rb │ ├── provisioner_spec.rb │ ├── action │ │ └── trigger_spec.rb │ └── dsl_spec.rb └── spec_helper.rb ├── README.md ├── Gemfile ├── locales └── en.yml ├── LICENSE.txt ├── vagrant-triggers.gemspec └── CHANGELOG.md /lib/vagrant-triggers/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Triggers 3 | VERSION = "0.5.4" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new 5 | 6 | task :default => "spec" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .bundle 4 | pkg/* 5 | Gemfile.lock 6 | Gemfile.dev 7 | Gemfile.dev.lock 8 | 9 | coverage 10 | 11 | .rspec 12 | 13 | .vagrant 14 | .vagrant.d/* 15 | Vagrantfile 16 | -------------------------------------------------------------------------------- /spec/vagrant-triggers/vagrant_spec.rb: -------------------------------------------------------------------------------- 1 | describe "Vagrant" do 2 | it "can run vagrant with the plugin loaded" do 3 | env = Vagrant::Environment.new 4 | expect(env.cli("-h")).to eq(0) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vagrant-triggers has been [merged](https://github.com/hashicorp/vagrant/pull/9713) into Vagrant! 2 | 3 | You can find the latest version of the source code [inside the vagrant repository](https://github.com/hashicorp/vagrant), where it will continue to be developed. 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "simplecov" 2 | SimpleCov.start 3 | 4 | require "vagrant" 5 | require_relative "../lib/vagrant-triggers" 6 | 7 | VagrantPlugins::Triggers::Plugin.init_i18n 8 | 9 | RSpec.configure do |config| 10 | config.mock_with :rspec do |mocks| 11 | mocks.syntax = :should 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/vagrant-triggers/config.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Triggers 3 | module Config 4 | # Autoload farm 5 | config_root = Pathname.new(File.expand_path("../config", __FILE__)) 6 | autoload :Provisioner, config_root.join("provisioner") 7 | autoload :Trigger, config_root.join("trigger") 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/vagrant-triggers/action.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Triggers 3 | module Action 4 | def self.action_trigger(condition) 5 | Vagrant::Action::Builder.new.tap do |b| 6 | b.use Trigger, condition 7 | end 8 | end 9 | 10 | # Autoload farm 11 | action_root = Pathname.new(File.expand_path("../action", __FILE__)) 12 | autoload :Trigger, action_root.join("trigger") 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | # Warning: Hack below. 6 | # 7 | # Add the current project gem to the "plugins" group 8 | dependencies.find { |dep| dep.name == "vagrant-triggers" }.instance_variable_set(:@groups, [:default, :plugins]) 9 | 10 | group :development do 11 | gem "vagrant", :github => "mitchellh/vagrant", :ref => ENV.fetch("VAGRANT_VERSION", "master") 12 | end 13 | 14 | group :test do 15 | gem "simplecov", :require => false 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-triggers/config/provisioner.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Triggers 3 | module Config 4 | class Provisioner < Vagrant.plugin("2", :config) 5 | attr_reader :options 6 | attr_reader :trigger_body 7 | 8 | def initialize 9 | @options = { 10 | :good_exit => [0], 11 | :stderr => true, 12 | :stdout => true 13 | } 14 | end 15 | 16 | def fire(&block) 17 | @trigger_body = block 18 | end 19 | 20 | def set_options(options) 21 | @options.merge!(options) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/vagrant-triggers/errors.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Triggers 3 | module Errors 4 | class VagrantTriggerError < Vagrant::Errors::VagrantError 5 | error_namespace("vagrant_triggers.errors") 6 | end 7 | 8 | class CommandFailed < VagrantTriggerError 9 | error_key(:command_failed) 10 | end 11 | 12 | class CommandUnavailable < VagrantTriggerError 13 | error_key(:command_unavailable) 14 | end 15 | 16 | class DSLError < VagrantTriggerError 17 | error_key(:dsl_error) 18 | end 19 | 20 | class NotMatchingMachine < VagrantTriggerError 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/vagrant-triggers/provisioner.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Triggers 3 | class Provisioner < Vagrant.plugin("2", :provisioner) 4 | def initialize(machine, config) 5 | @config = config 6 | begin 7 | @dsl = DSL.new(machine, @config.options) 8 | rescue Errors::NotMatchingMachine 9 | ENV["VAGRANT_NO_TRIGGERS"] = "1" 10 | end 11 | end 12 | 13 | def configure(root_config) 14 | end 15 | 16 | def provision 17 | unless ENV["VAGRANT_NO_TRIGGERS"] 18 | @dsl.instance_eval &@config.trigger_body 19 | end 20 | end 21 | 22 | def cleanup 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/vagrant-triggers.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | require "vagrant-triggers/plugin" 3 | 4 | module VagrantPlugins 5 | module Triggers 6 | lib_path = Pathname.new(File.expand_path("../vagrant-triggers", __FILE__)) 7 | autoload :Action, lib_path.join("action") 8 | autoload :DSL, lib_path.join("dsl") 9 | autoload :Config, lib_path.join("config") 10 | autoload :Errors, lib_path.join("errors") 11 | autoload :Provisioner, lib_path.join("provisioner") 12 | 13 | # This returns the path to the source of this plugin. 14 | # 15 | # @return [Pathname] 16 | def self.source_root 17 | @source_root ||= Pathname.new(File.expand_path("../../", __FILE__)) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_triggers: 3 | action: 4 | trigger: 5 | command_finished: |- 6 | Command execution finished. 7 | executing_command: |- 8 | Executing command "%{command}"... 9 | executing_remote_command: |- 10 | Executing remote command "%{command}"... 11 | remote_command_finished: |- 12 | Remote command execution finished. 13 | running_triggers: |- 14 | Running triggers %{condition} %{action}... 15 | errors: 16 | command_failed: |- 17 | The %{context} command "%{command}" returned a failed exit 18 | code or an exception. The error output is shown below: 19 | 20 | %{stderr} 21 | command_unavailable: |- 22 | The executable "%{command}" Vagrant is trying to trigger 23 | was not found. If you know it is not in the PATH, please 24 | specify its absolute path using the :append_to_path option. 25 | dsl_error: |- 26 | Trigger failed with an error. 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Emiliano Ticci 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /spec/vagrant-triggers/config/provisioner_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe VagrantPlugins::Triggers::Config::Provisioner do 4 | let(:config) { described_class.new } 5 | 6 | describe "defaults" do 7 | it "should default :good_exit option to [0]" do 8 | expect(config.options[:good_exit]).to eq([0]) 9 | end 10 | 11 | it "should default :stderr option to true" do 12 | expect(config.options[:stderr]).to be true 13 | end 14 | 15 | it "should default :stdout option to true" do 16 | expect(config.options[:stdout]).to be true 17 | end 18 | end 19 | 20 | describe "fire" do 21 | it "should record trigger code" do 22 | code = Proc.new { "foo" } 23 | config.fire(&code) 24 | expect(config.trigger_body).to eq(code) 25 | end 26 | end 27 | 28 | describe "set_options" do 29 | it "should set options" do 30 | options = { :foo => "bar" } 31 | config.set_options(options) 32 | expect(config.options[:foo]).to eq("bar") 33 | end 34 | 35 | it "should override defaults" do 36 | config.set_options(:stdout => false) 37 | expect(config.options[:stdout]).to be false 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/vagrant-triggers/provisioner_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe VagrantPlugins::Triggers::Provisioner do 4 | let(:config) { double("config", :options => options, :trigger_body => Proc.new { "foo" }) } 5 | let(:machine) { double("machine") } 6 | let(:options) { double("options") } 7 | 8 | before :each do 9 | ENV["VAGRANT_NO_TRIGGERS"] = nil 10 | end 11 | 12 | describe "constructor" do 13 | it "should create a DSL object" do 14 | VagrantPlugins::Triggers::DSL.should_receive(:new).with(machine, options) 15 | described_class.new(machine, config) 16 | end 17 | 18 | it "should handle gracefully a not matching :vm option" do 19 | VagrantPlugins::Triggers::DSL.stub(:new).and_raise(VagrantPlugins::Triggers::Errors::NotMatchingMachine) 20 | expect { described_class.new(machine, config) }.not_to raise_exception() 21 | end 22 | end 23 | 24 | describe "provision" do 25 | before :each do 26 | @dsl = double("dsl") 27 | VagrantPlugins::Triggers::DSL.stub(:new).with(machine, options).and_return(@dsl) 28 | end 29 | 30 | it "should run code against DSL object" do 31 | @dsl.should_receive(:instance_eval).and_yield 32 | described_class.new(machine, config).provision 33 | end 34 | 35 | it "should not run code if VAGRANT_NO_TRIGGERS is set" do 36 | ENV["VAGRANT_NO_TRIGGERS"] = "1" 37 | @dsl.should_not_receive(:instance_eval) 38 | described_class.new(machine, config).provision 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/vagrant-triggers/plugin.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | # This is a sanity check to make sure no one is attempting to install 4 | # this into an early Vagrant version. 5 | if Vagrant::VERSION < "1.2.0" 6 | raise "The Vagrant Triggers plugin is only compatible with Vagrant 1.2+" 7 | end 8 | 9 | if ["1.6.0", "1.6.1"].include?(Vagrant::VERSION) 10 | warn <<-WARNING.gsub /^\s{2}/, "" 11 | The Vagrant version you're using contains a bug that prevents some 12 | triggers to work as expected. Update to version 1.6.2+ if you 13 | want to avoid issues. 14 | 15 | WARNING 16 | end 17 | 18 | module VagrantPlugins 19 | module Triggers 20 | class Plugin < Vagrant.plugin("2") 21 | name "Triggers" 22 | description <<-DESC 23 | This plugin allow the definition of arbitrary scripts that 24 | will run on the host before and/or after Vagrant commands. 25 | DESC 26 | 27 | action_hook(:init_i18n, :environment_load) { init_i18n } 28 | 29 | action_hook(:trigger, Plugin::ALL_ACTIONS) do |hook| 30 | require_relative "action" 31 | unless ENV["VAGRANT_NO_TRIGGERS"] 32 | [:before, :instead_of, :after].each { |condition| hook.prepend(Action.action_trigger(condition)) } 33 | end 34 | end 35 | 36 | config(:trigger) do 37 | require_relative "config/trigger" 38 | Config::Trigger 39 | end 40 | 41 | config(:trigger, :provisioner) do 42 | require_relative "config/provisioner" 43 | Config::Provisioner 44 | end 45 | 46 | provisioner(:trigger) do 47 | require_relative "provisioner" 48 | Provisioner 49 | end 50 | 51 | # This initializes the I18n load path so that the plugin specific 52 | # translations work. 53 | def self.init_i18n 54 | I18n.load_path << File.expand_path("locales/en.yml", Triggers.source_root) 55 | I18n.reload! 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/vagrant-triggers/config/trigger.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Triggers 3 | module Config 4 | class Trigger < Vagrant.plugin("2", :config) 5 | attr_reader :triggers 6 | 7 | def initialize 8 | @blacklist = [] 9 | @options = { :good_exit => [0], :stderr => true, :stdout => true } 10 | @triggers = [] 11 | end 12 | 13 | def after(actions, options = {}, &block) 14 | add_trigger(actions, :after, options, block) 15 | end 16 | 17 | def before(actions, options = {}, &block) 18 | add_trigger(actions, :before, options, block) 19 | end 20 | 21 | def blacklist(actions = nil) 22 | if actions 23 | Array(actions).each { |action| @blacklist << action.to_s } 24 | @blacklist.uniq! 25 | end 26 | @blacklist 27 | end 28 | 29 | def instead_of(actions, options = {}, &block) 30 | add_trigger(actions, :instead_of, options, block) 31 | end 32 | alias_method :reject, :instead_of 33 | 34 | def merge(other) 35 | super.tap do |result| 36 | result.instance_variable_set(:@blacklist, @blacklist + other.blacklist) 37 | result.instance_variable_set(:@triggers, @triggers + other.triggers) 38 | end 39 | end 40 | 41 | def validate(machine) 42 | errors = [] 43 | 44 | if @__invalid_methods && !@__invalid_methods.empty? 45 | errors << I18n.t("vagrant.config.common.bad_field", :fields => @__invalid_methods.to_a.sort.join(", ")) 46 | end 47 | 48 | { "triggers" => errors } 49 | end 50 | 51 | private 52 | 53 | def add_trigger(actions, condition, options, proc) 54 | Array(actions).each do |action| 55 | @triggers << { :action => action, :condition => condition, :options => @options.merge(options), :proc => proc } 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/vagrant-triggers/action/trigger.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | 3 | module VagrantPlugins 4 | module Triggers 5 | module Action 6 | class Trigger 7 | def initialize(app, env, condition) 8 | @app = app 9 | @condition = condition 10 | @env = env 11 | @exit = false 12 | @logger = Log4r::Logger.new("vagrant::plugins::triggers::trigger") 13 | end 14 | 15 | def call(env) 16 | fire_triggers unless @condition == :after 17 | @app.call(env) unless @exit 18 | fire_triggers if @condition == :after 19 | end 20 | 21 | private 22 | 23 | def fire_triggers 24 | # Triggers don't fire on environment load and unload. 25 | return if [:environment_load, :environment_plugins_loaded, :environment_unload].include?(@env[:action_name]) 26 | 27 | # Also don't fire if machine action is not defined. 28 | return unless @env[:machine_action] 29 | 30 | @logger.debug("Looking for triggers with:") 31 | trigger_env.each { |k, v| @logger.debug("-- #{k}: #{v}")} 32 | 33 | # Loop through all defined triggers checking for matches. 34 | triggers_config = @env[:machine].config.trigger 35 | triggers_to_fire = [].tap do |triggers| 36 | triggers_config.triggers.each do |trigger| 37 | next if trigger[:action] != :ALL && trigger[:action] != trigger_env[:action] 38 | next if trigger[:condition] != trigger_env[:condition] 39 | 40 | next if triggers_config.blacklist.include?(trigger_env[:action]) 41 | 42 | triggers << trigger 43 | end 44 | end 45 | 46 | unless triggers_to_fire.empty? 47 | @env[:ui].info I18n.t("vagrant_triggers.action.trigger.running_triggers", trigger_env).gsub('_', ' ') 48 | @exit = true if trigger_env[:condition] == :instead_of 49 | end 50 | 51 | triggers_to_fire.each do |trigger| 52 | DSL.fire!(trigger, @env[:machine]) 53 | end 54 | end 55 | 56 | def trigger_env 57 | { 58 | :action => @env[:machine_action], 59 | :condition => @condition, 60 | :vm => @env[:machine].name 61 | } 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /vagrant-triggers.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path("../lib", __FILE__) 2 | require "vagrant-triggers/version" 3 | 4 | Gem::Specification.new do |spec| 5 | spec.name = "vagrant-triggers" 6 | spec.version = VagrantPlugins::Triggers::VERSION 7 | spec.authors = "Emiliano Ticci" 8 | spec.email = "emiticci@gmail.com" 9 | spec.homepage = "https://github.com/emyl/vagrant-triggers" 10 | spec.summary = "Triggers for Vagrant commands." 11 | spec.description = "This plugin allow the definition of arbitrary scripts that will run on the host before and/or after Vagrant commands." 12 | spec.license = "MIT" 13 | 14 | # The following block of code determines the files that should be included 15 | # in the gem. It does this by reading all the files in the directory where 16 | # this gemspec is, and parsing out the ignored files from the gitignore. 17 | # Note that the entire gitignore(5) syntax is not supported, specifically 18 | # the "!" syntax, but it should mostly work correctly. 19 | root_path = File.dirname(__FILE__) 20 | all_files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") } 21 | all_files.reject! { |file| [".", ".."].include?(File.basename(file)) } 22 | all_files.reject! { |file| file.start_with?("coverage/") } 23 | gitignore_path = File.join(root_path, ".gitignore") 24 | gitignore = File.readlines(gitignore_path) 25 | gitignore.map! { |line| line.chomp.strip } 26 | gitignore.reject! { |line| line.empty? || line =~ /^(#|!)/ } 27 | 28 | unignored_files = all_files.reject do |file| 29 | # Ignore any directories, the gemspec only cares about files 30 | next true if File.directory?(file) 31 | 32 | # Ignore any paths that match anything in the gitignore. We do 33 | # two tests here: 34 | # 35 | # - First, test to see if the entire path matches the gitignore. 36 | # - Second, match if the basename does, this makes it so that things 37 | # like '.DS_Store' will match sub-directories too (same behavior 38 | # as git). 39 | # 40 | gitignore.any? do |ignore| 41 | File.fnmatch(ignore, file, File::FNM_PATHNAME) || 42 | File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME) 43 | end 44 | end 45 | 46 | spec.files = unignored_files 47 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 48 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 49 | spec.require_path = "lib" 50 | 51 | spec.add_development_dependency "rake" 52 | spec.add_development_dependency "rspec" 53 | end 54 | -------------------------------------------------------------------------------- /spec/vagrant-triggers/config/trigger_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe VagrantPlugins::Triggers::Config::Trigger do 4 | let(:config) { described_class.new } 5 | let(:machine) { double("machine") } 6 | 7 | describe "defaults" do 8 | subject do 9 | config.tap do |o| 10 | o.finalize! 11 | end 12 | expect(config.triggers).to eq([]) 13 | end 14 | 15 | it "should default :good_exit option to [0]" do 16 | config.before(:up) { run "ls" } 17 | expect(config.triggers.first[:options][:good_exit]).to eq([0]) 18 | end 19 | 20 | it "should default :stderr option to true" do 21 | config.before(:up) { run "ls" } 22 | expect(config.triggers.first[:options][:stderr]).to eq(true) 23 | end 24 | 25 | it "should default :stdout option to true" do 26 | config.before(:up) { run "ls" } 27 | expect(config.triggers.first[:options][:stdout]).to eq(true) 28 | end 29 | 30 | it "should override :good_exit option" do 31 | config.before(:up, :good_exit => [0, 2]) { run "ls" } 32 | expect(config.triggers.first[:options][:good_exit]).to eq([0, 2]) 33 | end 34 | 35 | it "should override :stderr option" do 36 | config.before(:up, :stderr => false) { run "ls" } 37 | expect(config.triggers.first[:options][:stderr]).to eq(false) 38 | end 39 | 40 | it "should override :stdout option" do 41 | config.before(:up, :stdout => false) { run "ls" } 42 | expect(config.triggers.first[:options][:stdout]).to eq(false) 43 | end 44 | end 45 | 46 | describe "add triggers" do 47 | it "should add before triggers" do 48 | config.before(:up) { run "ls" } 49 | expect(config.triggers.size).to eq(1) 50 | end 51 | 52 | it "should add instead_of triggers" do 53 | config.instead_of(:up) { run "ls" } 54 | expect(config.triggers.size).to eq(1) 55 | end 56 | 57 | it "should add after triggers" do 58 | config.after(:up) { run "ls" } 59 | expect(config.triggers.size).to eq(1) 60 | end 61 | end 62 | 63 | describe "blacklist" do 64 | it "should blacklist an action" do 65 | config.blacklist(:up) 66 | expect(config.blacklist.size).to eq(1) 67 | end 68 | 69 | it "should blacklist multiple actions" do 70 | config.blacklist([:up, :destroy]) 71 | expect(config.blacklist.size).to eq(2) 72 | end 73 | 74 | it "should convert symbols to strings" do 75 | config.blacklist(:up) 76 | expect(config.blacklist).to eq(["up"]) 77 | end 78 | 79 | it "should blacklist an action only once" do 80 | config.blacklist(["up", "destroy"]) 81 | config.blacklist(:up) 82 | expect(config.blacklist).to eq(["up", "destroy"]) 83 | end 84 | end 85 | 86 | describe "accept multiple entries" do 87 | it "should record multiple entries" do 88 | config.before(:up) { run "ls" } 89 | config.after(:up) { run "ls" } 90 | expect(config.triggers.size).to eq(2) 91 | end 92 | 93 | it "should record multiple entries if the action is an array" do 94 | config.before([:up, :halt]) { run "ls" } 95 | expect(config.triggers.size).to eq(2) 96 | end 97 | end 98 | 99 | describe "validation" do 100 | it "should validate" do 101 | config.finalize! 102 | expect(config.validate(machine)["triggers"].size).to eq(0) 103 | end 104 | 105 | it "shouldn't accept invalid methods" do 106 | config.foo "bar" 107 | expect(config.validate(machine)["triggers"].size).to eq(1) 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /spec/vagrant-triggers/action/trigger_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe VagrantPlugins::Triggers::Action::Trigger do 4 | let(:app) { lambda { |env| } } 5 | let(:env) { { :action_name => action_name, :machine => machine, :machine_action => machine_action, :ui => ui } } 6 | let(:condition) { double("condition") } 7 | let(:action_name) { double("action_name") } 8 | let(:machine) { double("machine", :ui => ui) } 9 | let(:machine_action) { double("machine_action") } 10 | 11 | let(:ui) { double("ui", :info => info) } 12 | let(:info) { double("info") } 13 | 14 | before :each do 15 | trigger_block = Proc.new { nil } 16 | @triggers = [ { :action => machine_action, :condition => condition, :options => { }, :proc => trigger_block } ] 17 | machine.stub(:name) 18 | machine.stub_chain(:config, :trigger, :blacklist).and_return([]) 19 | machine.stub_chain(:config, :trigger, :triggers).and_return(@triggers) 20 | end 21 | 22 | it "should skip :environment_load and :environment_unload actions" do 23 | [:environment_load, :environment_unload].each do |action| 24 | env.stub(:[]).with(:action_name).and_return(action) 25 | VagrantPlugins::Triggers::DSL.should_not_receive(:new) 26 | described_class.new(app, env, condition).call(env) 27 | end 28 | end 29 | 30 | it "shouldn't fire if machine action is not defined" do 31 | env.stub(:[]).with(:action_name) 32 | env.stub(:[]).with(:machine_action).and_return(nil) 33 | VagrantPlugins::Triggers::DSL.should_not_receive(:new) 34 | described_class.new(app, env, condition).call(env) 35 | end 36 | 37 | it "should fire trigger when all conditions are satisfied" do 38 | dsl = double("dsl") 39 | VagrantPlugins::Triggers::DSL.stub(:new).with(machine, @triggers.first[:options]).and_return(dsl) 40 | dsl.should_receive(:instance_eval).and_yield 41 | described_class.new(app, env, condition).call(env) 42 | end 43 | 44 | it "should fire trigger when condition matches and action is :ALL" do 45 | @triggers[0][:action] = :ALL 46 | dsl = double("dsl") 47 | VagrantPlugins::Triggers::DSL.stub(:new).with(machine, @triggers.first[:options]).and_return(dsl) 48 | dsl.should_receive(:instance_eval).and_yield 49 | described_class.new(app, env, condition).call(env) 50 | end 51 | 52 | it "should fire all defined triggers" do 53 | @triggers << @triggers.first 54 | VagrantPlugins::Triggers::DSL.should_receive(:new).twice 55 | described_class.new(app, env, condition).call(env) 56 | end 57 | 58 | it "shouldn't execute trigger with no command or block" do 59 | @triggers[0][:proc] = nil 60 | dsl = double("dsl") 61 | dsl.should_not_receive(:instance_eval) 62 | described_class.new(app, env, condition).call(env) 63 | end 64 | 65 | it "shouldn't fire trigger when the action is blacklisted" do 66 | machine.stub_chain(:config, :trigger, :blacklist).and_return([machine_action]) 67 | VagrantPlugins::Triggers::DSL.should_not_receive(:new) 68 | described_class.new(app, env, condition).call(env) 69 | end 70 | 71 | it "shouldn't fire trigger when condition doesn't match" do 72 | @triggers[0][:condition] = :blah 73 | VagrantPlugins::Triggers::DSL.should_not_receive(:new) 74 | described_class.new(app, env, condition).call(env) 75 | end 76 | 77 | it "shouldn't fire trigger when action doesn't match" do 78 | @triggers[0][:action] = :blah 79 | VagrantPlugins::Triggers::DSL.should_not_receive(:new) 80 | described_class.new(app, env, condition).call(env) 81 | end 82 | 83 | it "shouldn't carry on in the middleware chain on instead_of condition" do 84 | @triggers[0][:condition] = :instead_of 85 | app.should_not_receive(:call).with(env) 86 | described_class.new(app, env, :instead_of).call(env) 87 | end 88 | 89 | it "should handle gracefully a not matching :vm option" do 90 | VagrantPlugins::Triggers::DSL.stub(:new).and_raise(VagrantPlugins::Triggers::Errors::NotMatchingMachine) 91 | expect { described_class.new(app, env, condition).call(env) }.not_to raise_exception() 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.4 (June 6, 2018) 2 | 3 | - Fixes Vagrant 1.9+ compatability. 4 | 5 | ## 0.5.3 (April 9, 2016) 6 | 7 | IMPROVEMENTS: 8 | 9 | - Add ```:good_exit``` option for specifying custom positive exit codes [(#37)](https://github.com/emyl/vagrant-triggers/issues/37) 10 | 11 | BUG FIXES: 12 | 13 | - Gracefully catch communication errors [(#55)](https://github.com/emyl/vagrant-triggers/issues/55) 14 | 15 | ## 0.5.2 (September 9, 2015) 16 | 17 | BUG FIXES: 18 | 19 | - Skip subprocess jailbreak introduced in Vagrant 1.7.3 [(#52)](https://github.com/emyl/vagrant-triggers/issues/52) 20 | 21 | ## 0.5.1 (August 3, 2015) 22 | 23 | BUG FIXES: 24 | 25 | - Change directory to environment root path before running host commands [(#44)](https://github.com/emyl/vagrant-triggers/issues/44) 26 | - ```:stdout``` and ```:stderr``` options defaults to true when using provisioner [(#42)](https://github.com/emyl/vagrant-triggers/issues/42) 27 | - Properly escape regexp in DSL [(#46)](https://github.com/emyl/vagrant-triggers/issues/46) 28 | 29 | ## 0.5.0 (December 29, 2014) 30 | 31 | **BEHAVIOURAL CHANGES:** 32 | 33 | - The ```:stdout``` option now defaults to true. 34 | 35 | NEW FEATURES: 36 | 37 | - New option ```:stderr``` for displaying standard error from scripts. 38 | - The special action ```:ALL``` can be used when a trigger should always run [(#23)](https://github.com/emyl/vagrant-triggers/issues/23) 39 | - Actions can be blacklisted using ```config.trigger.blacklist```. 40 | - Triggers can be run as a provisioner [(#21)](https://github.com/emyl/vagrant-triggers/issues/21) 41 | 42 | IMPROVEMENTS: 43 | 44 | - Do not buffer command output and better integrate in core UI [(#18)](https://github.com/emyl/vagrant-triggers/issues/18) 45 | 46 | BUG FIXES: 47 | 48 | - Handle MS-DOS commands better [(#27)](https://github.com/emyl/vagrant-triggers/issues/27) 49 | 50 | ## 0.4.4 (December 12, 2014) 51 | 52 | BUG FIXES: 53 | 54 | - Enforce bundler dependency [(#34)](https://github.com/emyl/vagrant-triggers/issues/34) 55 | 56 | ## 0.4.3 (November 26, 2014) 57 | 58 | BUG FIXES: 59 | 60 | - Passing strings to ```:vm``` option now works [(#30)](https://github.com/emyl/vagrant-triggers/issues/30) 61 | - Use Bundler.with_clean_env to remove bundler artifacts from environment [(#25)](https://github.com/emyl/vagrant-triggers/issues/25) 62 | 63 | ## 0.4.2 (September 12, 2014) 64 | 65 | IMPROVEMENTS: 66 | 67 | - Use Vagrant communicator interface for running remote commands [(#17)](https://github.com/emyl/vagrant-triggers/issues/17) 68 | - Allow options to be overridden by a single command. 69 | 70 | ## 0.4.1 (June 20, 2014) 71 | 72 | BUG FIXES: 73 | 74 | - Ensure after triggers are run at the very end of the action [(#14)](https://github.com/emyl/vagrant-triggers/issues/14) 75 | 76 | ## 0.4.0 (May 20, 2014) 77 | 78 | NEW FEATURES: 79 | 80 | - New trigger type: ```instead_of```. 81 | - Add ```:vm``` option for choosing the target vm(s) in case of multi-machine Vagrantfile. 82 | 83 | IMPROVEMENTS: 84 | 85 | - DSL: add ```run_remote``` as alias to ```run("vagrant ssh -c ...")```. 86 | - DSL: the ```error``` statement now stops the action and makes Vagrant fail with an error. 87 | - Ensure the ```run``` statement always returns the command output. 88 | 89 | BUG FIXES: 90 | 91 | - Use additive merge logic [(#12)](https://github.com/emyl/vagrant-triggers/issues/12) 92 | - Remove bundler settings from RUBYOPT [(reopened #5)](https://github.com/emyl/vagrant-triggers/issues/5) 93 | 94 | ## 0.3.0 (April 4, 2014) 95 | 96 | CHANGES: 97 | 98 | - Implement a new DSL for running scripts. 99 | 100 | DEPRECATIONS: 101 | 102 | - The ```:info``` and ```:execute``` options has been replaced by the new DSL. 103 | 104 | IMPROVEMENTS: 105 | 106 | - Plugin messages are now localized. 107 | 108 | BUG FIXES: 109 | 110 | - Avoid loops by adding VAGRANT_NO_TRIGGERS to the subprocess shell. 111 | 112 | ## 0.2.2 (March 1, 2014) 113 | 114 | NEW FEATURES: 115 | 116 | - Add ```:info``` as an alternative to ```:execute```. 117 | 118 | BUG FIXES: 119 | 120 | - Remove Vagrant specific environment variables when executing commands [(#5)](https://github.com/emyl/vagrant-triggers/issues/5) 121 | 122 | ## 0.2.1 (November 19, 2013) 123 | 124 | BUG FIXES: 125 | 126 | - Fixed regression in configuration [(#2)](https://github.com/emyl/vagrant-triggers/issues/2) 127 | 128 | ## 0.2.0 (October 19, 2013) 129 | 130 | NEW FEATURES: 131 | 132 | - New option: ```:append_to_path```. 133 | 134 | IMPROVEMENTS: 135 | 136 | - Triggers won't run if ```VAGRANT_NO_TRIGGERS``` environment variable is set. 137 | - Command argument could also be an array. 138 | 139 | ## 0.1.0 (August 19, 2013) 140 | 141 | - Initial release. 142 | -------------------------------------------------------------------------------- /lib/vagrant-triggers/dsl.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | require "vagrant/util/subprocess" 3 | 4 | module VagrantPlugins 5 | module Triggers 6 | class DSL 7 | def self.fire!(trigger, machine) 8 | begin 9 | dsl = new(machine, trigger[:options]) 10 | dsl.instance_eval &trigger[:proc] if trigger[:proc] 11 | rescue Errors::NotMatchingMachine 12 | end 13 | end 14 | 15 | def initialize(machine, options = {}) 16 | if options[:vm] 17 | match = false 18 | Array(options[:vm]).each do |pattern| 19 | match = true if machine.name.match(Regexp.new(pattern)) 20 | end 21 | raise Errors::NotMatchingMachine unless match 22 | end 23 | 24 | @buffer = Hash.new("") 25 | @logger = Log4r::Logger.new("vagrant::plugins::triggers::dsl") 26 | @machine = machine 27 | @options = { :good_exit => [0] }.merge(options) 28 | @ui = machine.ui 29 | 30 | @command_output = lambda do |channel, data, options| 31 | ui_method = (channel == :stdout) ? :info : :error 32 | @buffer[channel] += data 33 | @ui.send(ui_method, data) if options[channel] 34 | end 35 | end 36 | 37 | def error(message, *opts) 38 | raise Errors::DSLError, @ui.error(message, *opts) 39 | end 40 | 41 | def run(raw_command, options = {}) 42 | command = shellsplit(raw_command) 43 | options.merge!(@options) { |key, old, new| old } 44 | info I18n.t("vagrant_triggers.action.trigger.executing_command", :command => command.join(" ")) 45 | env_backup = ENV.to_hash 46 | begin 47 | result = nil 48 | Vagrant::Util::Env.with_clean_env do 49 | build_environment 50 | @buffer.clear 51 | Dir.chdir(@machine.env.root_path) do 52 | result = Vagrant::Util::Subprocess.execute(command[0], *command[1..-1], :notify => [:stdout, :stderr]) do |channel, data| 53 | @command_output.call(channel, data, options) 54 | end 55 | end 56 | end 57 | info I18n.t("vagrant_triggers.action.trigger.command_finished") 58 | rescue Vagrant::Errors::CommandUnavailable, Vagrant::Errors::CommandUnavailableWindows 59 | raise Errors::CommandUnavailable, :command => command[0] 60 | ensure 61 | ENV.replace(env_backup) 62 | end 63 | process_result("local", raw_command, result, options) 64 | end 65 | alias_method :execute, :run 66 | 67 | def run_remote(raw_command, options = {}) 68 | options.merge!(@options) { |key, old, new| old } 69 | info I18n.t("vagrant_triggers.action.trigger.executing_remote_command", :command => raw_command) 70 | @buffer.clear 71 | exit_code = 1 72 | begin 73 | exit_code = @machine.communicate.sudo(raw_command, :elevated => true, :good_exit => (0..255).to_a) do |channel, data| 74 | @command_output.call(channel, data, options) 75 | end 76 | rescue => e 77 | @command_output.call(:stderr, e.message, options) 78 | end 79 | info I18n.t("vagrant_triggers.action.trigger.remote_command_finished") 80 | process_result("remote", raw_command, Vagrant::Util::Subprocess::Result.new(exit_code, @buffer[:stdout], @buffer[:stderr]), options) 81 | end 82 | alias_method :execute_remote, :run_remote 83 | 84 | def method_missing(method, *args, &block) 85 | # If the @ui object responds to the given method, call it 86 | if @ui.respond_to?(method) 87 | @ui.send(method, *args, *block) 88 | else 89 | super(method, *args, &block) 90 | end 91 | end 92 | 93 | private 94 | 95 | def build_environment 96 | @logger.debug("Original environment: #{ENV.inspect}") 97 | 98 | # Remove GEM_ environment variables 99 | ["GEM_HOME", "GEM_PATH", "GEMRC"].each { |gem_var| ENV.delete(gem_var) } 100 | 101 | # Create the new PATH removing Vagrant bin directory 102 | # and appending directories specified through the 103 | # :append_to_path option 104 | new_path = ENV["VAGRANT_INSTALLER_ENV"] ? ENV["PATH"].gsub(/#{Regexp.quote(ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"])}.*?#{Regexp.quote(File::PATH_SEPARATOR)}/, "") : ENV["PATH"] 105 | new_path += Array(@options[:append_to_path]).map { |dir| "#{File::PATH_SEPARATOR}#{dir}" }.join 106 | ENV["PATH"] = new_path 107 | @logger.debug("PATH modified: #{ENV["PATH"]}") 108 | 109 | # Add the VAGRANT_NO_TRIGGERS variable to avoid loops 110 | ENV["VAGRANT_NO_TRIGGERS"] = "1" 111 | 112 | # Skip the subprocess jailbreak introduced in vagrant 1.7.3 113 | ENV["VAGRANT_SKIP_SUBPROCESS_JAILBREAK"] = "1" 114 | end 115 | 116 | def process_result(context, command, result, options) 117 | unless options[:good_exit].include?(result.exit_code) || options[:force] 118 | raise Errors::CommandFailed, :context => context, :command => command, :stderr => result.stderr 119 | end 120 | result.stdout 121 | end 122 | 123 | # This is a custom version of Shellwords.shellsplit adapted for handling MS-DOS commands. 124 | # 125 | # Basically escape sequences are left intact if the platform is Windows. 126 | def shellsplit(line) 127 | words = [] 128 | field = '' 129 | line.scan(/\G\s*(?>([^\s\\\'\"]+)|'([^\']*)'|"((?:[^\"\\]|\\.)*)"|(\\.?)|(\S))(\s|\z)?/) do |word, sq, dq, esc, garbage, sep| 130 | raise ArgumentError, "Unmatched double quote: #{line.inspect}" if garbage 131 | token = (word || sq || (dq || esc)) 132 | token.gsub!(/\\(.)/, '\1') unless Vagrant::Util::Platform.windows? 133 | field << token 134 | if sep 135 | words << field 136 | field = '' 137 | end 138 | end 139 | words 140 | end 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /spec/vagrant-triggers/dsl_spec.rb: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | require "spec_helper" 3 | 4 | describe VagrantPlugins::Triggers::DSL do 5 | let(:result) { double("result", :exit_code => 0, :stderr => stderr) } 6 | let(:stderr) { double("stderr") } 7 | 8 | let(:machine) { double("machine", :env => env, :ui => ui) } 9 | let(:env) { double("env", :root_path => ENV["PWD"]) } 10 | let(:ui) { double("ui", :info => info) } 11 | let(:info) { double("info") } 12 | 13 | before do 14 | @command = "foo" 15 | @dsl = described_class.new(machine, {}) 16 | 17 | result.stub(:stdout => "Some output") 18 | end 19 | 20 | context ":vm option" do 21 | before do 22 | machine.stub(:name => :vm1) 23 | end 24 | 25 | it "should raise no exception when :vm option match" do 26 | options = { :vm => "vm1" } 27 | expect { described_class.new(machine, options) }.not_to raise_error() 28 | end 29 | 30 | it "should raise NotMatchingMachine when :vm option doesn't match" do 31 | options = { :vm => "vm2" } 32 | expect { described_class.new(machine, options) }.to raise_error(VagrantPlugins::Triggers::Errors::NotMatchingMachine) 33 | end 34 | 35 | it "should raise no exception when :vm option is an array and one of the elements match" do 36 | options = { :vm => ["vm1", "vm2"] } 37 | expect { described_class.new(machine, options) }.not_to raise_error() 38 | end 39 | 40 | it "should raise NotMatchingMachine when :vm option is an array and no element match" do 41 | options = { :vm => ["vm2", "vm3"] } 42 | expect { described_class.new(machine, options) }.to raise_error(VagrantPlugins::Triggers::Errors::NotMatchingMachine) 43 | end 44 | 45 | it "should raise no exception when :vm option is a regex and the pattern match" do 46 | options = { :vm => /^vm/ } 47 | expect { described_class.new(machine, options) }.not_to raise_error() 48 | end 49 | 50 | it "should raise NotMatchingMachine when :vm option is a regex and the pattern doesn't match" do 51 | options = { :vm => /staging/ } 52 | expect { described_class.new(machine, options) }.to raise_error(VagrantPlugins::Triggers::Errors::NotMatchingMachine) 53 | end 54 | end 55 | 56 | context "error" do 57 | it "should raise a DSL error on UI error" do 58 | ui.should_receive(:error).with("Error message") 59 | expect { @dsl.error("Error message") }.to raise_error(VagrantPlugins::Triggers::Errors::DSLError) 60 | end 61 | end 62 | 63 | context "method missing" do 64 | it "acts as proxy if the ui object respond to the called method" do 65 | ui.stub(:foo).and_return("bar") 66 | expect(@dsl.foo).to eq("bar") 67 | end 68 | end 69 | 70 | context "run a regular command" do 71 | before do 72 | Vagrant::Util::Subprocess.stub(:execute => result) 73 | @options = { :notify => [:stdout, :stderr] } 74 | end 75 | 76 | it "should change directory to environment root path" do 77 | machine.stub_chain(:env, :root_path).and_return("/") 78 | Vagrant::Util::Subprocess.should_receive(:execute) do |command| 79 | expect(Dir.pwd).to eq("/") 80 | result 81 | end 82 | @dsl.run(@command) 83 | end 84 | 85 | it "should raise an error if executed command exits with non-zero code" do 86 | result.stub(:exit_code => 1) 87 | expect { @dsl.run(@command) }.to raise_error(VagrantPlugins::Triggers::Errors::CommandFailed) 88 | end 89 | 90 | it "shouldn't raise an error if executed command exits with non-zero code but :force option was specified" do 91 | dsl = described_class.new(machine, :force => true) 92 | result.stub(:exit_code => 1) 93 | expect { dsl.run(@command) }.not_to raise_error() 94 | end 95 | 96 | it "shouldn't raise an error if executed command exits with non-zero code but the code is included in the :good_exit array" do 97 | dsl = described_class.new(machine, :good_exit => [0, 2]) 98 | result.stub(:exit_code => 2) 99 | expect { dsl.run(@command) }.not_to raise_error() 100 | end 101 | 102 | it "should return standard output" do 103 | dsl = described_class.new(machine) 104 | expect(dsl.run(@command)).to eq("Some output") 105 | end 106 | 107 | it "should pass VAGRANT_NO_TRIGGERS environment variable to the command" do 108 | Vagrant::Util::Subprocess.should_receive(:execute) do |command| 109 | expect(ENV).to have_key("VAGRANT_NO_TRIGGERS") 110 | result 111 | end 112 | @dsl.run(@command) 113 | end 114 | 115 | it "should pass VAGRANT_SKIP_SUBPROCESS_JAILBREAK environment variable to the command" do 116 | Vagrant::Util::Subprocess.should_receive(:execute) do |command| 117 | expect(ENV).to have_key("VAGRANT_SKIP_SUBPROCESS_JAILBREAK") 118 | result 119 | end 120 | @dsl.run(@command) 121 | end 122 | 123 | it "should remove escape sequences on UNIX Bourne Shell" do 124 | command = "echo foo\\ bar" 125 | Vagrant::Util::Subprocess.should_receive(:execute).with("echo", "foo bar", @options) 126 | @dsl.run(command) 127 | end 128 | 129 | it "should not remove escape sequences on MS-DOS Shell" do 130 | Vagrant::Util::Platform.stub(:windows? => true) 131 | command = "echo foo\\ bar" 132 | Vagrant::Util::Subprocess.should_receive(:execute).with("echo", "foo\\ bar", @options) 133 | @dsl.run(command) 134 | end 135 | end 136 | 137 | context "run a command not in the PATH" do 138 | before do 139 | @tmp_dir = Vagrant::Util::Platform.windows? ? ENV["USERPROFILE"] : ENV["HOME"] 140 | File.open("#{@tmp_dir}/#{@command}", "w+", 0700) { |file| } 141 | File.stub(:executable? => false) 142 | File.stub(:executable?).with("#{@tmp_dir}/#{@command}").and_return(true) 143 | end 144 | 145 | after do 146 | File.delete("#{@tmp_dir}/#{@command}") 147 | end 148 | 149 | it "should raise a CommandUnavailable error by default" do 150 | expect { @dsl.run(@command) }.to raise_error(VagrantPlugins::Triggers::Errors::CommandUnavailable) 151 | end 152 | 153 | it "should raise a CommandUnavailable error on Windows" do 154 | Vagrant::Util::Platform.stub(:windows? => true) 155 | expect { @dsl.run(@command) }.to raise_error(VagrantPlugins::Triggers::Errors::CommandUnavailable) 156 | end 157 | 158 | it "should honor the :append_to_path option and restore original path after execution" do 159 | dsl = described_class.new(machine, :append_to_path => @tmp_dir) 160 | original_path = ENV["PATH"] 161 | dsl.run(@command) 162 | expect(ENV["PATH"]).to eq(original_path) 163 | end 164 | 165 | it "should accept an array for the :append_to_path option" do 166 | dsl = described_class.new(machine, :append_to_path => [@tmp_dir, @tmp_dir]) 167 | expect { dsl.run(@command) }.not_to raise_error() 168 | end 169 | end 170 | 171 | context "run a command simulating the Vagrant environment" do 172 | before do 173 | @original_path = ENV["PATH"] 174 | ENV["VAGRANT_INSTALLER_ENV"] = "1" 175 | ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] = Vagrant::Util::Platform.windows? ? ENV["USERPROFILE"] : ENV["HOME"] 176 | ENV["GEM_HOME"] = "#{ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]}/gems" 177 | ENV["GEM_PATH"] = ENV["GEM_HOME"] 178 | ENV["GEMRC"] = "#{ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]}/etc/gemrc" 179 | ENV["PATH"] = "#{ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]}/bin:#{ENV["PATH"]}" 180 | ENV["RUBYOPT"] = "-rbundler/setup" 181 | end 182 | 183 | context "with a command which is present into the Vagrant embedded dir" do 184 | before do 185 | Dir.mkdir("#{ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]}/bin") 186 | File.open("#{ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]}/bin/#{@command}", "w+", 0700) { |file| } 187 | end 188 | 189 | it "should raise a CommandUnavailable error", :skip_travis => true do 190 | expect { @dsl.run(@command) }.to raise_error(VagrantPlugins::Triggers::Errors::CommandUnavailable) 191 | end 192 | 193 | after do 194 | FileUtils.rm_rf("#{ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]}/bin") 195 | end 196 | end 197 | 198 | ["BUNDLE_BIN_PATH", "BUNDLE_GEMFILE", "GEM_PATH", "GEMRC"].each do |env_var| 199 | it "should not pass #{env_var} to the executed command" do 200 | Vagrant::Util::Subprocess.should_receive(:execute) do |command| 201 | expect(ENV).not_to have_key(env_var) 202 | result 203 | end 204 | @dsl.run(@command) 205 | end 206 | end 207 | 208 | it "should remove bundler settings from RUBYOPT" do 209 | Vagrant::Util::Subprocess.should_receive(:execute) do |command| 210 | expect(ENV["RUBYOPT"]).to eq("") 211 | result 212 | end 213 | @dsl.run(@command) 214 | end 215 | 216 | after do 217 | ENV["EMBEDDED_DIR"] = nil 218 | ENV["GEM_HOME"] = nil 219 | ENV["GEM_PATH"] = nil 220 | ENV["GEMRC"] = nil 221 | ENV["PATH"] = @original_path 222 | ENV["RUBYOPT"] = nil 223 | end 224 | end 225 | 226 | context "run a remote command" do 227 | before do 228 | Vagrant::Util::Subprocess::Result.stub(:new => result) 229 | machine.stub_chain(:communicate, :sudo).and_return(0) 230 | end 231 | 232 | it "should raise an error if executed command exits with non-zero code" do 233 | result.stub(:exit_code => 1) 234 | expect { @dsl.run_remote(@command) }.to raise_error(VagrantPlugins::Triggers::Errors::CommandFailed) 235 | end 236 | 237 | it "should catch communication errors and raise a proper exception" do 238 | machine.stub_chain(:communicate, :sudo).and_raise(StandardError) 239 | result.stub(:exit_code => 1) 240 | expect { @dsl.run_remote(@command) }.to raise_error(VagrantPlugins::Triggers::Errors::CommandFailed) 241 | end 242 | 243 | it "shouldn't raise an error if executed command exits with non-zero code but :force option was specified" do 244 | dsl = described_class.new(machine, :force => true) 245 | result.stub(:exit_code => 1) 246 | expect { dsl.run_remote(@command) }.not_to raise_error() 247 | end 248 | 249 | it "shouldn't raise an error if executed command exits with non-zero code but the code is included in the :good_exit array" do 250 | dsl = described_class.new(machine, :good_exit => [0, 2]) 251 | result.stub(:exit_code => 2) 252 | expect { dsl.run_remote(@command) }.not_to raise_error() 253 | end 254 | 255 | it "should return standard output" do 256 | dsl = described_class.new(machine) 257 | expect(dsl.run_remote(@command)).to eq("Some output") 258 | end 259 | end 260 | end 261 | --------------------------------------------------------------------------------