From 50c4464d442f35a139ffa5ca916de5b2eb0966aa Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 4 Jun 2019 10:07:02 -0700 Subject: [PATCH] Support loading plugin information from nested Vagrantfiles Since plugin installation happens when the environment is first initialized, attempt to determine the provider in use and load any box provided Vagrantfiles to include any plugin configuration they may include. --- lib/vagrant/environment.rb | 49 +++++++++++- test/unit/vagrant/environment_test.rb | 105 +++++++++++++++++++++++++- test/unit/vagrant/util/ssh_test.rb | 6 +- 3 files changed, 151 insertions(+), 9 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index de3b88066..f0b8114ff 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -175,9 +175,7 @@ module Vagrant # Load any global plugins Vagrant::Plugin::Manager.instance.load_plugins(plugins) - if !vagrantfile.config.vagrant.plugins.empty? - plugins = process_configured_plugins - end + plugins = process_configured_plugins # Call the hooks that does not require configurations to be loaded # by using a "clean" action runner @@ -922,6 +920,49 @@ module Vagrant protected + # Attempt to guess the configured provider in use. Will fallback + # to the default provider if an explicit provider name is not + # provided. This can be pretty error prone, but is used during + # initial environment setup to allow loading plugins so it doesn't + # need to be perfect + # + # @return [String] + def guess_provider + gp = nil + ARGV.each_with_index do |val, idx| + if val.start_with?("--provider=") + gp = val.split("=", 2).last + break + elsif val == "--provider" + gp = ARGV[idx+1] + break + end + end + return gp if gp + begin + default_provider + rescue Errors::NoDefaultProvider + # if a provider cannot be determined just return nil + nil + end + end + + # Load any configuration provided by guests defined within + # the Vagrantfile to pull plugin information they may have + # defined. + def find_configured_plugins + plugins = [] + provider = guess_provider + vagrantfile.machine_names.each do |mname| + ldp = @local_data_path.join("machines/#{mname}/#{provider}") if @local_data_path + plugins << vagrantfile.machine_config(mname, guess_provider, boxes, ldp)[:config] + end + result = plugins.reverse.inject(Vagrant::Util::HashWithIndifferentAccess.new) do |memo, val| + Vagrant::Util::DeepMerge.deep_merge(memo, val.vagrant.plugins) + end + Vagrant::Util::DeepMerge.deep_merge(result, vagrantfile.config.vagrant.plugins) + end + # Check for any local plugins defined within the Vagrantfile. If # found, validate they are available. If they are not available, # request to install them, or raise an exception @@ -939,7 +980,7 @@ module Vagrant # Check if defined plugins are installed installed = Plugin::Manager.instance.installed_plugins needs_install = [] - config_plugins = vagrantfile.config.vagrant.plugins + config_plugins = find_configured_plugins config_plugins.each do |name, info| if !installed[name] needs_install << name diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index 6c92cd5ba..8be6ddeed 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -165,7 +165,7 @@ describe Vagrant::Environment do collection = double("collection") expect(Vagrant::BoxCollection).to receive(:new).with( - env.homedir.join("boxes"), anything).and_return(collection) + env.homedir.join("boxes"), anything).twice.and_return(collection) expect(collection).to receive(:upgrade_v1_1_v1_5).once subject end @@ -761,6 +761,7 @@ VF before do m = Vagrant.plugin("2").manager allow(m).to receive(:providers).and_return(plugin_providers) + allow_any_instance_of(described_class).to receive(:process_configured_plugins) end it "is the highest matching usable provider" do @@ -1431,6 +1432,108 @@ VF end end + describe "guess_provider" do + before { allow_any_instance_of(described_class).to receive(:process_configured_plugins) } + + it "should return the default provider by default" do + expect(subject).to receive(:default_provider).and_return("default_provider") + expect(subject.send(:guess_provider)).to eq("default_provider") + end + + context "when provider is defined via command line argument" do + before { stub_const("ARGV", argv) } + + context "when provider is given as single argument" do + let(:argv) { ["--provider=single_arg"] } + + it "should return the provider name" do + expect(subject.send(:guess_provider)).to eq("single_arg") + end + end + + context "when provider is given as two arguments" do + let(:argv) { ["--provider", "double_arg"] } + + it "should return the provider name" do + expect(subject.send(:guess_provider)).to eq("double_arg") + end + end + end + + context "when no default provider is available" do + before { + expect(subject).to receive(:default_provider). + and_raise(Vagrant::Errors::NoDefaultProvider) } + + it "should return a nil value" do + expect(subject.send(:guess_provider)).to be_nil + end + end + end + + describe "#find_configured_plugins" do + before do + allow_any_instance_of(described_class).to receive(:guess_provider).and_return(:dummy) + allow_any_instance_of(described_class).to receive(:process_configured_plugins) + end + + it "should find no plugins when no plugins are configured" do + expect(subject.send(:find_configured_plugins)).to be_empty + end + + context "when plugins are defined in the Vagrantfile" do + before do + env.vagrantfile <<-VF + Vagrant.configure("2") do |config| + config.vagrant.plugins = "vagrant-plugin" + end + VF + end + + it "should return the vagrant-plugin" do + expect(subject.send(:find_configured_plugins).keys).to include("vagrant-plugin") + end + end + + context "when plugins are defined in the Vagrantfile of a box" do + before do + env.box3("foo", "1.0", :dummy, vagrantfile: <<-VF) + Vagrant.configure("2") do |config| + config.vagrant.plugins = "vagrant-plugin" + end + VF + env.vagrantfile <<-VF + Vagrant.configure("2") do |config| + config.vm.box = "foo" + end + VF + end + + it "should return the vagrant-plugin" do + expect(subject.send(:find_configured_plugins).keys).to include("vagrant-plugin") + end + end + + context "when the box does not match the provider" do + before do + env.box3("foo", "1.0", :other, vagrantfile: <<-VF) + Vagrant.configure("2") do |config| + config.vagrant.plugins = "vagrant-plugin" + end + VF + env.vagrantfile <<-VF + Vagrant.configure("2") do |config| + config.vm.box = "foo" + end + VF + end + + it "should not return the vagrant-plugin" do + expect(subject.send(:find_configured_plugins).keys).not_to include("vagrant-plugin") + end + end + end + describe "#process_configured_plugins" do let(:env) do isolated_environment.tap do |e| diff --git a/test/unit/vagrant/util/ssh_test.rb b/test/unit/vagrant/util/ssh_test.rb index 9d84f0225..88d51539e 100644 --- a/test/unit/vagrant/util/ssh_test.rb +++ b/test/unit/vagrant/util/ssh_test.rb @@ -39,7 +39,7 @@ describe Vagrant::Util::SSH do dsa_authentication: true }} - let(:ssh_path) { "/usr/bin/ssh" } + let(:ssh_path) { /.*ssh/ } it "searches original PATH for executable" do expect(Vagrant::Util::Which).to receive(:which).with("ssh", original_path: true).and_return("valid-return") @@ -97,8 +97,6 @@ describe Vagrant::Util::SSH do dsa_authentication: true }} - let(:ssh_path) { "/usr/bin/ssh" } - it "uses the IdentityFile argument and escapes the '%' character" do allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil) described_class.exec(ssh_info) @@ -247,7 +245,7 @@ describe Vagrant::Util::SSH do it "enables ssh config loading" do allow(Vagrant::Util::SafeExec).to receive(:exec).and_return(nil) expect(Vagrant::Util::SafeExec).to receive(:exec) do |exe_path, *args| - expect(exe_path).to eq(ssh_path) + expect(exe_path).to match(ssh_path) config_options = ["-F", "/path/to/config"] expect(args & config_options).to eq(config_options) end