diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index b9739ffb0..e78ff0180 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -1,4 +1,5 @@ require "pathname" +require "set" require "tempfile" require "bundler" @@ -94,9 +95,15 @@ module Vagrant f.tap do |gemfile| gemfile.puts(%Q[source "https://rubygems.org"]) gemfile.puts(%Q[source "http://gems.hashicorp.com"]) - gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) - gemfile.puts("group :plugins do") + sources = plugins.values.map { |p| p["sources"] }.flatten.compact.uniq + sources.each do |source| + next if source == "" + gemfile.puts(%Q[source "#{source}"]) + end + gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) + + gemfile.puts("group :plugins do") plugins.each do |name, plugin| version = plugin["gem_version"] version = nil if version == "" @@ -108,9 +115,11 @@ module Vagrant gemfile.puts(%Q[gem "#{name}", #{version.inspect}, #{opts.inspect}]) end - gemfile.puts("end") + gemfile.close + + puts File.read(gemfile.path) end end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index f78dd9b63..ce21aef7b 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -168,6 +168,10 @@ module Vagrant error_key(:failed, "vagrant.actions.box.verify") end + class BundlerError < VagrantError + error_key(:bundler_error) + end + class CFEngineBootstrapFailed < VagrantError error_key(:cfengine_bootstrap_failed) end diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 9cb016411..db37423ba 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -33,6 +33,7 @@ module Vagrant plugins[name] = { "require" => opts[:require], "gem_version" => opts[:version], + "sources" => opts[:sources], } result = nil @@ -44,11 +45,17 @@ module Vagrant # Add the plugin to the state file @global_file.add_plugin( - result.name, version: opts[:version], require: opts[:require]) + result.name, + version: opts[:version], + require: opts[:require], + sources: opts[:sources], + ) result rescue ::Bundler::GemNotFound raise Errors::PluginGemNotFound, name: name + rescue ::Bundler::BundlerError => e + raise Errors::BundlerError, message: e.to_s end # Uninstalls the plugin with the given name. @@ -59,11 +66,15 @@ module Vagrant # Clean the environment, removing any old plugins Vagrant::Bundler.instance.clean(installed_plugins) + rescue ::Bundler::BundlerError => e + raise Errors::BundlerError, message: e.to_s end # Updates all or a specific set of plugins. def update_plugins(specific) Vagrant::Bundler.instance.update(installed_plugins, specific) + rescue ::Bundler::BundlerError => e + raise Errors::BundlerError, message: e.to_s end # This returns the list of plugins that should be enabled. diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index d8d7ecb0d..b9161672f 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -33,6 +33,7 @@ module Vagrant "vagrant_version" => Vagrant::VERSION, "gem_version" => opts[:version] || "", "require" => opts[:require] || "", + "sources" => opts[:sources] || [], } save! diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index c9e6e967a..a6295ad6b 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -24,6 +24,7 @@ module VagrantPlugins def call(env) entrypoint = env[:plugin_entry_point] plugin_name = env[:plugin_name] + sources = env[:plugin_sources] version = env[:plugin_version] # Determine the plugin name we'll look for in the installed set @@ -52,7 +53,10 @@ module VagrantPlugins manager = Vagrant::Plugin::Manager.instance plugin_spec = manager.install_plugin( - plugin_name, version: version, require: entrypoint) + plugin_name, + version: version, + require: entrypoint, + sources: sources,) # Record it so we can uninstall if something goes wrong @installed_plugin_name = plugin_spec.name diff --git a/templates/locales/en.yml b/templates/locales/en.yml index b0c3a4b82..384d1220d 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -254,6 +254,13 @@ en: The box '%{name}' is still stored on disk in the Vagrant 1.0.x format. This box must be upgraded in order to work properly with this version of Vagrant. + bundler_error: |- + Bundler, the underlying system Vagrant uses to install plugins, + reported an error. The error is shown below. These errors are usually + caused by misconfigured plugin installations or transient network + issues. The error from Bundler is: + + %{message} cfengine_bootstrap_failed: |- Failed to bootstrap CFEngine. Please see the output above to see what went wrong and address the issue. diff --git a/test/unit/plugins/commands/plugin/action/install_gem_test.rb b/test/unit/plugins/commands/plugin/action/install_gem_test.rb index 918c63cdc..5b046b434 100644 --- a/test/unit/plugins/commands/plugin/action/install_gem_test.rb +++ b/test/unit/plugins/commands/plugin/action/install_gem_test.rb @@ -18,7 +18,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should install the plugin" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: nil, require: nil).once.and_return(spec) + "foo", version: nil, require: nil, sources: nil).once.and_return(spec) app.should_receive(:call).with(env).once @@ -29,7 +29,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should specify the version if given" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: "bar", require: nil).once.and_return(spec) + "foo", version: "bar", require: nil, sources: nil).once.and_return(spec) app.should_receive(:call).with(env).once @@ -41,7 +41,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should specify the entrypoint if given" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: "bar", require: "baz").once.and_return(spec) + "foo", version: "bar", require: "baz", sources: nil).once.and_return(spec) app.should_receive(:call).with(env).once @@ -50,6 +50,18 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do env[:plugin_version] = "bar" subject.call(env) end + + it "should specify the sources if given" do + spec = Gem::Specification.new + manager.should_receive(:install_plugin).with( + "foo", version: nil, require: nil, sources: ["foo"]).once.and_return(spec) + + app.should_receive(:call).with(env).once + + env[:plugin_name] = "foo" + env[:plugin_sources] = ["foo"] + subject.call(env) + end end describe "#recover" do diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb index c32c0ce90..3d6e728b4 100644 --- a/test/unit/vagrant/plugin/manager_test.rb +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -16,12 +16,83 @@ describe Vagrant::Plugin::Manager do Pathname.new(p) end + let(:bundler) { double("bundler") } + after do path.unlink if path.file? end + before do + Vagrant::Bundler.stub(instance: bundler) + end + subject { described_class.new(path) } + describe "#install_plugin" do + it "installs the plugin and adds it to the state file" do + specs = Array.new(5) { Gem::Specification.new } + specs[3].name = "foo" + bundler.should_receive(:install).once.with do |plugins| + expect(plugins).to have_key("foo") + end.and_return(specs) + + result = subject.install_plugin("foo") + + # It should return the spec of the installed plugin + expect(result).to eql(specs[3]) + + # It should've added the plugin to the state + expect(subject.installed_plugins).to have_key("foo") + end + + it "masks GemNotFound with our error" do + bundler.should_receive(:install).and_raise(Bundler::GemNotFound) + + expect { subject.install_plugin("foo") }. + to raise_error(Vagrant::Errors::PluginGemNotFound) + end + + it "masks bundler errors with our own error" do + bundler.should_receive(:install).and_raise(Bundler::InstallError) + + expect { subject.install_plugin("foo") }. + to raise_error(Vagrant::Errors::BundlerError) + end + end + + describe "#uninstall_plugin" do + it "removes the plugin from the state" do + sf = Vagrant::Plugin::StateFile.new(path) + sf.add_plugin("foo") + + # Sanity + expect(subject.installed_plugins).to have_key("foo") + + # Test + bundler.should_receive(:clean).once.with({}) + + # Remove it + subject.uninstall_plugin("foo") + expect(subject.installed_plugins).to_not have_key("foo") + end + + it "masks bundler errors with our own error" do + bundler.should_receive(:clean).and_raise(Bundler::InstallError) + + expect { subject.uninstall_plugin("foo") }. + to raise_error(Vagrant::Errors::BundlerError) + end + end + + describe "#update_plugins" do + it "masks bundler errors with our own error" do + bundler.should_receive(:update).and_raise(Bundler::InstallError) + + expect { subject.update_plugins([]) }. + to raise_error(Vagrant::Errors::BundlerError) + end + end + context "without state" do describe "#installed_plugins" do it "is empty initially" do @@ -30,7 +101,6 @@ describe Vagrant::Plugin::Manager do end end - context "with state" do before do sf = Vagrant::Plugin::StateFile.new(path) diff --git a/test/unit/vagrant/plugin/state_file_test.rb b/test/unit/vagrant/plugin/state_file_test.rb index 993d2c415..6b37198ef 100644 --- a/test/unit/vagrant/plugin/state_file_test.rb +++ b/test/unit/vagrant/plugin/state_file_test.rb @@ -34,6 +34,7 @@ describe Vagrant::Plugin::StateFile do "vagrant_version" => Vagrant::VERSION, "gem_version" => "", "require" => "", + "sources" => [], }) end