diff --git a/lib/vagrant/box_metadata.rb b/lib/vagrant/box_metadata.rb index 0366246f1..20562452a 100644 --- a/lib/vagrant/box_metadata.rb +++ b/lib/vagrant/box_metadata.rb @@ -27,6 +27,7 @@ module Vagrant error: e.to_s end + @raw ||= {} @name = @raw["name"] @description = @raw["description"] @version_map = (@raw["versions"] || []).map do |v| diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index f336cafd3..654621337 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -172,6 +172,14 @@ module Vagrant error_key(:box_metadata_malformed) end + class BoxNotFound < VagrantError + error_key(:box_not_found) + end + + class BoxNotFoundWithProvider < VagrantError + error_key(:box_not_found_with_provider) + end + class BoxOutdatedNoBox < VagrantError error_key(:box_outdated_no_box) end @@ -200,6 +208,10 @@ module Vagrant error_key(:untar_failure, "vagrant.actions.box.unpackage") end + class BoxUpdateMultiProvider < VagrantError + error_key(:box_update_multi_provider) + end + class BoxUpdateNoMetadata < VagrantError error_key(:box_update_no_metadata) end diff --git a/plugins/commands/box/command/update.rb b/plugins/commands/box/command/update.rb index 6cfb85458..5a204647c 100644 --- a/plugins/commands/box/command/update.rb +++ b/plugins/commands/box/command/update.rb @@ -23,11 +23,62 @@ module VagrantPlugins o.on("--box VALUE", String, "Update a specific box") do |b| options[:box] = b end + + o.on("--provider VALUE", String, "Update box with specific provider.") do |p| + options[:provider] = p.to_sym + end end argv = parse_options(opts) return if !argv + if options[:box] + update_specific(options[:box], options[:provider]) + else + update_vms(argv) + end + + 0 + end + + def update_specific(name, provider) + boxes = {} + @env.boxes.all.each do |n, v, p| + boxes[n] ||= {} + boxes[n][p] ||= [] + boxes[n][p] << v + end + + if !boxes[name] + raise Vagrant::Errors::BoxNotFound, name: name.to_s + end + + if !provider + if boxes[name].length > 1 + raise Vagrant::Errors::BoxUpdateMultiProvider, + name: name.to_s, + providers: boxes[name].keys.map(&:to_s).sort.join(", ") + end + + provider = boxes[name].keys.first + elsif !boxes[name][provider] + raise Vagrant::Errors::BoxNotFoundWithProvider, + name: name.to_s, + provider: provider.to_s, + providers: boxes[name].keys.map(&:to_s).sort.join(", ") + end + + to_update = [ + [name, provider, boxes[name][provider].last], + ] + + to_update.each do |n, p, v| + box = @env.boxes.find(n, p, v) + box_update(box, "> #{v}", @env.ui) + end + end + + def update_vms(argv) with_target_vms(argv) do |machine| if !machine.box machine.ui.output(I18n.t( @@ -38,33 +89,36 @@ module VagrantPlugins box = machine.box version = machine.config.vm.box_version - - machine.ui.output(I18n.t("vagrant.box_update_checking", name: box.name)) - machine.ui.detail("Version constraints: #{version}") - machine.ui.detail("Provider: #{box.provider}") - - update = box.has_update?(version) - if !update - machine.ui.success(I18n.t( - "vagrant.box_up_to_date_single", - name: box.name, version: box.version)) - next - end - - machine.ui.output(I18n.t( - "vagrant.box_updating", - name: update[0].name, - provider: update[2].name, - old: box.version, - new: update[1].version)) - @env.action_runner.run(Vagrant::Action.action_box_add, { - box_url: box.metadata_url, - box_provider: update[2].name, - box_version: update[1].version, - ui: machine.ui, - }) + box_update(box, version, machine.ui) end end + + def box_update(box, version, ui) + ui.output(I18n.t("vagrant.box_update_checking", name: box.name)) + ui.detail("Version constraints: #{version}") + ui.detail("Provider: #{box.provider}") + + update = box.has_update?(version) + if !update + ui.success(I18n.t( + "vagrant.box_up_to_date_single", + name: box.name, version: box.version)) + return + end + + ui.output(I18n.t( + "vagrant.box_updating", + name: update[0].name, + provider: update[2].name, + old: box.version, + new: update[1].version)) + @env.action_runner.run(Vagrant::Action.action_box_add, { + box_url: box.metadata_url, + box_provider: update[2].name, + box_version: update[1].version, + ui: ui, + }) + end end end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index c48c3ec07..0981aaaa7 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -378,6 +378,16 @@ en: that this issue can be fixed. %{error} + box_not_found: |- + The box '%{name}' does not exist. Please double check and + try again. You can see the boxes that are installed with + `vagrant box list`. + box_not_found_with_provider: |- + The box '%{name}' isn't installed for the provider '%{provider}'. + Please double-check and try again. The installed providers for + the box are shown below: + + %{providers} box_outdated_no_box: |- The box '%{name}' isn't downloaded or added yet, so we can't check if it is outdated. Run a `vagrant up` or add the box @@ -409,6 +419,12 @@ en: the provider specified. Please double-check and try again. The providers for this are: %{providers} + box_update_multi_provider: |- + You requested to update the box '%{name}'. This box has + multiple providers. You must explicitly select a single + provider to remove with `--provider`. + + Available providers: %{providers} box_update_no_metadata: |- The box '%{name}' is not a versioned box. The box was added directly instead of from a box catalog. Vagrant can only diff --git a/test/unit/plugins/commands/box/command/update_test.rb b/test/unit/plugins/commands/box/command/update_test.rb index 3f2c5cd46..8571d6ba3 100644 --- a/test/unit/plugins/commands/box/command/update_test.rb +++ b/test/unit/plugins/commands/box/command/update_test.rb @@ -1,3 +1,6 @@ +require "pathname" +require "tmpdir" + require File.expand_path("../../../../../base", __FILE__) require Vagrant.source_root.join("plugins/commands/box/command/update") @@ -23,6 +26,126 @@ describe VagrantPlugins::CommandBox::Command::Update do end describe "execute" do + context "updating specific box" do + let(:argv) { ["--box", "foo"] } + + let(:metadata_url) { Pathname.new(Dir.mktmpdir).join("metadata.json") } + + before do + metadata_url.open("w") do |f| + f.write("") + end + + test_iso_env.box3( + "foo", "1.0", :virtualbox, metadata_url: metadata_url.to_s) + end + + it "doesn't update if they're up to date" do + action_runner.should_receive(:run).never + + subject.execute + end + + it "does update if there is an update" do + metadata_url.open("w") do |f| + f.write(<<-RAW) + { + "name": "foo", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.1", + "providers": [ + { + "name": "virtualbox", + "url": "bar" + } + ] + } + ] + } + RAW + end + + action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_url]).to eq(metadata_url.to_s) + expect(opts[:box_provider]).to eq("virtualbox") + expect(opts[:box_version]).to eq("1.1") + true + end + + subject.execute + end + + it "raises an error if there are multiple providers" do + test_iso_env.box3("foo", "1.0", :vmware) + + action_runner.should_receive(:run).never + + expect { subject.execute }. + to raise_error(Vagrant::Errors::BoxUpdateMultiProvider) + end + + context "with multiple providers and specifying the provider" do + let(:argv) { ["--box", "foo", "--provider", "vmware"] } + + it "updates the proper box" do + metadata_url.open("w") do |f| + f.write(<<-RAW) + { + "name": "foo", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.1", + "providers": [ + { + "name": "vmware", + "url": "bar" + } + ] + } + ] + } + RAW + end + + test_iso_env.box3("foo", "1.0", :vmware) + + action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_url]).to eq(metadata_url.to_s) + expect(opts[:box_provider]).to eq("vmware") + expect(opts[:box_version]).to eq("1.1") + true + end + + subject.execute + end + + it "raises an error if that provider doesn't exist" do + action_runner.should_receive(:run).never + + expect { subject.execute }. + to raise_error(Vagrant::Errors::BoxNotFoundWithProvider) + end + end + + context "with a box that doesn't exist" do + let(:argv) { ["--box", "nope"] } + + it "raises an exception" do + action_runner.should_receive(:run).never + + expect { subject.execute }. + to raise_error(Vagrant::Errors::BoxNotFound) + end + end + end + context "updating environment machines" do before do subject.stub(:with_target_vms) { |&block| block.call machine } diff --git a/test/unit/support/isolated_environment.rb b/test/unit/support/isolated_environment.rb index 0ea9bb9a8..94fb907bd 100644 --- a/test/unit/support/isolated_environment.rb +++ b/test/unit/support/isolated_environment.rb @@ -117,6 +117,13 @@ module Unit end end + # Create the metadata URL + if opts[:metadata_url] + boxes_dir.join(name, "metadata_url").open("w") do |f| + f.write(opts[:metadata_url]) + end + end + box_dir end