From a03bc763f9afe30421ab8fa40e9762bb3d6ce69f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 09:36:22 -0800 Subject: [PATCH 01/90] core: tests around the environment setup_version --- lib/vagrant/environment.rb | 22 +++- lib/vagrant/errors.rb | 4 + templates/locales/en.yml | 4 + test/unit/vagrant/environment_test.rb | 140 ++++++++++++++++---------- 4 files changed, 114 insertions(+), 56 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 29e805659..2fc4143b1 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -14,6 +14,12 @@ module Vagrant # defined as basically a folder with a "Vagrantfile." This class allows # access to the VMs, CLI, etc. all in the scope of this environment. class Environment + # This is the current version that this version of Vagrant is + # compatible with in the home directory. + # + # @return [String] + CURRENT_SETUP_VERSION = "1.1" + DEFAULT_LOCAL_DATA = ".vagrant" # The `cwd` that this environment represents @@ -634,12 +640,24 @@ module Vagrant # we're using. version_file = @home_path.join("setup_version") if !version_file.file? - @logger.debug("Setting up the version file.") + @logger.debug( + "Creating home directory version file: #{CURRENT_SETUP_VERSION}") version_file.open("w") do |f| - f.write("1.1") + f.write(CURRENT_SETUP_VERSION) end end + # Determine if we need to update the directory structure + version = version_file.read + case version + when CURRENT_SETUP_VERSION + # We're already good, at the latest version. + else + raise Errors::HomeDirectoryUnknownVersion, + path: @home_path.to_s, + version: version + end + # Create the rgloader/loader file so we can use encoded files. loader_file = @home_path.join("rgloader", "loader.rb") if !loader_file.file? diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 0680de435..fe563694b 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -264,6 +264,10 @@ module Vagrant error_key(:home_dir_not_accessible) end + class HomeDirectoryUnknownVersion < VagrantError + error_key(:home_dir_unknown_version) + end + class ForwardPortAdapterNotFound < VagrantError error_key(:forward_port_adapter_not_found) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 5c8fef2c6..9f44e0da3 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -437,6 +437,10 @@ en: directory that Vagrant uses must be both readable and writable. You specified: %{home_path} + home_dir_unknown_version: |- + The Vagrant app data directory (%{path}) is in a + structure Vagrant doesn't understand. This is a rare exception. + Please report an issue or ask the mailing list for help. host_explicit_not_detected: |- The host implementation explicitly specified in your Vagrantfile ("%{value}") could not be found. Please verify that the plugin is diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index d686c09ce..a5e670542 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -25,6 +25,92 @@ describe Vagrant::Environment do let(:instance) { env.create_vagrant_env } subject { instance } + describe "#home_path" do + it "is set to the home path given" do + Dir.mktmpdir do |dir| + instance = described_class.new(:home_path => dir) + instance.home_path.should == Pathname.new(dir) + end + end + + it "is set to the environmental variable VAGRANT_HOME" do + Dir.mktmpdir do |dir| + instance = with_temp_env("VAGRANT_HOME" => dir) do + described_class.new + end + + instance.home_path.should == Pathname.new(dir) + end + end + + it "throws an exception if inaccessible" do + expect { + described_class.new(:home_path => "/") + }.to raise_error(Vagrant::Errors::HomeDirectoryNotAccessible) + end + + context "default home path" do + it "is set to '~/.vagrant.d' by default" do + expected = Vagrant::Util::Platform.fs_real_path("~/.vagrant.d") + described_class.new.home_path.should == expected + end + + it "is set to '~/.vagrant.d' if on Windows but no USERPROFILE" do + Vagrant::Util::Platform.stub(:windows? => true) + + expected = Vagrant::Util::Platform.fs_real_path("~/.vagrant.d") + + with_temp_env("USERPROFILE" => nil) do + described_class.new.home_path.should == expected + end + end + + it "is set to '%USERPROFILE%/.vagrant.d' if on Windows and USERPROFILE is set" do + Vagrant::Util::Platform.stub(:windows? => true) + + Dir.mktmpdir do |dir| + expected = Vagrant::Util::Platform.fs_real_path("#{dir}/.vagrant.d") + + with_temp_env("USERPROFILE" => dir) do + described_class.new.home_path.should == expected + end + end + end + end + + context "setup version file" do + it "creates a setup version flie" do + path = subject.home_path.join("setup_version") + expect(path).to be_file + expect(path.read).to eq(Vagrant::Environment::CURRENT_SETUP_VERSION) + end + + it "is okay if it has the current version" do + Dir.mktmpdir do |dir| + Pathname.new(dir).join("setup_version").open("w") do |f| + f.write(Vagrant::Environment::CURRENT_SETUP_VERSION) + end + + instance = described_class.new(home_path: dir) + path = instance.home_path.join("setup_version") + expect(path).to be_file + expect(path.read).to eq(Vagrant::Environment::CURRENT_SETUP_VERSION) + end + end + + it "raises an exception if there is an unknown home directory version" do + Dir.mktmpdir do |dir| + Pathname.new(dir).join("setup_version").open("w") do |f| + f.write("0.7") + end + + expect { described_class.new(home_path: dir) }. + to raise_error(Vagrant::Errors::HomeDirectoryUnknownVersion) + end + end + end + end + describe "#host" do let(:plugin_hosts) { {} } let(:plugin_host_caps) { {} } @@ -211,60 +297,6 @@ describe Vagrant::Environment do end end - describe "home path" do - it "is set to the home path given" do - Dir.mktmpdir do |dir| - instance = described_class.new(:home_path => dir) - instance.home_path.should == Pathname.new(dir) - end - end - - it "is set to the environmental variable VAGRANT_HOME" do - Dir.mktmpdir do |dir| - instance = with_temp_env("VAGRANT_HOME" => dir) do - described_class.new - end - - instance.home_path.should == Pathname.new(dir) - end - end - - context "default home path" do - it "is set to '~/.vagrant.d' by default" do - expected = Vagrant::Util::Platform.fs_real_path("~/.vagrant.d") - described_class.new.home_path.should == expected - end - - it "is set to '~/.vagrant.d' if on Windows but no USERPROFILE" do - Vagrant::Util::Platform.stub(:windows? => true) - - expected = Vagrant::Util::Platform.fs_real_path("~/.vagrant.d") - - with_temp_env("USERPROFILE" => nil) do - described_class.new.home_path.should == expected - end - end - - it "is set to '%USERPROFILE%/.vagrant.d' if on Windows and USERPROFILE is set" do - Vagrant::Util::Platform.stub(:windows? => true) - - Dir.mktmpdir do |dir| - expected = Vagrant::Util::Platform.fs_real_path("#{dir}/.vagrant.d") - - with_temp_env("USERPROFILE" => dir) do - described_class.new.home_path.should == expected - end - end - end - end - - it "throws an exception if inaccessible" do - expect { - described_class.new(:home_path => "/") - }.to raise_error(Vagrant::Errors::HomeDirectoryNotAccessible) - end - end - describe "local data path" do it "is set to the proper default" do default = instance.root_path.join(described_class::DEFAULT_LOCAL_DATA) From 5253da79cd8f109faffdee8df3122e34fb98077f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 11:10:20 -0800 Subject: [PATCH 02/90] core: use a Monitor in BoxCollection --- lib/vagrant/box_collection.rb | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index 9d26f5af5..f2f359c83 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -1,5 +1,5 @@ require "digest/sha1" -require "thread" +require "monitor" require "tmpdir" require "log4r" @@ -44,7 +44,7 @@ module Vagrant options ||= {} @directory = directory - @lock = Mutex.new + @lock = Monitor.new @temp_root = options[:temp_dir_root] @logger = Log4r::Logger.new("vagrant::box_collection") end @@ -361,17 +361,7 @@ module Vagrant # This locks the region given by the block with a lock on this # collection. def with_collection_lock - lock = @lock - - begin - lock.synchronize {} - rescue ThreadError - # If we already hold the lock, just create a new lock so - # we definitely don't block and don't get an error. - lock = Mutex.new - end - - lock.synchronize do + @lock.synchronize do return yield end end From b52d33e0af06437465eff2a714aca77753e79912 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 11:10:59 -0800 Subject: [PATCH 03/90] core: upgrade to v1.5 box dir format if it can --- lib/vagrant/box_collection.rb | 32 +++++++++ lib/vagrant/environment.rb | 44 ++++++++---- test/unit/vagrant/box_collection_test.rb | 46 +++++++++++++ test/unit/vagrant/environment_test.rb | 88 ++++++++++++++++-------- 4 files changed, 166 insertions(+), 44 deletions(-) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index f2f359c83..1ddc5d10a 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -304,6 +304,38 @@ module Vagrant return true end + # This upgrades a v1.1 - v1.4 box directory structure up to a v1.5 + # directory structure. This will raise exceptions if it fails in any + # way. + def upgrade_v1_1_v1_5 + with_collection_lock do + temp_dir = Pathname.new(Dir.mktmpdir(TEMP_PREFIX, @temp_root)) + + @directory.children(true).each do |boxdir| + # Ignore all non-directories because they can't be boxes + next if !boxdir.directory? + + box_name = boxdir.basename.to_s + + # If it is a v1 box, then we need to upgrade it first + upgrade(box_name) if v1_box?(boxdir) + + # Create the directory for this box + new_box_dir = temp_dir.join(box_name, "0") + new_box_dir.mkpath + + # Go through each provider and move it + boxdir.children(true).each do |providerdir| + FileUtils.cp_r(providerdir, new_box_dir.join(providerdir.basename)) + end + end + + # Move the folder into place + @directory.rmtree + FileUtils.mv(temp_dir.to_s, @directory.to_s) + end + end + protected # This checks if the given directory represents a V1 box on the diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 2fc4143b1..67a735f52 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -18,7 +18,7 @@ module Vagrant # compatible with in the home directory. # # @return [String] - CURRENT_SETUP_VERSION = "1.1" + CURRENT_SETUP_VERSION = "1.5" DEFAULT_LOCAL_DATA = ".vagrant" @@ -636,9 +636,28 @@ module Vagrant raise Errors::HomeDirectoryNotAccessible, home_path: @home_path.to_s end - # Create the version file to mark the version of the home directory - # we're using. + # Create the version file that we use to track the structure of + # the home directory. If we have an old version, we need to explicitly + # upgrade it. Otherwise, we just mark that its the current version. version_file = @home_path.join("setup_version") + if version_file.file? + version = version_file.read + case version + when CURRENT_SETUP_VERSION + # We're already good, at the latest version. + when "1.1" + # We need to update our directory structure + upgrade_home_path_v1_1 + + # Delete the version file so we put our latest version in + version_file.delete + else + raise Errors::HomeDirectoryUnknownVersion, + path: @home_path.to_s, + version: version + end + end + if !version_file.file? @logger.debug( "Creating home directory version file: #{CURRENT_SETUP_VERSION}") @@ -647,17 +666,6 @@ module Vagrant end end - # Determine if we need to update the directory structure - version = version_file.read - case version - when CURRENT_SETUP_VERSION - # We're already good, at the latest version. - else - raise Errors::HomeDirectoryUnknownVersion, - path: @home_path.to_s, - version: version - end - # Create the rgloader/loader file so we can use encoded files. loader_file = @home_path.join("rgloader", "loader.rb") if !loader_file.file? @@ -741,6 +749,14 @@ module Vagrant nil end + # This upgrades a home directory that was in the v1.1 format to the + # v1.5 format. It will raise exceptions if anything fails. + def upgrade_home_path_v1_1 + collection = BoxCollection.new( + @home_path.join("boxes"), temp_dir_root: tmp_path) + collection.upgrade_v1_1_v1_5 + end + # This upgrades a Vagrant 1.0.x "dotfile" to the new V2 format. # # This is a destructive process. Once the upgrade is complete, the diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index 9b098f39e..021ccf1e6 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -10,6 +10,8 @@ describe Vagrant::BoxCollection do let(:environment) { isolated_environment } let(:instance) { described_class.new(environment.boxes_dir) } + subject { instance } + it "should tell us the directory it is using" do instance.directory.should == environment.boxes_dir end @@ -238,4 +240,48 @@ describe Vagrant::BoxCollection do instance.upgrade("foo").should be end end + + describe "#upgrade_v1_1_v1_5" do + let(:boxes_dir) { environment.boxes_dir } + + before do + # Create all the various box directories + @foo_path = boxes_dir.join("foo", "virtualbox") + @vbox_path = boxes_dir.join("precise64", "virtualbox") + @vmware_path = boxes_dir.join("precise64", "vmware") + @v1_path = boxes_dir.join("v1box") + + [@foo_path, @vbox_path, @vmware_path].each do |path| + path.mkpath + path.join("name").open("w") do |f| + f.write(path.to_s) + end + end + + @v1_path.mkpath + @v1_path.join("box.ovf").open("w") { |f| f.write("yep!") } + @v1_path.join("name").open("w") { |f| f.write("v1box") } + end + + it "upgrades the boxes" do + subject.upgrade_v1_1_v1_5 + + # The old paths should not exist anymore + expect(@foo_path).to_not exist + expect(@vbox_path).to_not exist + expect(@vmware_path).to_not exist + expect(@v1_path.join("box.ovf")).to_not exist + + # New paths should exist + foo_path = boxes_dir.join("foo", "0", "virtualbox") + vbox_path = boxes_dir.join("precise64", "0", "virtualbox") + vmware_path = boxes_dir.join("precise64", "0", "vmware") + v1_path = boxes_dir.join("v1box", "0", "virtualbox") + + expect(foo_path).to exist + expect(vbox_path).to exist + expect(vmware_path).to exist + expect(v1_path).to exist + end + end end diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index a5e670542..7448d5170 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -49,36 +49,7 @@ describe Vagrant::Environment do }.to raise_error(Vagrant::Errors::HomeDirectoryNotAccessible) end - context "default home path" do - it "is set to '~/.vagrant.d' by default" do - expected = Vagrant::Util::Platform.fs_real_path("~/.vagrant.d") - described_class.new.home_path.should == expected - end - - it "is set to '~/.vagrant.d' if on Windows but no USERPROFILE" do - Vagrant::Util::Platform.stub(:windows? => true) - - expected = Vagrant::Util::Platform.fs_real_path("~/.vagrant.d") - - with_temp_env("USERPROFILE" => nil) do - described_class.new.home_path.should == expected - end - end - - it "is set to '%USERPROFILE%/.vagrant.d' if on Windows and USERPROFILE is set" do - Vagrant::Util::Platform.stub(:windows? => true) - - Dir.mktmpdir do |dir| - expected = Vagrant::Util::Platform.fs_real_path("#{dir}/.vagrant.d") - - with_temp_env("USERPROFILE" => dir) do - described_class.new.home_path.should == expected - end - end - end - end - - context "setup version file" do + context "with setup version file" do it "creates a setup version flie" do path = subject.home_path.join("setup_version") expect(path).to be_file @@ -109,6 +80,63 @@ describe Vagrant::Environment do end end end + + context "upgrading a v1.1 directory structure" do + let(:env) { isolated_environment } + + before do + env.homedir.join("setup_version").open("w") do |f| + f.write("1.1") + end + end + + it "replaces the setup version with the new version" do + expect(subject.home_path.join("setup_version").read). + to eq(Vagrant::Environment::CURRENT_SETUP_VERSION) + end + + it "moves the boxes into the new directory structure" do + # Kind of hacky but avoids two instantiations of BoxCollection + Vagrant::Environment.any_instance.stub(boxes: double("boxes")) + + collection = double("collection") + Vagrant::BoxCollection.should_receive(:new).with( + env.homedir.join("boxes"), anything).and_return(collection) + collection.should_receive(:upgrade_v1_1_v1_5).once + subject + end +=begin + # Create all the old box directories + boxdir = env.homedir.join("boxes") + boxdir.mkpath + + foo_path = boxdir.join("foo", "virtualbox") + vbox_path = boxdir.join("precise64", "virtualbox") + vmware_path = boxdir.join("precise64", "vmware") + v1_path = boxdir.join("v1box") + + [foo_path, vbox_path, vmware_path].each do |path| + path.mkpath + path.join("name").open("w") do |f| + f.write(path.to_s) + end + end + + v1_path.mkpath + v1_path.join("name").open("w") { |f| f.write("v1box") } + + # Upgrade! + subject + + expect(boxdir).to be_directory + expect(boxdir.children(false).map(&:to_s).sort).to eq( + ["foo", "precise64", "v1box"]) + expect(foo_path).to_not exist + expect(vbox_path).to_not exist + expect(vmware_path).to_not exist + expect(v1_path).to_not exist +=end + end end describe "#host" do From 6cddb92407e920730da913a03a4409b820131268 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 11:25:36 -0800 Subject: [PATCH 04/90] core: error if newer home dir version is detected --- lib/vagrant/environment.rb | 4 +++ lib/vagrant/errors.rb | 4 +++ templates/locales/en.yml | 5 ++++ test/unit/vagrant/environment_test.rb | 42 +++++++-------------------- 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 67a735f52..db76f1884 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -642,6 +642,10 @@ module Vagrant version_file = @home_path.join("setup_version") if version_file.file? version = version_file.read + if version > CURRENT_SETUP_VERSION + raise Errors::HomeDirectoryLaterVersion + end + case version when CURRENT_SETUP_VERSION # We're already good, at the latest version. diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index fe563694b..5927c5bfd 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -260,6 +260,10 @@ module Vagrant error_key(:environment_locked) end + class HomeDirectoryLaterVersion < VagrantError + error_key(:home_dir_later_version) + end + class HomeDirectoryNotAccessible < VagrantError error_key(:home_dir_not_accessible) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 9f44e0da3..2739a8b3f 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -432,6 +432,11 @@ en: as mounting shared folders and configuring networks. Please add the ability to detect this guest operating system to Vagrant by creating a plugin or reporting a bug. + home_dir_later_version: |- + It appears that a newer version of Vagrant was run on this machine + at some point. The current version of Vagrant is unable to read + the configuration structure of this newer version. Please upgrade to + the latest version of Vagrant. home_dir_not_accessible: |- The home directory you specified is not accessible. The home directory that Vagrant uses must be both readable and writable. diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index 7448d5170..e7499aa7a 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -69,6 +69,17 @@ describe Vagrant::Environment do end end + it "raises an exception if the version is newer than ours" do + Dir.mktmpdir do |dir| + Pathname.new(dir).join("setup_version").open("w") do |f| + f.write("100.5") + end + + expect { described_class.new(home_path: dir) }. + to raise_error(Vagrant::Errors::HomeDirectoryLaterVersion) + end + end + it "raises an exception if there is an unknown home directory version" do Dir.mktmpdir do |dir| Pathname.new(dir).join("setup_version").open("w") do |f| @@ -105,37 +116,6 @@ describe Vagrant::Environment do collection.should_receive(:upgrade_v1_1_v1_5).once subject end -=begin - # Create all the old box directories - boxdir = env.homedir.join("boxes") - boxdir.mkpath - - foo_path = boxdir.join("foo", "virtualbox") - vbox_path = boxdir.join("precise64", "virtualbox") - vmware_path = boxdir.join("precise64", "vmware") - v1_path = boxdir.join("v1box") - - [foo_path, vbox_path, vmware_path].each do |path| - path.mkpath - path.join("name").open("w") do |f| - f.write(path.to_s) - end - end - - v1_path.mkpath - v1_path.join("name").open("w") { |f| f.write("v1box") } - - # Upgrade! - subject - - expect(boxdir).to be_directory - expect(boxdir.children(false).map(&:to_s).sort).to eq( - ["foo", "precise64", "v1box"]) - expect(foo_path).to_not exist - expect(vbox_path).to_not exist - expect(vmware_path).to_not exist - expect(v1_path).to_not exist -=end end end From 77b5fa94b7cd857d7f24172b9e8c64da401a0b44 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 11:42:39 -0800 Subject: [PATCH 05/90] core: BoxCollection#all returns versions --- lib/vagrant/box_collection.rb | 37 +++---- test/unit/support/isolated_environment.rb | 23 ++++ test/unit/vagrant/box_collection_test.rb | 128 ++++++++++------------ 3 files changed, 98 insertions(+), 90 deletions(-) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index 1ddc5d10a..6ef8476a8 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -188,9 +188,8 @@ module Vagrant # This returns an array of all the boxes on the system, given by # their name and their provider. # - # @return [Array] Array of `[name, provider]` pairs of the boxes - # installed on this system. An optional third element in the array - # may specify `:v1` if the box is a version 1 box. + # @return [Array] Array of `[name, version, provider]` of the boxes + # installed on this system. def all results = [] @@ -203,25 +202,21 @@ module Vagrant box_name = child.basename.to_s - # If this is a V1 box, we still return that name, but specify - # that the box is a V1 box. - if v1_box?(child) - @logger.debug("V1 box found: #{box_name}") - results << [box_name, :virtualbox, :v1] - next - end - - # Otherwise, traverse the subdirectories and see what providers + # Otherwise, traverse the subdirectories and see what versions # we have. - child.children(true).each do |provider| - # Verify this is a potentially valid box. If it looks - # correct enough then include it. - if provider.directory? && provider.join("metadata.json").file? - provider_name = provider.basename.to_s.to_sym - @logger.debug("Box: #{box_name} (#{provider_name})") - results << [box_name, provider_name] - else - @logger.debug("Invalid box, ignoring: #{provider}") + child.children(true).each do |versiondir| + version = versiondir.basename.to_s + + versiondir.children(true).each do |provider| + # Verify this is a potentially valid box. If it looks + # correct enough then include it. + if provider.directory? && provider.join("metadata.json").file? + provider_name = provider.basename.to_s.to_sym + @logger.debug("Box: #{box_name} (#{provider_name})") + results << [box_name, version, provider_name] + else + @logger.debug("Invalid box, ignoring: #{provider}") + end end end end diff --git a/test/unit/support/isolated_environment.rb b/test/unit/support/isolated_environment.rb index 4f8e7552b..f69268ce7 100644 --- a/test/unit/support/isolated_environment.rb +++ b/test/unit/support/isolated_environment.rb @@ -89,6 +89,29 @@ module Unit box_dir end + # Creates a fake box to exist in this environment according + # to the "gen-3" box format. + # + # @param [String] name + # @param [String] version + # @param [String] provider + # @return [Pathname] + def box3(name, version, provider, **opts) + # Create the directory for the box + box_dir = boxes_dir.join(name, version, provider.to_s) + box_dir.mkpath + + # Create the metadata.json for it + box_metadata_file = box_dir.join("metadata.json") + box_metadata_file.open("w") do |f| + f.write(JSON.generate({ + :provider => provider.to_s + })) + end + + box_dir + end + # This creates a "box" file that is a valid V1 box. # # @return [Pathname] Path to the newly created box. diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index 021ccf1e6..54cca1ac7 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -16,6 +16,65 @@ describe Vagrant::BoxCollection do instance.directory.should == environment.boxes_dir end + describe "#all" do + it "should return an empty array when no boxes are there" do + instance.all.should == [] + end + + it "should return the boxes and their providers" do + # Create some boxes + environment.box3("foo", "1.0", :virtualbox) + environment.box3("foo", "1.0", :vmware) + environment.box3("bar", "0", :ec2) + + # Verify some output + results = instance.all + results.length.should == 3 + results.include?(["foo", "1.0", :virtualbox]).should be + results.include?(["foo", "1.0", :vmware]).should be + results.include?(["bar", "0", :ec2]).should be + end + + it 'does not raise an exception when a file appears in the boxes dir' do + Tempfile.new('a_file', environment.boxes_dir) + expect { instance.all }.to_not raise_error + end + end + + describe "finding" do + it "should return nil if the box does not exist" do + instance.find("foo", :i_dont_exist).should be_nil + end + + it "should return a box if the box does exist" do + # Create the "box" + environment.box2("foo", :virtualbox) + + # Actual test + result = instance.find("foo", :virtualbox) + result.should_not be_nil + result.should be_kind_of(box_class) + result.name.should == "foo" + end + + it "should throw an exception if it is a v1 box" do + # Create a V1 box + environment.box1("foo") + + # Test! + expect { instance.find("foo", :virtualbox) }. + to raise_error(Vagrant::Errors::BoxUpgradeRequired) + end + + it "should return nil if there is a V1 box but we're looking for another provider" do + # Create a V1 box + environment.box1("foo") + + # Test + instance.find("foo", :another_provider).should be_nil + end + end + describe "adding" do it "should add a valid box to the system" do box_path = environment.box2_file(:virtualbox) @@ -141,75 +200,6 @@ describe Vagrant::BoxCollection do end end - describe "listing all" do - it "should return an empty array when no boxes are there" do - instance.all.should == [] - end - - it "should return the boxes and their providers" do - # Create some boxes - environment.box2("foo", :virtualbox) - environment.box2("foo", :vmware) - environment.box2("bar", :ec2) - - # Verify some output - results = instance.all - results.length.should == 3 - results.include?(["foo", :virtualbox]).should be - results.include?(["foo", :vmware]).should be - results.include?(["bar", :ec2]).should be - end - - it "should return V1 boxes as well" do - # Create some boxes, including a V1 box - environment.box1("bar") - environment.box2("foo", :vmware) - - # Verify some output - results = instance.all.sort - results.should == [["bar", :virtualbox, :v1], ["foo", :vmware]] - end - - it 'does not raise an exception when a file appears in the boxes dir' do - Tempfile.new('a_file', environment.boxes_dir) - expect { instance.all }.to_not raise_error - end - end - - describe "finding" do - it "should return nil if the box does not exist" do - instance.find("foo", :i_dont_exist).should be_nil - end - - it "should return a box if the box does exist" do - # Create the "box" - environment.box2("foo", :virtualbox) - - # Actual test - result = instance.find("foo", :virtualbox) - result.should_not be_nil - result.should be_kind_of(box_class) - result.name.should == "foo" - end - - it "should throw an exception if it is a v1 box" do - # Create a V1 box - environment.box1("foo") - - # Test! - expect { instance.find("foo", :virtualbox) }. - to raise_error(Vagrant::Errors::BoxUpgradeRequired) - end - - it "should return nil if there is a V1 box but we're looking for another provider" do - # Create a V1 box - environment.box1("foo") - - # Test - instance.find("foo", :another_provider).should be_nil - end - end - describe "upgrading" do it "should upgrade a V1 box to V2" do # Create a V1 box From 4c1fa7359d6450ac5fdee58abae8077295458b4d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 15:16:12 -0800 Subject: [PATCH 06/90] core: BoxCollection can find with version constraints --- lib/vagrant/box_collection.rb | 52 ++++++++++++------------ test/unit/vagrant/box_collection_test.rb | 48 +++++++++++++--------- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index 6ef8476a8..b85fe56d6 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -229,42 +229,40 @@ module Vagrant # # @param [String] name Name of the box (logical name). # @param [Array] providers Providers that the box implements. + # @param [String] version Version constraints to adhere to. Example: + # "~> 1.0" or "= 1.0, ~> 1.1" # @return [Box] The box found, or `nil` if not found. - def find(name, providers) - providers = [providers].flatten + def find(name, providers, version) + providers = Array(providers) + + # Build up the requirements we have + requirements = version.split(",").map do |v| + Gem::Requirement.new(v.strip) + end with_collection_lock do - providers.each do |provider| - # First look directly for the box we're asking for. - box_directory = @directory.join(name, provider.to_s, "metadata.json") - @logger.info("Searching for box: #{name} (#{provider}) in #{box_directory}") - if box_directory.file? - @logger.info("Box found: #{name} (#{provider})") - return Box.new(name, provider, box_directory.dirname) + box_directory = @directory.join(name) + if !box_directory.directory? + @logger.info("Box not found: #{name} (#{providers.join(", ")})") + return nil + end + + box_directory.children(true).each do |versiondir| + version = versiondir.basename.to_s + if !requirements.all? { |r| r.satisfied_by?(Gem::Version.new(version)) } + # Unsatisfied version requirements + next end - # If we're looking for a VirtualBox box, then we check if there is - # a V1 box. - if provider.to_sym == :virtualbox - # Check if a V1 version of this box exists, and if so, raise an - # exception notifying the caller that the box exists but needs - # to be upgraded. We don't do the upgrade here because it can be - # a fairly intensive activity and don't want to immediately degrade - # user performance on a find. - # - # To determine if it is a V1 box we just do a simple heuristic - # based approach. - @logger.info("Searching for V1 box: #{name}") - if v1_box?(@directory.join(name)) - @logger.warn("V1 box found: #{name}") - raise Errors::BoxUpgradeRequired, :name => name - end + providers.each do |provider| + provider_dir = versiondir.join(provider.to_s) + next if !provider_dir.directory? + @logger.info("Box found: #{name} (#{provider})") + return Box.new(name, provider, provider_dir) end end end - # Didn't find it, return nil - @logger.info("Box not found: #{name} (#{providers.join(", ")})") nil end diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index 54cca1ac7..0309753f0 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -41,37 +41,45 @@ describe Vagrant::BoxCollection do end end - describe "finding" do - it "should return nil if the box does not exist" do - instance.find("foo", :i_dont_exist).should be_nil + describe "#find" do + it "returns nil if the box does not exist" do + expect(subject.find("foo", :i_dont_exist, ">= 0")).to be_nil end - it "should return a box if the box does exist" do + it "returns a box if the box does exist" do # Create the "box" - environment.box2("foo", :virtualbox) + environment.box3("foo", "0", :virtualbox) # Actual test - result = instance.find("foo", :virtualbox) - result.should_not be_nil - result.should be_kind_of(box_class) - result.name.should == "foo" + result = subject.find("foo", :virtualbox, ">= 0") + expect(result).to_not be_nil + expect(result).to be_kind_of(box_class) + expect(result.name).to eq("foo") end - it "should throw an exception if it is a v1 box" do - # Create a V1 box - environment.box1("foo") + it "can satisfy complex constraints" do + # Create the "box" + environment.box3("foo", "0.1", :virtualbox) + environment.box3("foo", "1.0", :virtualbox) + environment.box3("foo", "2.1", :virtualbox) - # Test! - expect { instance.find("foo", :virtualbox) }. - to raise_error(Vagrant::Errors::BoxUpgradeRequired) + # Actual test + result = subject.find("foo", :virtualbox, ">= 0.9, < 1.5") + expect(result).to_not be_nil + expect(result).to be_kind_of(box_class) + expect(result.name).to eq("foo") + # TODO: test version end - it "should return nil if there is a V1 box but we're looking for another provider" do - # Create a V1 box - environment.box1("foo") + it "returns nil if a box's constraints can't be satisfied" do + # Create the "box" + environment.box3("foo", "0.1", :virtualbox) + environment.box3("foo", "1.0", :virtualbox) + environment.box3("foo", "2.1", :virtualbox) - # Test - instance.find("foo", :another_provider).should be_nil + # Actual test + result = subject.find("foo", :virtualbox, "> 1.0, < 1.5") + expect(result).to be_nil end end From 87a85488d1e1a32cd98243648d2373d52a2c76c6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 15:50:10 -0800 Subject: [PATCH 07/90] core: all box collection tests pass --- lib/vagrant/box_collection.rb | 110 ++++++++------------ templates/locales/en.yml | 3 +- test/unit/vagrant/box_collection_test.rb | 124 +++++++---------------- 3 files changed, 78 insertions(+), 159 deletions(-) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index b85fe56d6..e1262c7ba 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -64,45 +64,48 @@ module Vagrant # # @param [Pathname] path Path to the box file on disk. # @param [String] name Logical name for the box. - # @param [Symbol] provider The provider that the box should be for. This - # will be verified with the `metadata.json` file in the box and is + # @param [String] version The version of this box. + # @param [Array] providers The providers that this box can + # be a part of. This will be verified with the `metadata.json` and is # meant as a basic check. If this isn't given, then whatever provider # the box represents will be added. # @param [Boolean] force If true, any existing box with the same name # and provider will be replaced. - def add(path, name, formats=nil, force=false) - formats = [formats] if formats && !formats.is_a?(Array) + def add(path, name, version, **opts) + providers = opts[:providers] + providers = Array(providers) if providers provider = nil - with_collection_lock do - # A helper to check if a box exists. We store this in a variable - # since we call it multiple times. - check_box_exists = lambda do |box_formats| - box = find(name, box_formats) - next if !box + # A helper to check if a box exists. We store this in a variable + # since we call it multiple times. + check_box_exists = lambda do |box_formats| + box = find(name, box_formats, version) + next if !box - if !force - @logger.error("Box already exists, can't add: #{name} #{box_formats.join(", ")}") - raise Errors::BoxAlreadyExists, :name => name, :formats => box_formats.join(", ") - end - - # We're forcing, so just delete the old box - @logger.info( - "Box already exists, but forcing so removing: #{name} #{box_formats.join(", ")}") - box.destroy! + if !opts[:force] + @logger.error( + "Box already exists, can't add: #{name} v#{version} #{box_formats.join(", ")}") + raise Errors::BoxAlreadyExists, + name: name, + providers: box_formats.join(", "), + version: version end - log_provider = formats ? formats.join(", ") : "any provider" + # We're forcing, so just delete the old box + @logger.info( + "Box already exists, but forcing so removing: " + + "#{name} v#{version} #{box_formats.join(", ")}") + box.destroy! + end + + with_collection_lock do + log_provider = providers ? providers.join(", ") : "any provider" @logger.debug("Adding box: #{name} (#{log_provider}) from #{path}") # Verify the box doesn't exist early if we're given a provider. This # can potentially speed things up considerably since we don't need # to unpack any files. - check_box_exists.call(formats) if formats - - # Verify that a V1 box doesn't exist. If it does, then we signal - # to the user that we need an upgrade. - raise Errors::BoxUpgradeRequired, :name => name if v1_box?(@directory.join(name)) + check_box_exists.call(providers) if providers # Create a temporary directory since we're not sure at this point if # the box we're unpackaging already exists (if no provider was given) @@ -111,7 +114,10 @@ module Vagrant @logger.debug("Unpacking box into temporary directory: #{temp_dir}") result = Util::Subprocess.execute( "bsdtar", "-v", "-x", "-m", "-C", temp_dir.to_s, "-f", path.to_s) - raise Errors::BoxUnpackageFailure, :output => result.stderr.to_s if result.exit_code != 0 + if result.exit_code != 0 + raise Errors::BoxUnpackageFailure, + output: result.stderr.to_s + end # If we get a V1 box, we want to update it in place if v1_box?(temp_dir) @@ -131,16 +137,8 @@ module Vagrant # to the system or check that it matches what is given to us. box_provider = box.metadata["provider"] - if formats - found = false - formats.each do |format| - # Verify that the given provider matches what the box has. - if box_provider.to_sym == format.to_sym - found = true - break - end - end - + if providers + found = providers.find { |p| p.to_sym == box_provider.to_sym } if !found @logger.error("Added box provider doesnt match expected: #{log_provider}") raise Errors::BoxProviderDoesntMatch, @@ -155,7 +153,7 @@ module Vagrant provider = box_provider.to_sym # Create the directory for this box, not including the provider - box_dir = @directory.join(name) + box_dir = @directory.join(name, version) box_dir.mkpath @logger.debug("Box directory: #{box_dir}") @@ -182,7 +180,7 @@ module Vagrant end # Return the box - find(name, provider) + find(name, provider, version) end # This returns an array of all the boxes on the system, given by @@ -266,37 +264,6 @@ module Vagrant nil end - # Upgrades a V1 box with the given name to a V2 box. If a box with the - # given name doesn't exist, then a `BoxNotFound` exception will be raised. - # If the given box is found but is not a V1 box then `true` is returned - # because this just works fine. - # - # @param [String] name Name of the box (logical name). - # @return [Boolean] `true` otherwise an exception is raised. - def upgrade(name) - with_collection_lock do - @logger.debug("Upgrade request for box: #{name}") - box_dir = @directory.join(name) - - # If the box doesn't exist at all, raise an exception - raise Errors::BoxNotFound, :name => name, :provider => "virtualbox" if !box_dir.directory? - - if v1_box?(box_dir) - @logger.debug("V1 box #{name} found. Upgrading!") - - # First we actually perform the upgrade - temp_dir = v1_upgrade(box_dir) - - # Rename the temporary directory to the provider. - FileUtils.mv(temp_dir.to_s, box_dir.join("virtualbox").to_s) - @logger.info("Box '#{name}' upgraded from V1 to V2.") - end - end - - # We did it! Or the v1 box didn't exist so it doesn't matter. - return true - end - # This upgrades a v1.1 - v1.4 box directory structure up to a v1.5 # directory structure. This will raise exceptions if it fails in any # way. @@ -311,7 +278,10 @@ module Vagrant box_name = boxdir.basename.to_s # If it is a v1 box, then we need to upgrade it first - upgrade(box_name) if v1_box?(boxdir) + if v1_box?(boxdir) + upgrade_dir = v1_upgrade(boxdir) + FileUtils.mv(upgrade_dir, boxdir.join("virtualbox")) + end # Create the directory for this box new_box_dir = temp_dir.join(box_name, "0") diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 2739a8b3f..f0803b809 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1230,7 +1230,8 @@ en: The box you're attempting to add already exists: Name: %{name} - Provider: %{formats} + Version: %{version} + Provider: %{providers} add: adding: |- Extracting box... diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index 0309753f0..b75849d83 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -8,17 +8,16 @@ describe Vagrant::BoxCollection do let(:box_class) { Vagrant::Box } let(:environment) { isolated_environment } - let(:instance) { described_class.new(environment.boxes_dir) } - subject { instance } + subject { described_class.new(environment.boxes_dir) } it "should tell us the directory it is using" do - instance.directory.should == environment.boxes_dir + subject.directory.should == environment.boxes_dir end describe "#all" do it "should return an empty array when no boxes are there" do - instance.all.should == [] + subject.all.should == [] end it "should return the boxes and their providers" do @@ -28,7 +27,7 @@ describe Vagrant::BoxCollection do environment.box3("bar", "0", :ec2) # Verify some output - results = instance.all + results = subject.all results.length.should == 3 results.include?(["foo", "1.0", :virtualbox]).should be results.include?(["foo", "1.0", :vmware]).should be @@ -37,7 +36,7 @@ describe Vagrant::BoxCollection do it 'does not raise an exception when a file appears in the boxes dir' do Tempfile.new('a_file', environment.boxes_dir) - expect { instance.all }.to_not raise_error + expect { subject.all }.to_not raise_error end end @@ -88,24 +87,23 @@ describe Vagrant::BoxCollection do box_path = environment.box2_file(:virtualbox) # Add the box - box = instance.add(box_path, "foo", :virtualbox) - box.should be_kind_of(box_class) - box.name.should == "foo" - box.provider.should == :virtualbox + box = subject.add(box_path, "foo", "1.0", providers: :virtualbox) + expect(box).to be_kind_of(box_class) + expect(box.name).to eq("foo") + expect(box.provider).to eq(:virtualbox) # Verify we can find it as well - box = instance.find("foo", :virtualbox) - box.should_not be_nil + expect(subject.find("foo", :virtualbox, "1.0")).to_not be_nil end it "should add a box without specifying a provider" do box_path = environment.box2_file(:vmware) # Add the box - box = instance.add(box_path, "foo") - box.should be_kind_of(box_class) - box.name.should == "foo" - box.provider.should == :vmware + box = subject.add(box_path, "foo", "1.0") + expect(box).to be_kind_of(box_class) + expect(box.name).to eq("foo") + expect(box.provider).to eq(:vmware) end it "should add a V1 box" do @@ -113,35 +111,39 @@ describe Vagrant::BoxCollection do box_path = environment.box1_file # Add the box - box = instance.add(box_path, "foo") - box.should be_kind_of(box_class) - box.name.should == "foo" - box.provider.should == :virtualbox + box = subject.add(box_path, "foo", "1.0") + expect(box).to be_kind_of(box_class) + expect(box.name).to eq("foo") + expect(box.provider).to eq(:virtualbox) end it "should raise an exception if the box already exists" do prev_box_name = "foo" prev_box_provider = :virtualbox + prev_box_version = "1.0" # Create the box we're adding - environment.box2(prev_box_name, prev_box_provider) + environment.box3(prev_box_name, "1.0", prev_box_provider) # Attempt to add the box with the same name box_path = environment.box2_file(prev_box_provider) - expect { instance.add(box_path, prev_box_name, prev_box_provider) }. - to raise_error(Vagrant::Errors::BoxAlreadyExists) + expect { + subject.add(box_path, prev_box_name, + prev_box_version, providers: prev_box_provider) + }.to raise_error(Vagrant::Errors::BoxAlreadyExists) end it "should replace the box if force is specified" do prev_box_name = "foo" prev_box_provider = :vmware + prev_box_version = "1.0" # Setup the environment with the box pre-added - environment.box2(prev_box_name, prev_box_provider) + environment.box3(prev_box_name, prev_box_version, prev_box_provider) # Attempt to add the box with the same name box_path = environment.box2_file(prev_box_provider, metadata: { "replaced" => "yes" }) - box = instance.add(box_path, prev_box_name, nil, true) + box = subject.add(box_path, prev_box_name, prev_box_version, force: true) box.metadata["replaced"].should == "yes" end @@ -151,25 +153,13 @@ describe Vagrant::BoxCollection do box_path = environment.box2_file(:vmware) # Add it once, successfully - expect { instance.add(box_path, box_name) }.to_not raise_error + expect { subject.add(box_path, box_name, "1.0") }.to_not raise_error # Add it again, and fail! - expect { instance.add(box_path, box_name) }. + expect { subject.add(box_path, box_name, "1.0") }. to raise_error(Vagrant::Errors::BoxAlreadyExists) end - it "should raise an exception if you're attempting to add a box that exists as a V1 box" do - prev_box_name = "foo" - - # Create the V1 box - environment.box1(prev_box_name) - - # Attempt to add some V2 box with the same name - box_path = environment.box2_file(:vmware) - expect { instance.add(box_path, prev_box_name) }. - to raise_error(Vagrant::Errors::BoxUpgradeRequired) - end - it "should raise an exception and not add the box if the provider doesn't match" do box_name = "foo" good_provider = :virtualbox @@ -180,11 +170,11 @@ describe Vagrant::BoxCollection do # Add the box but with an invalid provider, verify we get the proper # error. - expect { instance.add(box_path, box_name, bad_provider) }. + expect { subject.add(box_path, box_name, "1.0", providers: bad_provider) }. to raise_error(Vagrant::Errors::BoxProviderDoesntMatch) # Verify the box doesn't exist - instance.find(box_name, bad_provider).should be_nil + expect(subject.find(box_name, bad_provider, "1.0")).to be_nil end it "should raise an exception if you add an invalid box file" do @@ -199,7 +189,7 @@ describe Vagrant::BoxCollection do f.write("\0"*CHECKSUM_LENGTH) f.close - expect { instance.add(f.path, "foo", :virtualbox) }. + expect { subject.add(f.path, "foo", "1.0") }. to raise_error(Vagrant::Errors::BoxUnpackageFailure) ensure f.close @@ -208,57 +198,15 @@ describe Vagrant::BoxCollection do end end - describe "upgrading" do - it "should upgrade a V1 box to V2" do - # Create a V1 box - environment.box1("foo") - - # Verify that only a V1 box exists - expect { instance.find("foo", :virtualbox) }. - to raise_error(Vagrant::Errors::BoxUpgradeRequired) - - # Upgrade the box - instance.upgrade("foo").should be - - # Verify the box exists - box = instance.find("foo", :virtualbox) - box.should_not be_nil - box.name.should == "foo" - end - - it "should raise a BoxNotFound exception if a non-existent box is upgraded" do - expect { instance.upgrade("i-dont-exist") }. - to raise_error(Vagrant::Errors::BoxNotFound) - end - - it "should return true if we try to upgrade a V2 box" do - # Create a V2 box - environment.box2("foo", :vmware) - - instance.upgrade("foo").should be - end - end - describe "#upgrade_v1_1_v1_5" do let(:boxes_dir) { environment.boxes_dir } before do # Create all the various box directories - @foo_path = boxes_dir.join("foo", "virtualbox") - @vbox_path = boxes_dir.join("precise64", "virtualbox") - @vmware_path = boxes_dir.join("precise64", "vmware") - @v1_path = boxes_dir.join("v1box") - - [@foo_path, @vbox_path, @vmware_path].each do |path| - path.mkpath - path.join("name").open("w") do |f| - f.write(path.to_s) - end - end - - @v1_path.mkpath - @v1_path.join("box.ovf").open("w") { |f| f.write("yep!") } - @v1_path.join("name").open("w") { |f| f.write("v1box") } + @foo_path = environment.box2("foo", "virtualbox") + @vbox_path = environment.box2("precise64", "virtualbox") + @vmware_path = environment.box2("precise64", "vmware") + @v1_path = environment.box("v1box") end it "upgrades the boxes" do From d87c41432751545ae5931d15c5e4a2569e693ad9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 16:03:53 -0800 Subject: [PATCH 08/90] core: Box has a version field --- lib/vagrant/box.rb | 11 +++++-- lib/vagrant/box_collection.rb | 4 +-- test/unit/vagrant/box_test.rb | 62 +++++++++++++++++++++-------------- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/lib/vagrant/box.rb b/lib/vagrant/box.rb index e7e89a9ca..254c0c393 100644 --- a/lib/vagrant/box.rb +++ b/lib/vagrant/box.rb @@ -23,6 +23,11 @@ module Vagrant # @return [Symbol] attr_reader :provider + # The version of this box. + # + # @return [String] + attr_reader :version + # This is the directory on disk where this box exists. # # @return [Pathname] @@ -40,8 +45,9 @@ module Vagrant # @param [Symbol] provider The provider that this box implements. # @param [Pathname] directory The directory where this box exists on # disk. - def initialize(name, provider, directory) + def initialize(name, provider, version, directory) @name = name + @version = version @provider = provider @directory = directory @@ -96,7 +102,8 @@ module Vagrant return super if !other.is_a?(self.class) # Comparison is done by composing the name and provider - "#{@name}-#{@provider}" <=> "#{other.name}-#{other.provider}" + "#{@name}-#{@version}-#{@provider}" <=> + "#{other.name}-#{other.version}-#{other.provider}" end end end diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index e1262c7ba..4c258f990 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -131,7 +131,7 @@ module Vagrant with_temp_dir(temp_dir) do |final_temp_dir| # Get an instance of the box we just added before it is finalized # in the system so we can inspect and use its metadata. - box = Box.new(name, nil, final_temp_dir) + box = Box.new(name, nil, version, final_temp_dir) # Get the provider, since we'll need that to at the least add it # to the system or check that it matches what is given to us. @@ -256,7 +256,7 @@ module Vagrant provider_dir = versiondir.join(provider.to_s) next if !provider_dir.directory? @logger.info("Box found: #{name} (#{provider})") - return Box.new(name, provider, provider_dir) + return Box.new(name, provider, version, provider_dir) end end end diff --git a/test/unit/vagrant/box_test.rb b/test/unit/vagrant/box_test.rb index 93dbfea17..114a75130 100644 --- a/test/unit/vagrant/box_test.rb +++ b/test/unit/vagrant/box_test.rb @@ -11,21 +11,20 @@ describe Vagrant::Box do let(:name) { "foo" } let(:provider) { :virtualbox } + let(:version) { "1.0" } let(:directory) { environment.box2("foo", :virtualbox) } - let(:instance) { described_class.new(name, provider, directory) } - - subject { described_class.new(name, provider, directory) } + subject { described_class.new(name, provider, version, directory) } it "provides the name" do - instance.name.should == name + subject.name.should == name end it "provides the provider" do - instance.provider.should == provider + subject.provider.should == provider end it "provides the directory" do - instance.directory.should == directory + subject.directory.should == directory end it "provides the metadata associated with a box" do @@ -37,7 +36,7 @@ describe Vagrant::Box do end # Verify the metadata - instance.metadata.should == data + subject.metadata.should == data end context "with a corrupt metadata file" do @@ -70,15 +69,15 @@ describe Vagrant::Box do directory.exist?.should be # Destroy it - instance.destroy!.should be + subject.destroy!.should be # Verify that it is "destroyed" directory.exist?.should_not be end it "should not error destroying a non-existent box" do - # Get the instance so that it is instantiated - box = instance + # Get the subject so that it is instantiated + box = subject # Delete the directory directory.rmtree @@ -100,36 +99,51 @@ describe Vagrant::Box do # Repackage our box to some temporary directory box_output_path = temporary_dir.join("package.box") - instance.repackage(box_output_path).should be + expect(subject.repackage(box_output_path)).to be_true # Let's now add this box again under a different name, and then # verify that we get the proper result back. - new_box = box_collection.add(box_output_path, "foo2") + new_box = box_collection.add(box_output_path, "foo2", "1.0") new_box.directory.join("test_file").read.should == test_file_contents end end describe "comparison and ordering" do - it "should be equal if the name and provider match" do - a = described_class.new("a", :foo, directory) - b = described_class.new("a", :foo, directory) + it "should be equal if the name, provider, version match" do + a = described_class.new("a", :foo, "1.0", directory) + b = described_class.new("a", :foo, "1.0", directory) a.should == b end - it "should not be equal if the name and provider do not match" do - a = described_class.new("a", :foo, directory) - b = described_class.new("b", :foo, directory) + it "should not be equal if name doesn't match" do + a = described_class.new("a", :foo, "1.0", directory) + b = described_class.new("b", :foo, "1.0", directory) - a.should_not == b + expect(a).to_not eq(b) end - it "should sort them in order of name then provider" do - a = described_class.new("a", :foo, directory) - b = described_class.new("b", :foo, directory) - c = described_class.new("c", :foo2, directory) + it "should not be equal if provider doesn't match" do + a = described_class.new("a", :foo, "1.0", directory) + b = described_class.new("a", :bar, "1.0", directory) - [c, a, b].sort.should == [a, b, c] + expect(a).to_not eq(b) + end + + it "should not be equal if version doesn't match" do + a = described_class.new("a", :foo, "1.0", directory) + b = described_class.new("a", :foo, "1.1", directory) + + expect(a).to_not eq(b) + end + + it "should sort them in order of name, version, provider" do + a = described_class.new("a", :foo, "1.0", directory) + b = described_class.new("a", :foo2, "1.0", directory) + c = described_class.new("a", :foo2, "1.1", directory) + d = described_class.new("b", :foo2, "1.0", directory) + + [d, c, a, b].sort.should == [a, b, c, d] end end end From 16ced8d36c4f09c2ab4a379c117f8a1db1784a84 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 16:04:25 -0800 Subject: [PATCH 09/90] core: boxcollection test tests versions --- test/unit/vagrant/box_collection_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index b75849d83..64ddb8896 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -67,7 +67,7 @@ describe Vagrant::BoxCollection do expect(result).to_not be_nil expect(result).to be_kind_of(box_class) expect(result.name).to eq("foo") - # TODO: test version + expect(result.version).to eq("1.0") end it "returns nil if a box's constraints can't be satisfied" do From e111b7a691ed4ba78762fe3e05b60eb135409076 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 16:13:24 -0800 Subject: [PATCH 10/90] core: environment tests pass --- lib/vagrant/environment.rb | 3 ++- test/unit/support/isolated_environment.rb | 8 +++++++ test/unit/vagrant/environment_test.rb | 28 +++++++++++------------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index db76f1884..dff913c39 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -349,7 +349,8 @@ module Vagrant box = nil if config.vm.box begin - box = boxes.find(config.vm.box, box_formats) + # TODO: support real version constraints + box = boxes.find(config.vm.box, box_formats, ">= 0") rescue Errors::BoxUpgradeRequired # Upgrade the box if we must @logger.info("Upgrading box during config load: #{config.vm.box}") diff --git a/test/unit/support/isolated_environment.rb b/test/unit/support/isolated_environment.rb index f69268ce7..0ea9bb9a8 100644 --- a/test/unit/support/isolated_environment.rb +++ b/test/unit/support/isolated_environment.rb @@ -109,6 +109,14 @@ module Unit })) end + # Create a Vagrantfile + if opts[:vagrantfile] + box_vagrantfile = box_dir.join("Vagrantfile") + box_vagrantfile.open("w") do |f| + f.write(opts[:vagrantfile]) + end + end + box_dir end diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index e7499aa7a..5dadef024 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -13,7 +13,7 @@ describe Vagrant::Environment do let(:env) do isolated_environment.tap do |e| - e.box2("base", :virtualbox) + e.box3("base", "1.0", :virtualbox) e.vagrantfile <<-VF Vagrant.configure("2") do |config| config.vm.box = "base" @@ -469,7 +469,7 @@ Vagrant.configure("2") do |config| end VF - env.box2("base", :virtualbox) + env.box3("base", "1.0", :virtualbox) end env = environment.create_vagrant_env @@ -486,7 +486,7 @@ Vagrant.configure("2") do |config| end VF - env.box2("base", :virtualbox) + env.box3("base", "1.0", :virtualbox) end env = environment.create_vagrant_env @@ -588,7 +588,7 @@ Vagrant.configure("2") do |config| end VF - e.box2("base", :foo) + e.box3("base", "1.0", :foo) end # Verify that we can get the machine @@ -621,7 +621,7 @@ Vagrant.configure("2") do |config| end VF - e.box2("base", :foo) + e.box3("base", "1.0", :foo) end # Verify that we can get the machine @@ -648,8 +648,8 @@ Vagrant.configure("2") do |config| end VF - e.box2("base", :foo) - e.box2("base", :bar) + e.box3("base", "1.0", :foo) + e.box3("base", "1.0", :bar) end env = isolated_env.create_vagrant_env @@ -694,7 +694,7 @@ Vagrant.configure("2") do |config| end VF - env.box2("base", :foo) + env.box3("base", "1.0", :foo) end env = environment.create_vagrant_env @@ -713,7 +713,7 @@ Vagrant.configure("2") do |config| end VF - env.box2("base", :foo, :vagrantfile => <<-VF) + env.box3("base", "1.0", :foo, :vagrantfile => <<-VF) Vagrant.configure("2") do |config| config.ssh.port = 100 end @@ -735,7 +735,7 @@ Vagrant.configure("2") do |config| end VF - env.box2("base", :foo, :vagrantfile => <<-VF) + env.box3("base", "1.0", :foo, :vagrantfile => <<-VF) Vagrant.configure("2") do |config| config.ssh.port = 100 end @@ -760,7 +760,7 @@ Vagrant.configure("2") do |config| end VF - env.box2("base", :bar, :vagrantfile => <<-VF) + env.box3("base", "1.0", :bar, :vagrantfile => <<-VF) Vagrant.configure("2") do |config| config.ssh.port = 100 end @@ -782,13 +782,13 @@ Vagrant.configure("2") do |config| end VF - env.box2("base", :fA, :vagrantfile => <<-VF) + env.box3("base", "1.0", :fA, :vagrantfile => <<-VF) Vagrant.configure("2") do |config| config.ssh.port = 100 end VF - env.box2("base", :fB, :vagrantfile => <<-VF) + env.box3("base", "1.0", :fB, :vagrantfile => <<-VF) Vagrant.configure("2") do |config| config.ssh.port = 200 end @@ -835,7 +835,7 @@ Vagrant.configure("2") do |config| end VF - e.box2("base", :foo) + e.box3("base", "1.0", :foo) end env = isolated_env.create_vagrant_env From e8197c4e87d14bada7a3214136421171896a045b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 19:09:33 -0800 Subject: [PATCH 11/90] commands/box: list works with the new versions --- plugins/commands/box/command/list.rb | 8 +++++--- test/unit/vagrant/box_test.rb | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/commands/box/command/list.rb b/plugins/commands/box/command/list.rb index 7b2d09408..5e5c78038 100644 --- a/plugins/commands/box/command/list.rb +++ b/plugins/commands/box/command/list.rb @@ -42,13 +42,15 @@ module VagrantPlugins # ignore the "v1" param for now since I'm not yet sure if its # important for the user to know what boxes need to be upgraded # and which don't, since we plan on doing that transparently. - boxes.each do |name, provider, _v1| - @env.ui.info("#{name.ljust(longest_box_length)} (#{provider})", :prefix => false) + boxes.each do |name, version, provider| + @env.ui.info("#{name.ljust(longest_box_length)} (#{provider})") @env.ui.machine("box-name", name) @env.ui.machine("box-provider", provider) + @env.ui.machine("box-version", version) - info_file = @env.boxes.find(name, provider).directory.join("info.json") + info_file = @env.boxes.find(name, provider, version). + directory.join("info.json") if info_file.file? info = JSON.parse(info_file.read) info.each do |k, v| diff --git a/test/unit/vagrant/box_test.rb b/test/unit/vagrant/box_test.rb index 114a75130..a540476f7 100644 --- a/test/unit/vagrant/box_test.rb +++ b/test/unit/vagrant/box_test.rb @@ -12,7 +12,7 @@ describe Vagrant::Box do let(:name) { "foo" } let(:provider) { :virtualbox } let(:version) { "1.0" } - let(:directory) { environment.box2("foo", :virtualbox) } + let(:directory) { environment.box3("foo", "1.0", :virtualbox) } subject { described_class.new(name, provider, version, directory) } it "provides the name" do From 8abcc6e5f25ce3593c84af3f1ab002f30c4a64d3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Jan 2014 20:17:30 -0800 Subject: [PATCH 12/90] core: BoxMetadata can read the JSON description --- lib/vagrant/box_metadata.rb | 124 ++++++++++++++++++++++ lib/vagrant/errors.rb | 4 + templates/locales/en.yml | 6 ++ test/unit/vagrant/box_metadata_test.rb | 139 +++++++++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 lib/vagrant/box_metadata.rb create mode 100644 test/unit/vagrant/box_metadata_test.rb diff --git a/lib/vagrant/box_metadata.rb b/lib/vagrant/box_metadata.rb new file mode 100644 index 000000000..fead0c0ba --- /dev/null +++ b/lib/vagrant/box_metadata.rb @@ -0,0 +1,124 @@ +require "json" + +module Vagrant + # BoxMetadata represents metadata about a box, including the name + # it should have, a description of it, the versions it has, and + # more. + class BoxMetadata + # The name that the box should be if it is added. + # + # @return [String] + attr_accessor :name + + # The long-form human-readable description of a box. + # + # @return [String] + attr_accessor :description + + # Loads the metadata associated with the box from the given + # IO. + # + # @param [IO] io An IO object to read the metadata from. + def initialize(io) + begin + @raw = JSON.load(io) + rescue JSON::ParserError => e + raise Errors::BoxMetadataMalformed, + error: e.to_s + end + + @name = @raw["name"] + @description = @raw["description"] + @version_map = @raw["versions"].map do |v| + [Gem::Version.new(v["version"]), v] + end + @version_map = Hash[@version_map] + + # TODO: check for corruption: + # - malformed version + end + + # Returns data about a single version that is included in this + # metadata. + # + # @param [String] version The version to return, this can also + # be a constraint. + # @return [Version] The matching version or nil if a matching + # version was not found. + def version(version) + requirements = version.split(",").map do |v| + Gem::Requirement.new(v.strip) + end + + @version_map.keys.sort.reverse.each do |v| + if requirements.all? { |r| r.satisfied_by?(v) } + return Version.new(@version_map[v]) + end + end + + nil + end + + # Returns all the versions supported by this metadata. These + # versions are sorted so the last element of the list is the + # latest version. + # + # @return[Array] + def versions + @version_map.keys.sort.map(&:to_s) + end + + # Represents a single version within the metadata. + class Version + # The version that this Version object represents. + # + # @return [String] + attr_accessor :version + + def initialize(raw=nil) + return if !raw + + @version = raw["version"] + @provider_map = (raw["providers"] || []).map do |p| + [p["name"], p] + end + @provider_map = Hash[@provider_map] + end + + # Returns a [Provider] for the given name, or nil if it isn't + # supported by this version. + def provider(name) + p = @provider_map[name] + return nil if !p + Provider.new(p) + end + + # Returns the providers that are available for this version + # of the box. + # + # @return [Provider] + def providers + @provider_map.keys + end + end + + # Provider represents a single provider-specific box available + # for a version for a box. + class Provider + # The name of the provider. + # + # @return [String] + attr_accessor :name + + # The URL of the box. + # + # @return [String] + attr_accessor :url + + def initialize(raw) + @name = raw["name"] + @url = raw["url"] + end + end + end +end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 5927c5bfd..099c925a4 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -144,6 +144,10 @@ module Vagrant error_key(:box_metadata_file_not_found) end + class BoxMetadataMalformed < VagrantError + error_key(:box_metadata_malformed) + end + class BoxNotFound < VagrantError error_key(:box_not_found) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index f0803b809..dbc2eedfa 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -266,6 +266,12 @@ en: box file format can be found at the URL below: http://docs.vagrantup.com/v2/boxes/format.html + box_metadata_malformed: |- + The metadata for the box was malformed. The exact error + is shown below. Please contact the maintainer of the box so + that this issue can be fixed. + + %{error} box_not_found: Box '%{name}' with '%{provider}' provider could not be found. box_provider_doesnt_match: |- The box you attempted to add doesn't match the provider you specified. diff --git a/test/unit/vagrant/box_metadata_test.rb b/test/unit/vagrant/box_metadata_test.rb new file mode 100644 index 000000000..a950168f7 --- /dev/null +++ b/test/unit/vagrant/box_metadata_test.rb @@ -0,0 +1,139 @@ +require File.expand_path("../../base", __FILE__) + +require "vagrant/box_metadata" + +describe Vagrant::BoxMetadata do + include_context "unit" + + let(:raw) do + <<-RAW + { + "name": "foo", + "description": "bar", + "versions": [ + { + "version": "1.0.0" + }, + { + "version": "1.1.5" + }, + { + "version": "1.1.0" + } + ] + } + RAW + end + + subject { described_class.new(raw) } + + its(:name) { should eq("foo") } + its(:description) { should eq("bar") } + + context "with poorly formatted JSON" do + let(:raw) { + <<-RAW + { "name": "foo", } + RAW + } + + it "raises an exception" do + expect { subject }. + to raise_error(Vagrant::Errors::BoxMetadataMalformed) + end + end + + describe "#version" do + it "matches an exact version" do + result = subject.version("1.0.0") + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.0.0") + end + + it "matches a constraint with latest matching version" do + result = subject.version(">= 1.0") + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.1.5") + end + + it "matches complex constraints" do + result = subject.version(">= 0.9, ~> 1.0.0") + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.0.0") + end + end + + describe "#versions" do + it "returns the versions it contained" do + expect(subject.versions).to eq( + ["1.0.0", "1.1.0", "1.1.5"]) + end + end +end + +describe Vagrant::BoxMetadata::Version do + let(:raw) { {} } + + subject { described_class.new(raw) } + + before do + raw["providers"] = [ + { + "name" => "virtualbox", + }, + { + "name" => "vmware", + } + ] + end + + describe "#version" do + it "is the version in the raw data" do + v = "1.0" + raw["version"] = v + expect(subject.version).to eq(v) + end + end + + describe "#provider" do + it "returns nil if a provider isn't supported" do + expect(subject.provider("foo")).to be_nil + end + + it "returns the provider specified" do + result = subject.provider("virtualbox") + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Provider) + end + end + + describe "#providers" do + it "returns the providers available" do + expect(subject.providers.sort).to eq( + ["virtualbox", "vmware"]) + end + end +end + +describe Vagrant::BoxMetadata::Provider do + let(:raw) { {} } + + subject { described_class.new(raw) } + + describe "#name" do + it "is the name specified" do + raw["name"] = "foo" + expect(subject.name).to eq("foo") + end + end + + describe "#url" do + it "is the URL specified" do + raw["url"] = "bar" + expect(subject.url).to eq("bar") + end + end +end From 96e92167d968e31b1ef1496c0bf2f7375e3d54d7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 10:45:39 -0800 Subject: [PATCH 13/90] commands/box: broken box adding, but more options/tests --- plugins/commands/box/command/add.rb | 71 +++++++++++-------- .../plugins/commands/box/command/add_test.rb | 67 +++++++++++++++++ .../vagrant/action/builtin/box_add_test.rb | 8 +++ 3 files changed, 117 insertions(+), 29 deletions(-) create mode 100644 test/unit/plugins/commands/box/command/add_test.rb create mode 100644 test/unit/vagrant/action/builtin/box_add_test.rb diff --git a/plugins/commands/box/command/add.rb b/plugins/commands/box/command/add.rb index f53283704..d7b0e79e2 100644 --- a/plugins/commands/box/command/add.rb +++ b/plugins/commands/box/command/add.rb @@ -8,18 +8,10 @@ module VagrantPlugins options = {} opts = OptionParser.new do |o| - o.banner = "Usage: vagrant box add [--provider provider] [-h]" + o.banner = "Usage: vagrant box add [-h]" o.separator "" - o.on("--checksum VALUE", String, "Checksum") do |c| - options[:checksum] = c - end - - o.on("--checksum-type VALUE", String, "Checksum type") do |c| - options[:checksum_type] = c.to_sym - end - - o.on("-c", "--clean", "Remove old temporary download if it exists.") do |c| + o.on("-c", "--clean", "Clean any temporary download files") do |c| options[:clean] = c end @@ -27,45 +19,66 @@ module VagrantPlugins options[:force] = f end - o.on("--insecure", "If set, SSL certs will not be validated.") do |i| + o.on("--insecure", "Do not validate SSL certificates") do |i| options[:insecure] = i end - o.on("--cacert certfile", String, "CA certificate") do |c| + o.on("--cacert certfile", String, "CA certificate for SSL download") do |c| options[:ca_cert] = c end o.on("--cert certfile", String, - "The client SSL cert") do |c| + "A client SSL cert, if needed") do |c| options[:client_cert] = c end - o.on("--provider provider", String, - "The provider that backs the box.") do |p| + o.on("--provider VALUE", String, "Provider the box should satisfy") do |p| options[:provider] = p end + + o.separator "" + o.separator "The options below only apply if you're adding a box file directly," + o.separator "and not using a Vagrant server or a box structured like 'user/box':" + o.separator "" + + o.on("--checksum VALUE", String, "Checksum for the box") do |c| + options[:checksum] = c + end + + o.on("--checksum-type VALUE", String, "Checksum type (md5, sha1, sha256)") do |c| + options[:checksum_type] = c.to_sym + end + + o.on("--name VALUE", String, "Name of the box") do |n| + options[:name] = n + end end # Parse the options argv = parse_options(opts) return if !argv - raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length < 2 + if argv.empty? || argv.length > 2 + raise Vagrant::Errors::CLIInvalidUsage, + help: opts.help.chomp + end - # Get the provider if one was set - provider = nil - provider = options[:provider].to_sym if options[:provider] + url = argv[0] + if argv.length == 2 + options[:name] = argv[0] + url = argv[1] + end @env.action_runner.run(Vagrant::Action.action_box_add, { - :box_name => argv[0], - :box_provider => provider, - :box_url => argv[1], - :box_checksum_type => options[:checksum_type], - :box_checksum => options[:checksum], - :box_clean => options[:clean], - :box_force => options[:force], - :box_download_ca_cert => options[:ca_cert], - :box_download_client_cert => options[:client_cert], - :box_download_insecure => options[:insecure], + box_url: url, + box_name: options[:name], + box_provider: options[:provider], + box_checksum_type: options[:checksum_type], + box_checksum: options[:checksum], + box_clean: options[:clean], + box_force: options[:force], + box_download_ca_cert: options[:ca_cert], + box_download_client_cert: options[:client_cert], + box_download_insecure: options[:insecure], }) # Success, exit status 0 diff --git a/test/unit/plugins/commands/box/command/add_test.rb b/test/unit/plugins/commands/box/command/add_test.rb new file mode 100644 index 000000000..23b109974 --- /dev/null +++ b/test/unit/plugins/commands/box/command/add_test.rb @@ -0,0 +1,67 @@ +require File.expand_path("../../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/box/command/add") + +describe VagrantPlugins::CommandBox::Command::Add do + include_context "unit" + + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + subject { described_class.new(argv, iso_env) } + + let(:action_runner) { double("action_runner") } + + before do + iso_env.stub(action_runner: action_runner) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with one argument" do + let(:argv) { ["foo"] } + + it "executes the runner with the proper actions" do + action_runner.should_receive(:run).with do |action, **opts| + expect(opts[:box_name]).to be_nil + expect(opts[:box_url]).to eq("foo") + true + end + + subject.execute + end + end + + context "with two arguments" do + let(:argv) { ["foo", "bar"] } + + it "executes the runner with the proper actions" do + action_runner.should_receive(:run).with do |action, **opts| + expect(opts[:box_name]).to eq("foo") + expect(opts[:box_url]).to eq("bar") + true + end + + subject.execute + end + end + + context "with more than two arguments" do + let(:argv) { ["one", "two", "three"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end +end diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb new file mode 100644 index 000000000..659c90665 --- /dev/null +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -0,0 +1,8 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Action::Builtin::BoxAdd do + let(:app) { lambda { |env| } } + let(:env) { {} } + + subject { described_class.new(app, env) } +end From 15aa91b073966a8c214fa71d2500cec968555473 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 10:57:57 -0800 Subject: [PATCH 14/90] core: can add/search boxes with slashes in name --- lib/vagrant/box_collection.rb | 21 +++++++++++++++++---- test/unit/vagrant/box_collection_test.rb | 17 ++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index 4c258f990..cc57456f3 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -153,7 +153,7 @@ module Vagrant provider = box_provider.to_sym # Create the directory for this box, not including the provider - box_dir = @directory.join(name, version) + box_dir = @directory.join(dir_name(name), version) box_dir.mkpath @logger.debug("Box directory: #{box_dir}") @@ -198,7 +198,7 @@ module Vagrant # us in our folder structure. next if !child.directory? - box_name = child.basename.to_s + box_name = undir_name(child.basename.to_s) # Otherwise, traverse the subdirectories and see what versions # we have. @@ -239,7 +239,7 @@ module Vagrant end with_collection_lock do - box_directory = @directory.join(name) + box_directory = @directory.join(dir_name(name)) if !box_directory.directory? @logger.info("Box not found: #{name} (#{providers.join(", ")})") return nil @@ -284,7 +284,7 @@ module Vagrant end # Create the directory for this box - new_box_dir = temp_dir.join(box_name, "0") + new_box_dir = temp_dir.join(dir_name(box_name), "0") new_box_dir.mkpath # Go through each provider and move it @@ -301,6 +301,19 @@ module Vagrant protected + # Returns the directory name for the box of the given name. + # + # @param [String] name + # @return [String] + def dir_name(name) + name.gsub("/", "-VAGRANTSLASH-") + end + + # Returns the directory name for the box cleaned up + def undir_name(name) + name.gsub("-VAGRANTSLASH-", "/") + end + # This checks if the given directory represents a V1 box on the # system. # diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index 64ddb8896..6a83f847d 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -25,13 +25,15 @@ describe Vagrant::BoxCollection do environment.box3("foo", "1.0", :virtualbox) environment.box3("foo", "1.0", :vmware) environment.box3("bar", "0", :ec2) + environment.box3("foo-VAGRANTSLASH-bar", "1.0", :virtualbox) # Verify some output results = subject.all - results.length.should == 3 + results.length.should == 4 results.include?(["foo", "1.0", :virtualbox]).should be results.include?(["foo", "1.0", :vmware]).should be results.include?(["bar", "0", :ec2]).should be + results.include?(["foo/bar", "1.0", :virtualbox]).should be end it 'does not raise an exception when a file appears in the boxes dir' do @@ -96,6 +98,19 @@ describe Vagrant::BoxCollection do expect(subject.find("foo", :virtualbox, "1.0")).to_not be_nil end + it "should add a box with a name with '/' in it" do + box_path = environment.box2_file(:virtualbox) + + # Add the box + box = subject.add(box_path, "foo/bar", "1.0") + expect(box).to be_kind_of(box_class) + expect(box.name).to eq("foo/bar") + expect(box.provider).to eq(:virtualbox) + + # Verify we can find it as well + expect(subject.find("foo/bar", :virtualbox, "1.0")).to_not be_nil + end + it "should add a box without specifying a provider" do box_path = environment.box2_file(:vmware) From 7d44cd61c951c0a4a73b677b8c4698faaaca23e2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 11:45:31 -0800 Subject: [PATCH 15/90] core: BoxAdd can add from metadata, version constraints, more --- lib/vagrant/action/builtin/box_add.rb | 116 ++++++++ lib/vagrant/errors.rb | 8 + templates/locales/en.yml | 17 ++ .../vagrant/action/builtin/box_add_test.rb | 250 +++++++++++++++++- 4 files changed, 390 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 5adfa80ce..f48020d24 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -1,6 +1,7 @@ require "digest/sha1" require "log4r" +require "vagrant/box_metadata" require "vagrant/util/downloader" require "vagrant/util/file_checksum" require "vagrant/util/platform" @@ -19,6 +20,120 @@ module Vagrant def call(env) @download_interrupted = false + provider = env[:box_provider] + url = env[:box_url] + version = env[:box_version] + + metadata = nil + if File.file?(url) + # TODO: What if file isn't valid JSON + # TODO: What if file is old-style box + # TODO: What if URL is in the "file:" format + File.open(url) do |f| + metadata = BoxMetadata.new(f) + end + end + + # TODO: provider that is in an earlier version + # TODO: multiple providers + metadata_version = metadata.version(version || ">= 0") + if !metadata_version + raise Errors::BoxAddNoMatchingVersion, + constraints: version || ">= 0", + url: url, + versions: metadata.versions.join(", ") + end + + metadata_provider = nil + if provider + # If a provider was specified, make sure we get that specific + # version. + metadata_provider = metadata_version.provider(provider) + if !metadata_provider + raise Errors::BoxAddNoMatchingProvider, + providers: metadata_version.providers.join(", "), + requested: provider, + url: url + end + elsif metadata_version.providers.length == 1 + # If we have only one provider in the metadata, just use that + # provider. + metadata_provider = metadata_version.provider( + metadata_version.providers.first) + end + + # Now we have a URL, we have to download this URL. + box_url = download(metadata_provider.url, env) + + # Add the box! + env[:box_collection].add( + box_url, metadata.name, metadata_version.version) + + @app.call(env) + end + + def download(url, env) + temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url)) + @logger.info("Downloading box: #{url} => #{temp_path}") + + if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i + @logger.info("URL is a file or protocol not found and assuming file.") + file_path = File.expand_path(url) + file_path = Util::Platform.cygwin_windows_path(file_path) + url = "file:#{file_path}" + end + + downloader_options = {} + downloader_options[:ca_cert] = env[:box_download_ca_cert] + downloader_options[:continue] = true + downloader_options[:insecure] = env[:box_download_insecure] + downloader_options[:ui] = env[:ui] + downloader_options[:client_cert] = env[:box_client_cert] + + # If the temporary path exists, verify it is not too old. If its + # too old, delete it first because the data may have changed. + if temp_path.file? + delete = false + if env[:box_clean] + @logger.info("Cleaning existing temp box file.") + delete = true + elsif temp_path.mtime.to_i < (Time.now.to_i - 6 * 60 * 60) + @logger.info("Existing temp file is too old. Removing.") + delete = true + end + + temp_path.unlink if delete + end + + # Download the box to a temporary path. We store the temporary + # path as an instance variable so that the `#recover` method can + # access it. + env[:ui].info(I18n.t( + "vagrant.actions.box.download.downloading", + url: url)) + if temp_path.file? + env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) + end + + begin + downloader = Util::Downloader.new(url, temp_path, downloader_options) + downloader.download! + rescue Errors::DownloaderInterrupted + # The downloader was interrupted, so just return, because that + # means we were interrupted as well. + @download_interrupted = true + env[:ui].info(I18n.t("vagrant.actions.box.download.interrupted")) + rescue Errors::DownloaderError + # The download failed for some reason, clean out the temp path + temp_path.unlink if temp_path.file? + raise + end + + temp_path + end +=begin + @download_interrupted = false + box_name = env[:box_name] box_formats = env[:box_provider] if box_formats @@ -209,6 +324,7 @@ module Vagrant f.write(JSON.dump(info)) end end +=end end end end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 099c925a4..36d278a69 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -120,6 +120,14 @@ module Vagrant error_key(:batch_multi_error) end + class BoxAddNoMatchingProvider < VagrantError + error_key(:box_add_no_matching_provider) + end + + class BoxAddNoMatchingVersion < VagrantError + error_key(:box_add_no_matching_version) + end + class BoxAlreadyExists < VagrantError error_key(:already_exists, "vagrant.actions.box.unpackage") end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index dbc2eedfa..5106aab2c 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -211,6 +211,23 @@ en: Any errors that occurred are shown below. %{message} + box_add_no_matching_provider: |- + The box you're attempting to add doesn't support the provider + you requested. Please find an alternate box or use an alternate + provider. Double-check your requested provider to verify you didn't + simply misspell it. + + Box: %{url} + Requested provider: %{requested} + Available providers: %{providers} + box_add_no_matching_version: |- + The box you're attempting to add has no available version that + matches the constraints you requested. Please double-check your + settings. + + Box: %{url} + Constraints: %{constraints} + Available versions: %{versions} boot_bad_state: |- The guest machine entered an invalid state while waiting for it to boot. Valid states are '%{valid}'. The machine is in the diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index 659c90665..19a850b5f 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -1,8 +1,256 @@ +require "digest/sha1" +require "pathname" +require "tempfile" +require "tmpdir" + require File.expand_path("../../../../base", __FILE__) +require "vagrant/util/file_checksum" + describe Vagrant::Action::Builtin::BoxAdd do + include_context "unit" + let(:app) { lambda { |env| } } - let(:env) { {} } + let(:env) { { + box_collection: box_collection, + tmp_path: Pathname.new(Dir.mktmpdir), + ui: Vagrant::UI::Silent.new, + } } subject { described_class.new(app, env) } + + let(:box_collection) { double("box_collection") } + let(:iso_env) { isolated_environment } + + # Helper to quickly SHA1 checksum a path + def checksum(path) + FileChecksum.new(path, Digest::SHA1).checksum + end + + context "with box metadata" do + it "adds the latest version of a box with only one provider" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + true + end + + app.should_receive(:call).with(env) + + subject.call(env) + end + + it "adds the latest version of a box with the specified provider" do + box_path = iso_env.box2_file(:vmware) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{iso_env.box2_file(:virtualbox)}" + }, + { + "name": "vmware", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + env[:box_provider] = "vmware" + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + true + end + + app.should_receive(:call).with(env) + + subject.call(env) + end + + it "adds the constrained version of a box with the only provider" do + box_path = iso_env.box2_file(:vmware) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5", + "providers": [ + { + "name": "vmware", + "url": "#{box_path}" + } + ] + }, + { "version": "1.1" } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + env[:box_version] = "~> 0.1" + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.5") + true + end + + app.should_receive(:call).with(env) + + subject.call(env) + end + + it "adds the constrained version of a box with the specified provider" do + box_path = iso_env.box2_file(:vmware) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5", + "providers": [ + { + "name": "vmware", + "url": "#{box_path}" + }, + { + "name": "virtualbox", + "url": "#{iso_env.box2_file(:virtualbox)}" + } + ] + }, + { "version": "1.1" } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + env[:box_provider] = "vmware" + env[:box_version] = "~> 0.1" + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.5") + true + end + + app.should_receive(:call).with(env) + + subject.call(env) + end + + it "raises an exception if no matching version" do + box_path = iso_env.box2_file(:vmware) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5", + "providers": [ + { + "name": "vmware", + "url": "#{box_path}" + } + ] + }, + { "version": "1.1" } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + env[:box_version] = "~> 2.0" + box_collection.should_receive(:add).never + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion) + end + + it "raises an error if there is no matching provider" do + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{iso_env.box2_file(:virtualbox)}" + } + ] + } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + env[:box_provider] = "vmware" + box_collection.should_receive(:add).never + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxAddNoMatchingProvider) + end + end end From 80194cde354182a251fad1f835a490749bbb4224 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 11:52:42 -0800 Subject: [PATCH 16/90] core: BoxMetadata#version can constrain by providers --- lib/vagrant/box_metadata.rb | 12 ++++++++---- test/unit/vagrant/box_metadata_test.rb | 24 +++++++++++++++++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/lib/vagrant/box_metadata.rb b/lib/vagrant/box_metadata.rb index fead0c0ba..e0431b4f4 100644 --- a/lib/vagrant/box_metadata.rb +++ b/lib/vagrant/box_metadata.rb @@ -45,15 +45,19 @@ module Vagrant # be a constraint. # @return [Version] The matching version or nil if a matching # version was not found. - def version(version) + def version(version, **opts) requirements = version.split(",").map do |v| Gem::Requirement.new(v.strip) end + providers = nil + providers = Array(opts[:provider]) if opts[:provider] + @version_map.keys.sort.reverse.each do |v| - if requirements.all? { |r| r.satisfied_by?(v) } - return Version.new(@version_map[v]) - end + next if !requirements.all? { |r| r.satisfied_by?(v) } + version = Version.new(@version_map[v]) + next if (providers & version.providers).empty? if providers + return version end nil diff --git a/test/unit/vagrant/box_metadata_test.rb b/test/unit/vagrant/box_metadata_test.rb index a950168f7..45d6e7974 100644 --- a/test/unit/vagrant/box_metadata_test.rb +++ b/test/unit/vagrant/box_metadata_test.rb @@ -12,13 +12,24 @@ describe Vagrant::BoxMetadata do "description": "bar", "versions": [ { - "version": "1.0.0" + "version": "1.0.0", + "providers": [ + { "name": "virtualbox" }, + { "name": "vmware" } + ] }, { - "version": "1.1.5" + "version": "1.1.5", + "providers": [ + { "name": "virtualbox" } + ] }, { - "version": "1.1.0" + "version": "1.1.0", + "providers": [ + { "name": "virtualbox" }, + { "name": "vmware" } + ] } ] } @@ -64,6 +75,13 @@ describe Vagrant::BoxMetadata do expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) expect(result.version).to eq("1.0.0") end + + it "matches the constraint that has the given provider" do + result = subject.version(">= 0", provider: "vmware") + expect(result).to_not be_nil + expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result.version).to eq("1.1.0") + end end describe "#versions" do From 5b075fa34ec43f0425dc73900665beec0652625f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 11:58:52 -0800 Subject: [PATCH 17/90] core: BoxAdd adds matching provider even if lower version --- lib/vagrant/action/builtin/box_add.rb | 30 +++--- templates/locales/en.yml | 1 - .../vagrant/action/builtin/box_add_test.rb | 91 +++++++++++++++++++ 3 files changed, 108 insertions(+), 14 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index f48020d24..154673042 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -21,6 +21,7 @@ module Vagrant @download_interrupted = false provider = env[:box_provider] + provider = Array(provider) if provider url = env[:box_url] version = env[:box_version] @@ -34,26 +35,29 @@ module Vagrant end end - # TODO: provider that is in an earlier version - # TODO: multiple providers - metadata_version = metadata.version(version || ">= 0") + metadata_version = metadata.version( + version || ">= 0", provider: provider) if !metadata_version - raise Errors::BoxAddNoMatchingVersion, - constraints: version || ">= 0", - url: url, - versions: metadata.versions.join(", ") + if !provider + raise Errors::BoxAddNoMatchingVersion, + constraints: version || ">= 0", + url: url, + versions: metadata.versions.join(", ") + else + # TODO: show supported providers + raise Errors::BoxAddNoMatchingProvider, + requested: provider, + url: url + end end metadata_provider = nil if provider # If a provider was specified, make sure we get that specific # version. - metadata_provider = metadata_version.provider(provider) - if !metadata_provider - raise Errors::BoxAddNoMatchingProvider, - providers: metadata_version.providers.join(", "), - requested: provider, - url: url + provider.each do |p| + metadata_provider = metadata_version.provider(p) + break if metadata_provider end elsif metadata_version.providers.length == 1 # If we have only one provider in the metadata, just use that diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 5106aab2c..6744e2768 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -219,7 +219,6 @@ en: Box: %{url} Requested provider: %{requested} - Available providers: %{providers} box_add_no_matching_version: |- The box you're attempting to add has no available version that matches the constraints you requested. Please double-check your diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index 19a850b5f..e9fcb53c5 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -109,6 +109,52 @@ describe Vagrant::Action::Builtin::BoxAdd do subject.call(env) end + it "adds the latest version of a box with the specified provider, even if not latest" do + box_path = iso_env.box2_file(:vmware) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{iso_env.box2_file(:virtualbox)}" + }, + { + "name": "vmware", + "url": "#{box_path}" + } + ] + }, + { + "version": "1.5" + } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + env[:box_provider] = "vmware" + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + true + end + + app.should_receive(:call).with(env) + + subject.call(env) + end + it "adds the constrained version of a box with the only provider" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new("vagrant").tap do |f| @@ -188,6 +234,51 @@ describe Vagrant::Action::Builtin::BoxAdd do subject.call(env) end + it "adds the latest version of a box with any specified provider" do + box_path = iso_env.box2_file(:vmware) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5", + "providers": [ + { + "name": "virtualbox", + "url": "#{iso_env.box2_file(:virtualbox)}" + } + ] + }, + { + "version": "0.7", + "providers": [ + { + "name": "vmware", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + env[:box_provider] = ["virtualbox", "vmware"] + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + true + end + + app.should_receive(:call).with(env) + + subject.call(env) + end + it "raises an exception if no matching version" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new("vagrant").tap do |f| From ee139eb79dfcce8986e5f09bf3bf9d652c7765ea Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 13:07:35 -0800 Subject: [PATCH 18/90] core: more output for box adding --- lib/vagrant/action/builtin/box_add.rb | 12 +++++++++++- templates/locales/en.yml | 6 ++++-- .../unit/vagrant/action/builtin/box_add_test.rb | 17 +++++++++++------ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 154673042..4403ed863 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -66,13 +66,23 @@ module Vagrant metadata_version.providers.first) end + env[:ui].output(I18n.t( + "vagrant.box_add_with_version", + name: metadata.name, + version: metadata_version.version, + provider: metadata_provider.name)) + # Now we have a URL, we have to download this URL. box_url = download(metadata_provider.url, env) # Add the box! - env[:box_collection].add( + box = env[:box_collection].add( box_url, metadata.name, metadata_version.version) + env[:ui].success(I18n.t( + "vagrant.box_added", + name: box.name, provider: box.provider)) + @app.call(env) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 6744e2768..51bd723f0 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -4,6 +4,10 @@ en: Machine booted and ready! boot_waiting: |- Waiting for machine to boot. This may take a few minutes... + box_add_with_version: |- + Adding box '%{name}' (v%{version}) for '%{provider}' provider... + box_added: |- + Successfully added box '%{name}' for '%{provider}'! cfengine_bootstrapping: |- Bootstrapping CFEngine with policy server: %{policy_server}... cfengine_bootstrapping_policy_hub: |- @@ -1257,8 +1261,6 @@ en: add: adding: |- Extracting box... - added: |- - Successfully added box '%{name}' with provider '%{provider}'! checksumming: |- Calculating and comparing box checksum... destroy: diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index e9fcb53c5..88e44acdb 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -28,6 +28,11 @@ describe Vagrant::Action::Builtin::BoxAdd do end context "with box metadata" do + let(:box) do + box_dir = iso_env.box3("foo", "1.0", :virtualbox) + Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) + end + it "adds the latest version of a box with only one provider" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new("vagrant").tap do |f| @@ -59,7 +64,7 @@ describe Vagrant::Action::Builtin::BoxAdd do expect(name).to eq("foo/bar") expect(version).to eq("0.7") true - end + end.and_return(box) app.should_receive(:call).with(env) @@ -102,7 +107,7 @@ describe Vagrant::Action::Builtin::BoxAdd do expect(name).to eq("foo/bar") expect(version).to eq("0.7") true - end + end.and_return(box) app.should_receive(:call).with(env) @@ -148,7 +153,7 @@ describe Vagrant::Action::Builtin::BoxAdd do expect(name).to eq("foo/bar") expect(version).to eq("0.7") true - end + end.and_return(box) app.should_receive(:call).with(env) @@ -185,7 +190,7 @@ describe Vagrant::Action::Builtin::BoxAdd do expect(name).to eq("foo/bar") expect(version).to eq("0.5") true - end + end.and_return(box) app.should_receive(:call).with(env) @@ -227,7 +232,7 @@ describe Vagrant::Action::Builtin::BoxAdd do expect(name).to eq("foo/bar") expect(version).to eq("0.5") true - end + end.and_return(box) app.should_receive(:call).with(env) @@ -272,7 +277,7 @@ describe Vagrant::Action::Builtin::BoxAdd do expect(name).to eq("foo/bar") expect(version).to eq("0.7") true - end + end.and_return(box) app.should_receive(:call).with(env) From adbcc9f34e931cc5a61220831bb45e67c854da06 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 13:13:26 -0800 Subject: [PATCH 19/90] core: delete the temporary file if adding succeeds --- lib/vagrant/action/builtin/box_add.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 4403ed863..062dcb943 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -73,11 +73,22 @@ module Vagrant provider: metadata_provider.name)) # Now we have a URL, we have to download this URL. - box_url = download(metadata_provider.url, env) + box = nil + begin + box_url = download(metadata_provider.url, env) - # Add the box! - box = env[:box_collection].add( - box_url, metadata.name, metadata_version.version) + # Add the box! + box = env[:box_collection].add( + box_url, metadata.name, metadata_version.version) + ensure + # Make sure we delete the temporary file after we add it, + # unless we were interrupted, in which case we keep it around + # so we can resume the download later. + if !@download_interrupted + @logger.debug("Deleting temporary box: #{box_url}") + box_url.delete + end + end env[:ui].success(I18n.t( "vagrant.box_added", From 961d3607b9c4782d674acbea77099092d9603ca7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 13:25:28 -0800 Subject: [PATCH 20/90] core: add newline to end of download if windows --- lib/vagrant/util/downloader.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index 6f26447d0..e49c8c39a 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -1,6 +1,7 @@ require "log4r" require "vagrant/util/busy" +require "vagrant/util/platform" require "vagrant/util/subprocess" module Vagrant @@ -138,7 +139,13 @@ module Vagrant # If we're outputting to the UI, clear the output to # avoid lingering progress meters. - @ui.clear_line if @ui + if @ui + @ui.clear_line + + # Windows doesn't clear properly for some reason, so we just + # output one more newline. + @ui.info("") if Platform.windows? + end # If it didn't exit successfully, we need to parse the data and # show an error message. From 28a6beaa56cc2fb56e3ee84ce2865d79af0b6f55 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 13:27:50 -0800 Subject: [PATCH 21/90] commands/box: can constrain the version --- plugins/commands/box/command/add.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/commands/box/command/add.rb b/plugins/commands/box/command/add.rb index d7b0e79e2..3d32596bf 100644 --- a/plugins/commands/box/command/add.rb +++ b/plugins/commands/box/command/add.rb @@ -36,6 +36,10 @@ module VagrantPlugins options[:provider] = p end + o.on("--version VALUE", String, "Constrain version of the added box") do |v| + options[:version] = v + end + o.separator "" o.separator "The options below only apply if you're adding a box file directly," o.separator "and not using a Vagrant server or a box structured like 'user/box':" @@ -72,6 +76,7 @@ module VagrantPlugins box_url: url, box_name: options[:name], box_provider: options[:provider], + box_version: options[:version], box_checksum_type: options[:checksum_type], box_checksum: options[:checksum], box_clean: options[:clean], From 2a08302145cb225ef67e45545c087f3761a875be Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 15:20:11 -0800 Subject: [PATCH 22/90] core: BoxAdd can add old boxes (happy path) --- lib/vagrant/action/builtin/box_add.rb | 207 ++++++++++++------ lib/vagrant/util/downloader.rb | 3 + .../vagrant/action/builtin/box_add_test.rb | 30 ++- 3 files changed, 171 insertions(+), 69 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 062dcb943..66ab23245 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -1,5 +1,7 @@ require "digest/sha1" require "log4r" +require "pathname" +require "uri" require "vagrant/box_metadata" require "vagrant/util/downloader" @@ -20,6 +22,47 @@ module Vagrant def call(env) @download_interrupted = false + url = env[:box_url] + if metadata_url?(url, env) + add_from_metadata(env) + else + add_direct(env) + end + + @app.call(env) + end + + # Adds a box file directly (no metadata component, versioning, + # etc.) + def add_direct(env) + # TODO: what if we have no name + name = env[:box_name] + url = env[:box_url] + + # Now we have a URL, we have to download this URL. + box = nil + begin + box_url = download(url, env) + + # Add the box! + box = env[:box_collection].add(box_url, name, "0") + ensure + # Make sure we delete the temporary file after we add it, + # unless we were interrupted, in which case we keep it around + # so we can resume the download later. + if !@download_interrupted + @logger.debug("Deleting temporary box: #{box_url}") + box_url.delete + end + end + + env[:ui].success(I18n.t( + "vagrant.box_added", + name: box.name, provider: box.provider)) + end + + # Adds a box given that the URL is a metadata document. + def add_from_metadata(env) provider = env[:box_provider] provider = Array(provider) if provider url = env[:box_url] @@ -28,7 +71,6 @@ module Vagrant metadata = nil if File.file?(url) # TODO: What if file isn't valid JSON - # TODO: What if file is old-style box # TODO: What if URL is in the "file:" format File.open(url) do |f| metadata = BoxMetadata.new(f) @@ -72,6 +114,9 @@ module Vagrant version: metadata_version.version, provider: metadata_provider.name)) + # TODO(mitchellh): verify that the box we're adding + # doesn't already exist. + # Now we have a URL, we have to download this URL. box = nil begin @@ -93,72 +138,9 @@ module Vagrant env[:ui].success(I18n.t( "vagrant.box_added", name: box.name, provider: box.provider)) - - @app.call(env) end - def download(url, env) - temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url)) - @logger.info("Downloading box: #{url} => #{temp_path}") - - if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i - @logger.info("URL is a file or protocol not found and assuming file.") - file_path = File.expand_path(url) - file_path = Util::Platform.cygwin_windows_path(file_path) - url = "file:#{file_path}" - end - - downloader_options = {} - downloader_options[:ca_cert] = env[:box_download_ca_cert] - downloader_options[:continue] = true - downloader_options[:insecure] = env[:box_download_insecure] - downloader_options[:ui] = env[:ui] - downloader_options[:client_cert] = env[:box_client_cert] - - # If the temporary path exists, verify it is not too old. If its - # too old, delete it first because the data may have changed. - if temp_path.file? - delete = false - if env[:box_clean] - @logger.info("Cleaning existing temp box file.") - delete = true - elsif temp_path.mtime.to_i < (Time.now.to_i - 6 * 60 * 60) - @logger.info("Existing temp file is too old. Removing.") - delete = true - end - - temp_path.unlink if delete - end - - # Download the box to a temporary path. We store the temporary - # path as an instance variable so that the `#recover` method can - # access it. - env[:ui].info(I18n.t( - "vagrant.actions.box.download.downloading", - url: url)) - if temp_path.file? - env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) - end - - begin - downloader = Util::Downloader.new(url, temp_path, downloader_options) - downloader.download! - rescue Errors::DownloaderInterrupted - # The downloader was interrupted, so just return, because that - # means we were interrupted as well. - @download_interrupted = true - env[:ui].info(I18n.t("vagrant.actions.box.download.interrupted")) - rescue Errors::DownloaderError - # The download failed for some reason, clean out the temp path - temp_path.unlink if temp_path.file? - raise - end - - temp_path - end =begin - @download_interrupted = false - box_name = env[:box_name] box_formats = env[:box_provider] if box_formats @@ -350,6 +332,103 @@ module Vagrant end end =end + + protected + + # Returns the download options for the download. + # + # @return [Hash] + def downloader(url, env) + temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url)) + @logger.info("Downloading box: #{url} => #{temp_path}") + + if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i + @logger.info("URL is a file or protocol not found and assuming file.") + file_path = File.expand_path(url) + file_path = Util::Platform.cygwin_windows_path(file_path) + url = "file:#{file_path}" + end + + # If the temporary path exists, verify it is not too old. If its + # too old, delete it first because the data may have changed. + if temp_path.file? + delete = false + if env[:box_clean] + @logger.info("Cleaning existing temp box file.") + delete = true + elsif temp_path.mtime.to_i < (Time.now.to_i - 6 * 60 * 60) + @logger.info("Existing temp file is too old. Removing.") + delete = true + end + + temp_path.unlink if delete + end + + downloader_options = {} + downloader_options[:ca_cert] = env[:box_download_ca_cert] + downloader_options[:continue] = true + downloader_options[:insecure] = env[:box_download_insecure] + downloader_options[:ui] = env[:ui] + downloader_options[:client_cert] = env[:box_client_cert] + + Util::Downloader.new(url, temp_path, downloader_options) + end + + def download(url, env) + d = downloader(url, env) + + # Download the box to a temporary path. We store the temporary + # path as an instance variable so that the `#recover` method can + # access it. + env[:ui].info(I18n.t( + "vagrant.actions.box.download.downloading", + url: url)) + if File.file?(d.destination) + env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) + end + + begin + d.download! + rescue Errors::DownloaderInterrupted + # The downloader was interrupted, so just return, because that + # means we were interrupted as well. + @download_interrupted = true + env[:ui].info(I18n.t("vagrant.actions.box.download.interrupted")) + rescue Errors::DownloaderError + # The download failed for some reason, clean out the temp path + File.unlink(d.destination) if File.file?(d.destination) + raise + end + + Pathname.new(d.destination) + end + + # Tests whether the given URL points to a metadata file or a + # box file without completely downloading the file. + # + # @param [String] url + # @return [Boolean] true if metadata + def metadata_url?(url, env) + d = downloader(url, env) + + # If we're downloading a file, cURL just returns no + # content-type (makes sense), so we just test if it is JSON + # by trying to parse JSON! + uri = URI.parse(d.source) + if uri.scheme == "file" + begin + File.open(uri.opaque, "r") do |f| + BoxMetadata.new(f) + end + return true + rescue Errors::BoxMetadataMalformed + return false + end + end + + # TODO: do the HEAD request + true + end end end end diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index e49c8c39a..88a2e7d98 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -14,6 +14,9 @@ module Vagrant # are properly tracked. USER_AGENT = "Vagrant/#{VERSION}" + attr_reader :source + attr_reader :destination + def initialize(source, destination, options=nil) @logger = Log4r::Logger.new("vagrant::util::downloader") @source = source.to_s diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index 88e44acdb..03aa619b2 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -22,17 +22,37 @@ describe Vagrant::Action::Builtin::BoxAdd do let(:box_collection) { double("box_collection") } let(:iso_env) { isolated_environment } + let(:box) do + box_dir = iso_env.box3("foo", "1.0", :virtualbox) + Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) + end + # Helper to quickly SHA1 checksum a path def checksum(path) FileChecksum.new(path, Digest::SHA1).checksum end - context "with box metadata" do - let(:box) do - box_dir = iso_env.box3("foo", "1.0", :virtualbox) - Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) - end + context "with box file directly" do + it "adds it" do + box_path = iso_env.box2_file(:virtualbox) + env[:box_name] = "foo" + env[:box_url] = box_path.to_s + + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo") + expect(version).to eq("0") + true + end.and_return(box) + + app.should_receive(:call).with(env) + + subject.call(env) + end + end + + context "with box metadata" do it "adds the latest version of a box with only one provider" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new("vagrant").tap do |f| From c1989603be9ccab8b7fb60d3b2ab1312314b1a92 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 15:21:42 -0800 Subject: [PATCH 23/90] core: adding from metadata wlil force if specified --- lib/vagrant/action/builtin/box_add.rb | 3 +- .../vagrant/action/builtin/box_add_test.rb | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 66ab23245..45a9f3129 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -124,7 +124,8 @@ module Vagrant # Add the box! box = env[:box_collection].add( - box_url, metadata.name, metadata_version.version) + box_url, metadata.name, metadata_version.version, + force: env[:box_force]) ensure # Make sure we delete the temporary file after we add it, # unless we were interrupted, in which case we keep it around diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index 03aa619b2..c9551be28 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -368,5 +368,45 @@ describe Vagrant::Action::Builtin::BoxAdd do expect { subject.call(env) }. to raise_error(Vagrant::Errors::BoxAddNoMatchingProvider) end + + it "force adds a box if specified" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + env[:box_force] = true + env[:box_url] = tf.path + box_collection.should_receive(:add).with do |path, name, version, **opts| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + expect(opts[:force]).to be_true + true + end.and_return(box) + + app.should_receive(:call).with(env) + + subject.call(env) + end end end From 75cffe53d1bac653e3f2c0251262568ff288fb72 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 15:31:54 -0800 Subject: [PATCH 24/90] core: BoxAdd checks if box already exists for metadata unless force --- lib/vagrant/action/builtin/box_add.rb | 13 +++++- lib/vagrant/errors.rb | 2 +- templates/locales/en.yml | 13 +++--- .../vagrant/action/builtin/box_add_test.rb | 40 +++++++++++++++++++ 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 45a9f3129..d96b25d9a 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -114,8 +114,17 @@ module Vagrant version: metadata_version.version, provider: metadata_provider.name)) - # TODO(mitchellh): verify that the box we're adding - # doesn't already exist. + # Verify the box we're adding doesn't already exist + if !env[:box_force] + box = env[:box_collection].find( + metadata.name, metadata_provider.name, metadata_version.version) + if box + raise Errors::BoxAlreadyExists, + name: metadata.name, + provider: metadata_provider.name, + version: metadata_version.version + end + end # Now we have a URL, we have to download this URL. box = nil diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 36d278a69..7453d3c72 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -129,7 +129,7 @@ module Vagrant end class BoxAlreadyExists < VagrantError - error_key(:already_exists, "vagrant.actions.box.unpackage") + error_key(:box_add_exists) end class BoxChecksumInvalidType < VagrantError diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 51bd723f0..d8398954d 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -4,6 +4,13 @@ en: Machine booted and ready! boot_waiting: |- Waiting for machine to boot. This may take a few minutes... + box_add_exists: |- + The box you're attempting to add already exists. Remove it before + adding it again or add it with the `--force` flag. + + Name: %{name} + Provider: %{provider} + Version: %{version} box_add_with_version: |- Adding box '%{name}' (v%{version}) for '%{provider}' provider... box_added: |- @@ -1252,12 +1259,6 @@ en: output from attempting to unpackage (if any): %{output} - already_exists: |- - The box you're attempting to add already exists: - - Name: %{name} - Version: %{version} - Provider: %{providers} add: adding: |- Extracting box... diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index c9551be28..3439b159a 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -32,6 +32,10 @@ describe Vagrant::Action::Builtin::BoxAdd do FileChecksum.new(path, Digest::SHA1).checksum end + before do + box_collection.stub(find: nil) + end + context "with box file directly" do it "adds it" do box_path = iso_env.box2_file(:virtualbox) @@ -369,6 +373,41 @@ describe Vagrant::Action::Builtin::BoxAdd do to raise_error(Vagrant::Errors::BoxAddNoMatchingProvider) end + it "raises an error if a box already exists" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + box_collection.should_receive(:find). + with("foo/bar", "virtualbox", "0.7").and_return(box) + box_collection.should_receive(:add).never + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxAlreadyExists) + end + it "force adds a box if specified" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new("vagrant").tap do |f| @@ -396,6 +435,7 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:box_force] = true env[:box_url] = tf.path + box_collection.stub(find: box) box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") From 1ca7f86f766681ee936eeca8edc3edaf7a000a37 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 15:46:30 -0800 Subject: [PATCH 25/90] core: verify providers with direct box adding --- lib/vagrant/action/builtin/box_add.rb | 244 +++++------------- .../vagrant/action/builtin/box_add_test.rb | 36 +++ 2 files changed, 104 insertions(+), 176 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index d96b25d9a..7168347cd 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -38,27 +38,15 @@ module Vagrant # TODO: what if we have no name name = env[:box_name] url = env[:box_url] + provider = env[:box_provider] + provider = Array(provider) if provider - # Now we have a URL, we have to download this URL. - box = nil - begin - box_url = download(url, env) - - # Add the box! - box = env[:box_collection].add(box_url, name, "0") - ensure - # Make sure we delete the temporary file after we add it, - # unless we were interrupted, in which case we keep it around - # so we can resume the download later. - if !@download_interrupted - @logger.debug("Deleting temporary box: #{box_url}") - box_url.delete - end - end - - env[:ui].success(I18n.t( - "vagrant.box_added", - name: box.name, provider: box.provider)) + box_add( + url, + name, + "0", + provider, + env) end # Adds a box given that the URL is a metadata document. @@ -108,77 +96,14 @@ module Vagrant metadata_version.providers.first) end - env[:ui].output(I18n.t( - "vagrant.box_add_with_version", - name: metadata.name, - version: metadata_version.version, - provider: metadata_provider.name)) - - # Verify the box we're adding doesn't already exist - if !env[:box_force] - box = env[:box_collection].find( - metadata.name, metadata_provider.name, metadata_version.version) - if box - raise Errors::BoxAlreadyExists, - name: metadata.name, - provider: metadata_provider.name, - version: metadata_version.version - end - end - - # Now we have a URL, we have to download this URL. - box = nil - begin - box_url = download(metadata_provider.url, env) - - # Add the box! - box = env[:box_collection].add( - box_url, metadata.name, metadata_version.version, - force: env[:box_force]) - ensure - # Make sure we delete the temporary file after we add it, - # unless we were interrupted, in which case we keep it around - # so we can resume the download later. - if !@download_interrupted - @logger.debug("Deleting temporary box: #{box_url}") - box_url.delete - end - end - - env[:ui].success(I18n.t( - "vagrant.box_added", - name: box.name, provider: box.provider)) + box_add( + metadata_provider.url, + metadata.name, + metadata_version.version, + metadata_provider.name, env) end =begin - box_name = env[:box_name] - box_formats = env[:box_provider] - if box_formats - # Determine the formats a box can support and allow the box to - # be any of those formats. - provider_plugin = Vagrant.plugin("2").manager.providers[env[:box_provider]] - if provider_plugin - box_formats = provider_plugin[1][:box_format] - box_formats ||= env[:box_provider] - end - end - - # Determine if we already have the box before downloading - # it again. We can only do this if we specify a format - if box_formats && !env[:box_force] - begin - if env[:box_collection].find(box_name, box_formats) - raise Errors::BoxAlreadyExists, - :name => box_name, - :formats => [box_formats].flatten.join(", ") - end - rescue Vagrant::Errors::BoxUpgradeRequired - # If the box needs to be upgraded, do it. - env[:box_collection].upgrade(box_name) - retry - end - end - # Determine the checksum type to use checksum = (env[:box_checksum] || "").to_s checksum_klass = nil @@ -237,28 +162,6 @@ module Vagrant end end - # Add the box - env[:ui].info I18n.t("vagrant.actions.box.add.adding", :name => box_name) - box_added = nil - begin - box_added = env[:box_collection].add( - @temp_path, box_name, box_formats, env[:box_force]) - rescue Vagrant::Errors::BoxUpgradeRequired - # Upgrade the box - env[:box_collection].upgrade(box_name) - - # Try adding it again - retry - end - - # Call the 'recover' method in all cases to clean up the - # downloaded temporary file. - recover(env) - - # Success, we added a box! - env[:ui].success( - I18n.t("vagrant.actions.box.add.added", name: box_added.name, provider: box_added.provider)) - # Persists URL used on download and the time it was added write_extra_info(box_added, download_url) @@ -269,72 +172,6 @@ module Vagrant @app.call(env) end - def recover(env) - if @temp_path && File.exist?(@temp_path) && !@download_interrupted - File.unlink(@temp_path) - end - end - - def download_box_url(url, env) - temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url)) - @logger.info("Downloading box: #{url} => #{temp_path}") - - if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i - @logger.info("URL is a file or protocol not found and assuming file.") - file_path = File.expand_path(url) - file_path = Util::Platform.cygwin_windows_path(file_path) - url = "file:#{file_path}" - end - - downloader_options = {} - downloader_options[:ca_cert] = env[:box_download_ca_cert] - downloader_options[:continue] = true - downloader_options[:insecure] = env[:box_download_insecure] - downloader_options[:ui] = env[:ui] - downloader_options[:client_cert] = env[:box_client_cert] - - # If the temporary path exists, verify it is not too old. If its - # too old, delete it first because the data may have changed. - if temp_path.file? - delete = false - if env[:box_clean] - @logger.info("Cleaning existing temp box file.") - delete = true - elsif temp_path.mtime.to_i < (Time.now.to_i - 6 * 60 * 60) - @logger.info("Existing temp file is too old. Removing.") - delete = true - end - - temp_path.unlink if delete - end - - # Download the box to a temporary path. We store the temporary - # path as an instance variable so that the `#recover` method can - # access it. - env[:ui].info(I18n.t( - "vagrant.actions.box.download.downloading", - url: url)) - if temp_path.file? - env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) - end - - begin - downloader = Util::Downloader.new(url, temp_path, downloader_options) - downloader.download! - rescue Errors::DownloaderInterrupted - # The downloader was interrupted, so just return, because that - # means we were interrupted as well. - @download_interrupted = true - env[:ui].info(I18n.t("vagrant.actions.box.download.interrupted")) - rescue Errors::DownloaderError - # The download failed for some reason, clean out the temp path - temp_path.unlink if temp_path.file? - raise - end - - temp_path - end - def write_extra_info(box_added, url) info = {'url' => url, 'downloaded_at' => Time.now.utc} box_added.directory.join('info.json').open("w+") do |f| @@ -345,6 +182,61 @@ module Vagrant protected + # Shared helper to add a box once you know various details + # about it. Shared between adding via metadata or by direct. + # + # @param [String] url + # @param [String] name + # @param [String] version + # @param [String] provider + # @param [Hash] env + # @return [Box] + def box_add(url, name, version, provider, env, **opts) + env[:ui].output(I18n.t( + "vagrant.box_add_with_version", + name: name, + version: version, + provider: provider)) + + # Verify the box we're adding doesn't already exist + if provider && !env[:box_force] + box = env[:box_collection].find( + name, provider, version) + if box + raise Errors::BoxAlreadyExists, + name: name, + provider: provider, + version: version + end + end + + # Now we have a URL, we have to download this URL. + box = nil + begin + box_url = download(url, env) + + # Add the box! + box = env[:box_collection].add( + box_url, name, version, + force: env[:box_force], + providers: provider) + ensure + # Make sure we delete the temporary file after we add it, + # unless we were interrupted, in which case we keep it around + # so we can resume the download later. + if !@download_interrupted + @logger.debug("Deleting temporary box: #{box_url}") + box_url.delete + end + end + + env[:ui].success(I18n.t( + "vagrant.box_added", + name: box.name, provider: box.provider)) + + box + end + # Returns the download options for the download. # # @return [Hash] diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index 3439b159a..eead8ff83 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -54,6 +54,42 @@ describe Vagrant::Action::Builtin::BoxAdd do subject.call(env) end + + it "raises an error if the box already exists" do + box_path = iso_env.box2_file(:virtualbox) + + env[:box_name] = "foo" + env[:box_url] = box_path.to_s + env[:box_provider] = "virtualbox" + + box_collection.should_receive(:find).with( + "foo", ["virtualbox"], "0").and_return(box) + box_collection.should_receive(:add).never + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxAlreadyExists) + end + + it "force adds if exists and specified" do + box_path = iso_env.box2_file(:virtualbox) + + env[:box_force] = true + env[:box_name] = "foo" + env[:box_url] = box_path.to_s + env[:box_provider] = "virtualbox" + + box_collection.stub(find: box) + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo") + expect(version).to eq("0") + true + end.and_return(box) + app.should_receive(:call).with(env).once + + subject.call(env) + end end context "with box metadata" do From 03b22ab9a1149581fb3adf0d13d59420a0416010 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 15:48:24 -0800 Subject: [PATCH 26/90] core: add the box_added to the middleware --- lib/vagrant/action/builtin/box_add.rb | 19 +++---------------- .../vagrant/action/builtin/box_add_test.rb | 12 ++++++++++++ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 7168347cd..4d591e7a9 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -161,22 +161,6 @@ module Vagrant expected: checksum end end - - # Persists URL used on download and the time it was added - write_extra_info(box_added, download_url) - - # Passes on the newly added box to the rest of the middleware chain - env[:box_added] = box_added - - # Carry on! - @app.call(env) - end - - def write_extra_info(box_added, url) - info = {'url' => url, 'downloaded_at' => Time.now.utc} - box_added.directory.join('info.json').open("w+") do |f| - f.write(JSON.dump(info)) - end end =end @@ -234,6 +218,9 @@ module Vagrant "vagrant.box_added", name: box.name, provider: box.provider)) + # Store the added box in the env for future middleware + env[:box_added] = box + box end diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index eead8ff83..cfe3ecedb 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -172,6 +172,8 @@ describe Vagrant::Action::Builtin::BoxAdd do app.should_receive(:call).with(env) subject.call(env) + + expect(env[:box_added]).to equal(box) end it "adds the latest version of a box with the specified provider, even if not latest" do @@ -218,6 +220,8 @@ describe Vagrant::Action::Builtin::BoxAdd do app.should_receive(:call).with(env) subject.call(env) + + expect(env[:box_added]).to equal(box) end it "adds the constrained version of a box with the only provider" do @@ -255,6 +259,8 @@ describe Vagrant::Action::Builtin::BoxAdd do app.should_receive(:call).with(env) subject.call(env) + + expect(env[:box_added]).to equal(box) end it "adds the constrained version of a box with the specified provider" do @@ -297,6 +303,8 @@ describe Vagrant::Action::Builtin::BoxAdd do app.should_receive(:call).with(env) subject.call(env) + + expect(env[:box_added]).to equal(box) end it "adds the latest version of a box with any specified provider" do @@ -342,6 +350,8 @@ describe Vagrant::Action::Builtin::BoxAdd do app.should_receive(:call).with(env) subject.call(env) + + expect(env[:box_added]).to equal(box) end it "raises an exception if no matching version" do @@ -483,6 +493,8 @@ describe Vagrant::Action::Builtin::BoxAdd do app.should_receive(:call).with(env) subject.call(env) + + expect(env[:box_added]).to equal(box) end end end From 09e86662962d854f22de5205111d0ad4b724713e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 16:43:03 -0800 Subject: [PATCH 27/90] core: BoxAdd now works with HTTP URLs --- lib/vagrant/action/builtin/box_add.rb | 15 ++- lib/vagrant/util/downloader.rb | 111 +++++++++++------- .../vagrant/action/builtin/box_add_test.rb | 84 +++++++++++++ 3 files changed, 164 insertions(+), 46 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 4d591e7a9..97ed3d26d 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -57,12 +57,14 @@ module Vagrant version = env[:box_version] metadata = nil - if File.file?(url) - # TODO: What if file isn't valid JSON - # TODO: What if URL is in the "file:" format - File.open(url) do |f| + begin + metadata_path = download(url, env) + + File.open(metadata_path) do |f| metadata = BoxMetadata.new(f) end + ensure + metadata_path.delete if metadata_path.file? end metadata_version = metadata.version( @@ -316,7 +318,10 @@ module Vagrant end # TODO: do the HEAD request - true + output = d.head + match = output.scan(/^Content-Type: (.+?)$/).last + return false if !match + match.last.chomp == "application/json" end end end diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index 88a2e7d98..64028b18e 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -38,30 +38,8 @@ module Vagrant # If this method returns without an exception, the download # succeeded. An exception will be raised if the download failed. def download! - # Build the list of parameters to execute with cURL - options = [ - "--fail", - "--location", - "--max-redirs", "10", - "--user-agent", USER_AGENT, - "--output", @destination, - ] - - options += ["--cacert", @ca_cert] if @ca_cert - options += ["--continue-at", "-"] if @continue - options << "--insecure" if @insecure - options << "--cert" << @client_cert if @client_cert - options << @source - - # Specify some options for the subprocess - subprocess_options = {} - - # If we're in Vagrant, then we use the packaged CA bundle - if Vagrant.in_installer? - subprocess_options[:env] ||= {} - subprocess_options[:env]["CURL_CA_BUNDLE"] = - File.expand_path("cacert.pem", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]) - end + options, subprocess_options = self.options + options += ["--output", @destination] # This variable can contain the proc that'll be sent to # the subprocess execute. @@ -118,7 +96,42 @@ module Vagrant end end - # Add the subprocess options onto the options we'll execute with + @logger.info("Downloader starting download: ") + @logger.info(" -- Source: #{@source}") + @logger.info(" -- Destination: #{@destination}") + + begin + execute_curl(options, subprocess_options, &data_proc) + ensure + # If we're outputting to the UI, clear the output to + # avoid lingering progress meters. + if @ui + @ui.clear_line + + # Windows doesn't clear properly for some reason, so we just + # output one more newline. + @ui.info("") if Platform.windows? + end + end + + # Everything succeeded + true + end + + # Does a HEAD request of the URL and returns the output. + def head + options, subprocess_options = self.options + options.unshift("-I") + + @logger.info("HEAD: #{@source}") + result = execute_curl(options, subprocess_options) + result.stdout + end + + protected + + def execute_curl(options, subprocess_options, &data_proc) + options = options.dup options << subprocess_options # Create the callback that is called if we are interrupted @@ -128,10 +141,6 @@ module Vagrant interrupted = true end - @logger.info("Downloader starting download: ") - @logger.info(" -- Source: #{@source}") - @logger.info(" -- Destination: #{@destination}") - # Execute! result = Busy.busy(int_callback) do Subprocess.execute("curl", *options, &data_proc) @@ -140,16 +149,6 @@ module Vagrant # If the download was interrupted, then raise a specific error raise Errors::DownloaderInterrupted if interrupted - # If we're outputting to the UI, clear the output to - # avoid lingering progress meters. - if @ui - @ui.clear_line - - # Windows doesn't clear properly for some reason, so we just - # output one more newline. - @ui.info("") if Platform.windows? - end - # If it didn't exit successfully, we need to parse the data and # show an error message. if result.exit_code != 0 @@ -159,8 +158,38 @@ module Vagrant raise Errors::DownloaderError, :message => parts[1].chomp end - # Everything succeeded - true + result + end + + # Returns the varoius cURL and subprocess options. + # + # @return [Array] + def options + # Build the list of parameters to execute with cURL + options = [ + "--fail", + "--location", + "--max-redirs", "10", + "--user-agent", USER_AGENT, + ] + + options += ["--cacert", @ca_cert] if @ca_cert + options += ["--continue-at", "-"] if @continue + options << "--insecure" if @insecure + options << "--cert" << @client_cert if @client_cert + options << @source + + # Specify some options for the subprocess + subprocess_options = {} + + # If we're in Vagrant, then we use the packaged CA bundle + if Vagrant.in_installer? + subprocess_options[:env] ||= {} + subprocess_options[:env]["CURL_CA_BUNDLE"] = + File.expand_path("cacert.pem", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]) + end + + return [options, subprocess_options] end end end diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index cfe3ecedb..e900dff87 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -2,6 +2,7 @@ require "digest/sha1" require "pathname" require "tempfile" require "tmpdir" +require "webrick" require File.expand_path("../../../../base", __FILE__) @@ -32,6 +33,27 @@ describe Vagrant::Action::Builtin::BoxAdd do FileChecksum.new(path, Digest::SHA1).checksum end + def with_web_server(path) + tf = Tempfile.new("vagrant") + tf.close + + mime_types = WEBrick::HTTPUtils::DefaultMimeTypes + mime_types.store "json", "application/json" + + port = 3838 + server = WEBrick::HTTPServer.new( + AccessLog: [], + Logger: WEBrick::Log.new(tf.path, 7), + Port: port, + DocumentRoot: path.dirname.to_s, + MimeTypes: mime_types) + thr = Thread.new { server.start } + yield port + ensure + server.shutdown rescue nil + thr.join rescue nil + end + before do box_collection.stub(find: nil) end @@ -55,6 +77,25 @@ describe Vagrant::Action::Builtin::BoxAdd do subject.call(env) end + it "adds from HTTP URL" do + box_path = iso_env.box2_file(:virtualbox) + with_web_server(box_path) do |port| + env[:box_name] = "foo" + env[:box_url] = "http://127.0.0.1:#{port}/#{box_path.basename}" + + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo") + expect(version).to eq("0") + true + end.and_return(box) + + app.should_receive(:call).with(env) + + subject.call(env) + end + end + it "raises an error if the box already exists" do box_path = iso_env.box2_file(:virtualbox) @@ -93,6 +134,49 @@ describe Vagrant::Action::Builtin::BoxAdd do end context "with box metadata" do + it "adds from HTTP URL" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new(["vagrant", ".json"]).tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + + md_path = Pathname.new(tf.path) + with_web_server(md_path) do |port| + env[:box_url] = "http://127.0.0.1:#{port}/#{md_path.basename}" + + box_collection.should_receive(:add).with do |path, name, version| + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + expect(checksum(path)).to eq(checksum(box_path)) + true + end.and_return(box) + + app.should_receive(:call).with(env) + + subject.call(env) + end + end + it "adds the latest version of a box with only one provider" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new("vagrant").tap do |f| From 8f0f0506d669560f4d7b00957f9bac2d3f26f972 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 16:46:46 -0800 Subject: [PATCH 28/90] core: BoxAdd requires name if old-style box --- lib/vagrant/action/builtin/box_add.rb | 5 ++++- lib/vagrant/errors.rb | 4 ++++ templates/locales/en.yml | 18 +++++++++++------- .../vagrant/action/builtin/box_add_test.rb | 12 ++++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 97ed3d26d..11a42273b 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -35,8 +35,11 @@ module Vagrant # Adds a box file directly (no metadata component, versioning, # etc.) def add_direct(env) - # TODO: what if we have no name name = env[:box_name] + if !name || name == "" + raise Errors::BoxAddNameRequired + end + url = env[:box_url] provider = env[:box_provider] provider = Array(provider) if provider diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 7453d3c72..61c2c4ec2 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -120,6 +120,10 @@ module Vagrant error_key(:batch_multi_error) end + class BoxAddNameRequired < VagrantError + error_key(:box_add_name_required) + end + class BoxAddNoMatchingProvider < VagrantError error_key(:box_add_no_matching_provider) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index d8398954d..7ee1a2c7c 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -4,13 +4,6 @@ en: Machine booted and ready! boot_waiting: |- Waiting for machine to boot. This may take a few minutes... - box_add_exists: |- - The box you're attempting to add already exists. Remove it before - adding it again or add it with the `--force` flag. - - Name: %{name} - Provider: %{provider} - Version: %{version} box_add_with_version: |- Adding box '%{name}' (v%{version}) for '%{provider}' provider... box_added: |- @@ -263,6 +256,17 @@ en: If the box appears to be booting properly, you may want to increase the timeout ("config.vm.boot_timeout") value. + box_add_exists: |- + The box you're attempting to add already exists. Remove it before + adding it again or add it with the `--force` flag. + + Name: %{name} + Provider: %{provider} + Version: %{version} + box_add_name_required: |- + A name is required when adding a box file directly. Please pass + the `--name` parameter to `vagrant box add`. See + `vagrant box add -h` for more help. box_checksum_invalid_type: |- The specified checksum type is not supported by Vagrant: %{type}. Vagrant supports the following checksum types: diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index e900dff87..a2391132c 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -96,6 +96,18 @@ describe Vagrant::Action::Builtin::BoxAdd do end end + it "raises an error if no name is given" do + box_path = iso_env.box2_file(:virtualbox) + + env[:box_url] = box_path.to_s + + box_collection.should_receive(:add).never + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxAddNameRequired) + end + it "raises an error if the box already exists" do box_path = iso_env.box2_file(:virtualbox) From c31feb0e94ba03b818b3307bf6e558da9b7edcda Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 16:52:03 -0800 Subject: [PATCH 29/90] core: fix missing interpolation for boxcollection --- lib/vagrant/box_collection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index cc57456f3..a108d84d3 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -87,7 +87,7 @@ module Vagrant "Box already exists, can't add: #{name} v#{version} #{box_formats.join(", ")}") raise Errors::BoxAlreadyExists, name: name, - providers: box_formats.join(", "), + provider: box_formats.join(", "), version: version end From 05eeefbe36e28b672dd9720002d3c96d3f0c6e67 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 16:56:11 -0800 Subject: [PATCH 30/90] core: Fix Downloader tests --- lib/vagrant/util/downloader.rb | 3 ++- test/unit/vagrant/util/downloader_test.rb | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index 64028b18e..3858936db 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -40,6 +40,7 @@ module Vagrant def download! options, subprocess_options = self.options options += ["--output", @destination] + options << @source # This variable can contain the proc that'll be sent to # the subprocess execute. @@ -122,6 +123,7 @@ module Vagrant def head options, subprocess_options = self.options options.unshift("-I") + options << @source @logger.info("HEAD: #{@source}") result = execute_curl(options, subprocess_options) @@ -177,7 +179,6 @@ module Vagrant options += ["--continue-at", "-"] if @continue options << "--insecure" if @insecure options << "--cert" << @client_cert if @client_cert - options << @source # Specify some options for the subprocess subprocess_options = {} diff --git a/test/unit/vagrant/util/downloader_test.rb b/test/unit/vagrant/util/downloader_test.rb index 1f73d57b8..6be2ee7f9 100644 --- a/test/unit/vagrant/util/downloader_test.rb +++ b/test/unit/vagrant/util/downloader_test.rb @@ -54,4 +54,22 @@ describe Vagrant::Util::Downloader do pending "tests for a UI" end end + + describe "#head" do + let(:curl_options) { + ["--fail", "--location", "--max-redirs", "10", "--user-agent", described_class::USER_AGENT, source, {}] + } + + it "returns the output" do + subprocess_result.stub(stdout: "foo") + + options = curl_options.dup + options.unshift("-I") + + Vagrant::Util::Subprocess.should_receive(:execute). + with("curl", *options).and_return(subprocess_result) + + expect(subject.head).to eq("foo") + end + end end From 0fceed6ff40b23880b7276283f1152c14b1c2c93 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 17:02:29 -0800 Subject: [PATCH 31/90] core: BoxAdd uses the proper path on Unix --- lib/vagrant/action/builtin/box_add.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 11a42273b..246797532 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -310,8 +310,11 @@ module Vagrant # by trying to parse JSON! uri = URI.parse(d.source) if uri.scheme == "file" + url = uri.path + url = uri.opaque if Util::Platform.windows? + begin - File.open(uri.opaque, "r") do |f| + File.open(url, "r") do |f| BoxMetadata.new(f) end return true From acc57a3c18e961ed5989e6261f01f6851ce570e6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 17:08:28 -0800 Subject: [PATCH 32/90] core: clarify output for boxadd error message --- lib/vagrant/action/builtin/box_add.rb | 2 ++ plugins/commands/box/command/add.rb | 2 +- templates/locales/en.yml | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 246797532..5b8be6336 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -76,11 +76,13 @@ module Vagrant if !provider raise Errors::BoxAddNoMatchingVersion, constraints: version || ">= 0", + name: metadata.name, url: url, versions: metadata.versions.join(", ") else # TODO: show supported providers raise Errors::BoxAddNoMatchingProvider, + name: metadata.name, requested: provider, url: url end diff --git a/plugins/commands/box/command/add.rb b/plugins/commands/box/command/add.rb index 3d32596bf..c94e10013 100644 --- a/plugins/commands/box/command/add.rb +++ b/plugins/commands/box/command/add.rb @@ -36,7 +36,7 @@ module VagrantPlugins options[:provider] = p end - o.on("--version VALUE", String, "Constrain version of the added box") do |v| + o.on("--box-version VALUE", String, "Constrain version of the added box") do |v| options[:version] = v end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 7ee1a2c7c..d42bfbbf1 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -221,14 +221,16 @@ en: provider. Double-check your requested provider to verify you didn't simply misspell it. - Box: %{url} + Name: %{name} + Address: %{url} Requested provider: %{requested} box_add_no_matching_version: |- The box you're attempting to add has no available version that matches the constraints you requested. Please double-check your settings. - Box: %{url} + Box: %{name} + Address: %{url} Constraints: %{constraints} Available versions: %{versions} boot_bad_state: |- From e38ce34e563b9a3f7b4eda067ecc2a70df4e7c34 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 17:23:59 -0800 Subject: [PATCH 33/90] core: make the BoxAdd UI look a bit better --- lib/vagrant/action/builtin/box_add.rb | 30 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 5b8be6336..513cf6886 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -61,13 +61,13 @@ module Vagrant metadata = nil begin - metadata_path = download(url, env) + metadata_path = download(url, env, ui: false) File.open(metadata_path) do |f| metadata = BoxMetadata.new(f) end ensure - metadata_path.delete if metadata_path.file? + metadata_path.delete if metadata_path && metadata_path.file? end metadata_version = metadata.version( @@ -234,7 +234,9 @@ module Vagrant # Returns the download options for the download. # # @return [Hash] - def downloader(url, env) + def downloader(url, env, **opts) + opts[:ui] = true if !opts.has_key?(:ui) + temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url)) @logger.info("Downloading box: #{url} => #{temp_path}") @@ -264,23 +266,27 @@ module Vagrant downloader_options[:ca_cert] = env[:box_download_ca_cert] downloader_options[:continue] = true downloader_options[:insecure] = env[:box_download_insecure] - downloader_options[:ui] = env[:ui] downloader_options[:client_cert] = env[:box_client_cert] + downloader_options[:ui] = env[:ui] if opts[:ui] Util::Downloader.new(url, temp_path, downloader_options) end - def download(url, env) - d = downloader(url, env) + def download(url, env, **opts) + opts[:ui] = true if !opts.has_key?(:ui) + + d = downloader(url, env, **opts) # Download the box to a temporary path. We store the temporary # path as an instance variable so that the `#recover` method can # access it. - env[:ui].info(I18n.t( - "vagrant.actions.box.download.downloading", - url: url)) - if File.file?(d.destination) - env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) + if opts[:ui] + env[:ui].info(I18n.t( + "vagrant.actions.box.download.downloading", + url: url)) + if File.file?(d.destination) + env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) + end end begin @@ -305,7 +311,7 @@ module Vagrant # @param [String] url # @return [Boolean] true if metadata def metadata_url?(url, env) - d = downloader(url, env) + d = downloader(url, env, ui: false) # If we're downloading a file, cURL just returns no # content-type (makes sense), so we just test if it is JSON From f9c9559320147589bcd6ed4f21b22bce1df20e01 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 19:29:45 -0800 Subject: [PATCH 34/90] core: ask what provider to use if multiple providers --- lib/vagrant/action/builtin/box_add.rb | 23 +++++++++- templates/locales/en.yml | 10 +++++ .../vagrant/action/builtin/box_add_test.rb | 45 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 513cf6886..138f81c75 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -101,6 +101,28 @@ module Vagrant # provider. metadata_provider = metadata_version.provider( metadata_version.providers.first) + else + providers = metadata_version.providers.sort + + choice = 0 + options = providers.map do |p| + choice += 1 + "#{choice}) #{p}" + end.join("\n") + + # We have more than one provider, ask the user what they want + choice = env[:ui].ask(I18n.t( + "vagrant.box_add_choose_provider", + options: options)) + choice = choice.to_i if choice + while !choice || choice <= 0 || choice > providers.length + choice = env[:ui].ask(I18n.t( + "vagrant.box_add_choose_provider_again")) + choice = choice.to_i if choice + end + + metadata_provider = metadata_version.provider( + providers[choice-1]) end box_add( @@ -331,7 +353,6 @@ module Vagrant end end - # TODO: do the HEAD request output = d.head match = output.scan(/^Content-Type: (.+?)$/).last return false if !match diff --git a/templates/locales/en.yml b/templates/locales/en.yml index d42bfbbf1..4dd04d08f 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -4,6 +4,16 @@ en: Machine booted and ready! boot_waiting: |- Waiting for machine to boot. This may take a few minutes... + box_add_choose_provider: |- + This box can work with multiple providers! The providers that it + can work with are listed below. Please review the list and choose + the provider you will be working with. + + %{options} + + Enter your choice: + box_add_choose_provider_again: |- + Invalid choice. Try again: box_add_with_version: |- Adding box '%{name}' (v%{version}) for '%{provider}' provider... box_added: |- diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index a2391132c..0940bd0b7 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -450,6 +450,51 @@ describe Vagrant::Action::Builtin::BoxAdd do expect(env[:box_added]).to equal(box) end + it "asks the user what provider if multiple options" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + }, + { + "name": "vmware", + "url": "#{iso_env.box2_file(:vmware)}" + } + ] + } + ] + } + RAW + f.close + end + + env[:box_url] = tf.path + + env[:ui].should_receive(:ask).and_return("1") + + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo/bar") + expect(version).to eq("0.7") + true + end.and_return(box) + + app.should_receive(:call).with(env) + + subject.call(env) + end + it "raises an exception if no matching version" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new("vagrant").tap do |f| From 8ef13b037c82662bf2459ca9cb63a477eab07204 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 19:30:30 -0800 Subject: [PATCH 35/90] core: add a space to the question; --- lib/vagrant/action/builtin/box_add.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 138f81c75..0553a3dd0 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -113,11 +113,11 @@ module Vagrant # We have more than one provider, ask the user what they want choice = env[:ui].ask(I18n.t( "vagrant.box_add_choose_provider", - options: options)) + options: options) + " ") choice = choice.to_i if choice while !choice || choice <= 0 || choice > providers.length choice = env[:ui].ask(I18n.t( - "vagrant.box_add_choose_provider_again")) + "vagrant.box_add_choose_provider_again") + " ") choice = choice.to_i if choice end From 29da74870200bc7e26e40b8a5d8f90e6eb063bf8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 20:51:00 -0800 Subject: [PATCH 36/90] commands/box/remove: update for new syntax --- plugins/commands/box/command/remove.rb | 46 +++++++------ templates/locales/en.yml | 11 ---- .../commands/box/command/remove_test.rb | 66 +++++++++++++++++++ 3 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 test/unit/plugins/commands/box/command/remove_test.rb diff --git a/plugins/commands/box/command/remove.rb b/plugins/commands/box/command/remove.rb index d58f5bcec..95fc5aabe 100644 --- a/plugins/commands/box/command/remove.rb +++ b/plugins/commands/box/command/remove.rb @@ -5,38 +5,42 @@ module VagrantPlugins module Command class Remove < Vagrant.plugin("2", :command) def execute + options = {} opts = OptionParser.new do |o| - o.banner = "Usage: vagrant box remove " + o.banner = "Usage: vagrant box remove " + o.separator "" + + o.on("--provider VALUE", String, + "The specific provider type for the box to remove.") do |p| + options[:provider] = p + end + + o.on("--version VALUE", String, + "The specific version of the box to remove.") do |v| + options[:version] = v + end end # Parse the options argv = parse_options(opts) return if !argv - raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length < 1 + if argv.empty? || argv.length > 2 + raise Vagrant::Errors::CLIInvalidUsage, + help: opts.help.chomp + end - if !argv[1] - # Try to automatically determine the provider. - providers = [] - @env.boxes.all.each do |name, provider| - if name == argv[0] - providers << provider - end - end - - if providers.length > 1 - @env.ui.error( - I18n.t("vagrant.commands.box.remove_must_specify_provider", - name: argv[0], - providers: providers.join(", "))) - return 1 - end - - argv[1] = providers[0] || "" + if argv.length == 2 + # @deprecated + @env.ui.warn("WARNING: The second argument to `vagrant box remove`") + @env.ui.warn("is deprecated. Please use the --provider flag. This") + @env.ui.warn("feature will stop working in the next version.") + options[:provider] = argv[1] end @env.action_runner.run(Vagrant::Action.action_box_remove, { :box_name => argv[0], - :box_provider => argv[1] + :box_provider => options[:provider], + :box_version => options[:version], }) # Success, exit status 0 diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 4dd04d08f..47d80ead7 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -974,17 +974,6 @@ en: vm_not_created: "VM not created. Moving on..." vm_not_running: "VM is not currently running. Please, first bring it up with `vagrant up` then run this command." box: - remove_must_specify_provider: |- - Multiple providers were found for the box '%{name}'. Please specify - the specific provider for the box you want to remove. The list of - providers backing this box is: - - '%{providers}' - - To remove the box for a specific provider, run the following command, - filling in PROVIDER with one of the providers above: - - vagrant box remove '%{name}' PROVIDER no_installed_boxes: "There are no installed boxes! Use `vagrant box add` to add some." removing: |- Removing box '%{name}' with provider '%{provider}'... diff --git a/test/unit/plugins/commands/box/command/remove_test.rb b/test/unit/plugins/commands/box/command/remove_test.rb new file mode 100644 index 000000000..a13fd30fb --- /dev/null +++ b/test/unit/plugins/commands/box/command/remove_test.rb @@ -0,0 +1,66 @@ +require File.expand_path("../../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/box/command/remove") + +describe VagrantPlugins::CommandBox::Command::Remove do + include_context "unit" + + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + subject { described_class.new(argv, iso_env) } + + let(:action_runner) { double("action_runner") } + + before do + iso_env.stub(action_runner: action_runner) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with one argument" do + let(:argv) { ["foo"] } + + it "invokes the action runner" do + action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_name]).to eq("foo") + true + end + + subject.execute + end + end + + context "with two arguments" do + let(:argv) { ["foo", "bar"] } + + it "uses the 2nd arg as a provider" do + action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_name]).to eq("foo") + expect(opts[:box_provider]).to eq("bar") + true + end + + subject.execute + end + end + + context "with more than two arguments" do + let(:argv) { ["one", "two", "three"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end +end From c7fc9d1d468b71375027f567869e585da8ec2a4c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 21:44:11 -0800 Subject: [PATCH 37/90] core: BoxRemove works, tests --- lib/vagrant/action/builtin/box_remove.rb | 63 ++++++++-- lib/vagrant/errors.rb | 20 +++- templates/locales/en.yml | 23 +++- .../vagrant/action/builtin/box_remove_test.rb | 108 ++++++++++++++++++ 4 files changed, 199 insertions(+), 15 deletions(-) create mode 100644 test/unit/vagrant/action/builtin/box_remove_test.rb diff --git a/lib/vagrant/action/builtin/box_remove.rb b/lib/vagrant/action/builtin/box_remove.rb index 93a1dd355..faa1d685c 100644 --- a/lib/vagrant/action/builtin/box_remove.rb +++ b/lib/vagrant/action/builtin/box_remove.rb @@ -12,24 +12,67 @@ module Vagrant def call(env) box_name = env[:box_name] - box_provider = env[:box_provider].to_sym + box_provider = env[:box_provider] + box_provider = box_provider.to_sym if box_provider + box_version = env[:box_version] - box = nil - begin - box = env[:box_collection].find(box_name, box_provider) - rescue Vagrant::Errors::BoxUpgradeRequired - env[:box_collection].upgrade(box_name) - retry + boxes = {} + env[:box_collection].all.each do |n, v, p| + boxes[n] ||= {} + boxes[n][p] ||= [] + boxes[n][p] << v end - raise Vagrant::Errors::BoxNotFound, :name => box_name, :provider => box_provider if !box + all_box = boxes[box_name] + if !all_box + raise Errors::BoxRemoveNotFound, name: box_name + end + + all_versions = nil + if !box_provider + if all_box.length == 1 + # There is only one provider, just use that. + all_versions = all_box.values.first + box_provider = all_box.keys.first + else + raise Errors::BoxRemoveMultiProvider, + name: box_name, + providers: all_box.keys.map(&:to_s).sort.join(", ") + end + else + all_versions = all_box[box_provider] + if !all_versions + raise Errors::BoxRemoveProviderNotFound, + name: box_name, + provider: box_provider.to_s, + providers: all_box.keys.map(&:to_s).sort.join(", ") + end + end + + if !box_version + if all_versions.length == 1 + # There is only one version, just use that. + box_version = all_versions.first + else + # There are multiple versions, we can't choose. + raise Errors::BoxRemoveMultiVersion, + name: box_name, + provider: box_provider.to_s, + versions: all_versions.join(", ") + end + end + + box = env[:box_collection].find( + box_name, box_provider, box_version) + env[:ui].info(I18n.t("vagrant.commands.box.removing", - :name => box_name, - :provider => box_provider)) + :name => box.name, + :provider => box.provider)) box.destroy! # Passes on the removed box to the rest of the middleware chain env[:box_removed] = box + @app.call(env) end end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 61c2c4ec2..1984c5efd 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -160,14 +160,26 @@ module Vagrant error_key(:box_metadata_malformed) end - class BoxNotFound < VagrantError - error_key(:box_not_found) - end - class BoxProviderDoesntMatch < VagrantError error_key(:box_provider_doesnt_match) end + class BoxRemoveNotFound < VagrantError + error_key(:box_remove_not_found) + end + + class BoxRemoveProviderNotFound < VagrantError + error_key(:box_remove_provider_not_found) + end + + class BoxRemoveMultiProvider < VagrantError + error_key(:box_remove_multi_provider) + end + + class BoxRemoveMultiVersion < VagrantError + error_key(:box_remove_multi_version) + end + class BoxUnpackageFailure < VagrantError error_key(:untar_failure, "vagrant.actions.box.unpackage") end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 47d80ead7..611367c29 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -315,12 +315,33 @@ en: that this issue can be fixed. %{error} - box_not_found: Box '%{name}' with '%{provider}' provider could not be found. box_provider_doesnt_match: |- The box you attempted to add doesn't match the provider you specified. Provider expected: %{expected} Provider of box: %{actual} + box_remove_multi_provider: |- + You requested to remove the box '%{name}'. This box has + multiple providers. You must explicitly select a single + provider to remove with `--provider`. + + Available providers: %{providers} + box_remove_multi_version: |- + You requested to remove the box '%{name}' with provider + '%{provider}'. This box has multiple versions. You must + explicitly specify which version you want to remove with + the `--box-version` flag. + + Versions: %{versions} + box_remove_not_found: |- + The box you requested to be removed could not be found. No + boxes named '%{name}' could be found. + box_remove_provider_not_found: |- + You requested to remove the box '%{name}' with provider + '%{provider}'. The box '%{name}' exists but not with + the provider specified. Please double-check and try again. + + The providers for this are: %{providers} box_upgrade_required: |- 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 diff --git a/test/unit/vagrant/action/builtin/box_remove_test.rb b/test/unit/vagrant/action/builtin/box_remove_test.rb new file mode 100644 index 000000000..19a15b3f3 --- /dev/null +++ b/test/unit/vagrant/action/builtin/box_remove_test.rb @@ -0,0 +1,108 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Action::Builtin::BoxRemove do + include_context "unit" + + let(:app) { lambda { |env| } } + let(:env) { { + box_collection: box_collection, + ui: Vagrant::UI::Silent.new, + } } + + subject { described_class.new(app, env) } + + let(:box_collection) { double("box_collection") } + let(:iso_env) { isolated_environment } + + let(:box) do + box_dir = iso_env.box3("foo", "1.0", :virtualbox) + Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) + end + + it "deletes the box if it is the only option" do + box_collection.stub(all: [["foo", "1.0", :virtualbox]]) + + env[:box_name] = "foo" + + box_collection.should_receive(:find).with( + "foo", :virtualbox, "1.0").and_return(box) + box.should_receive(:destroy!).once + app.should_receive(:call).with(env).once + + subject.call(env) + + expect(env[:box_removed]).to equal(box) + end + + it "deletes the box with the specified provider if given" do + box_collection.stub( + all: [ + ["foo", "1.0", :virtualbox], + ["foo", "1.0", :vmware], + ]) + + env[:box_name] = "foo" + env[:box_provider] = "virtualbox" + + box_collection.should_receive(:find).with( + "foo", :virtualbox, "1.0").and_return(box) + box.should_receive(:destroy!).once + app.should_receive(:call).with(env).once + + subject.call(env) + + expect(env[:box_removed]).to equal(box) + end + + it "errors if the box doesn't exist" do + box_collection.stub(all: []) + + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxRemoveNotFound) + end + + it "errors if the specified provider doesn't exist" do + env[:box_name] = "foo" + env[:box_provider] = "bar" + + box_collection.stub(all: [["foo", "1.0", :virtualbox]]) + + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxRemoveProviderNotFound) + end + + it "errors if there are multiple providers" do + env[:box_name] = "foo" + + box_collection.stub( + all: [ + ["foo", "1.0", :virtualbox], + ["foo", "1.0", :vmware], + ]) + + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxRemoveMultiProvider) + end + + it "errors if the specified provider has multiple versions" do + env[:box_name] = "foo" + env[:box_provider] = "virtualbox" + + box_collection.stub( + all: [ + ["foo", "1.0", :virtualbox], + ["foo", "1.1", :virtualbox], + ]) + + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxRemoveMultiVersion) + end +end From c1d5c8f33cb544f09d4834344fcc032a80739376 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 21:46:21 -0800 Subject: [PATCH 38/90] commands/box: remove has --box-version flag --- plugins/commands/box/command/remove.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/commands/box/command/remove.rb b/plugins/commands/box/command/remove.rb index 95fc5aabe..0bc8dcf52 100644 --- a/plugins/commands/box/command/remove.rb +++ b/plugins/commands/box/command/remove.rb @@ -15,7 +15,7 @@ module VagrantPlugins options[:provider] = p end - o.on("--version VALUE", String, + o.on("--box-version VALUE", String, "The specific version of the box to remove.") do |v| options[:version] = v end From b71cde6b999611e9b346f113b39bf075eefd2e45 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 22:16:20 -0800 Subject: [PATCH 39/90] core: make box add UI much icer --- lib/vagrant/action/builtin/box_add.rb | 2 ++ lib/vagrant/ui.rb | 19 ++++++++++++------- lib/vagrant/util/downloader.rb | 4 ++-- plugins/commands/box/command/add.rb | 1 + templates/locales/en.yml | 2 ++ test/unit/vagrant/ui_test.rb | 18 +++++++++++++++--- 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 0553a3dd0..ab9946c02 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -59,6 +59,8 @@ module Vagrant url = env[:box_url] version = env[:box_version] + env[:ui].output(I18n.t( + "vagrant.box_loading_metadata", name: url)) metadata = nil begin metadata_path = download(url, env, ui: false) diff --git a/lib/vagrant/ui.rb b/lib/vagrant/ui.rb index 9563af974..3654c83f8 100644 --- a/lib/vagrant/ui.rb +++ b/lib/vagrant/ui.rb @@ -209,7 +209,10 @@ module Vagrant class_eval <<-CODE def #{method}(message, *args, **opts) super(message) - opts[:bold] = #{method.inspect} != :detail if !opts.has_key?(:bold) + if !opts.has_key?(:bold) + opts[:bold] = #{method.inspect} != :detail && \ + #{method.inspect} != :ask + end @ui.#{method}(format_message(#{method.inspect}, message, **opts), *args, **opts) end CODE @@ -241,7 +244,8 @@ module Vagrant prefix = "" if !opts.has_key?(:prefix) || opts[:prefix] prefix = OUTPUT_PREFIX - prefix = " " * OUTPUT_PREFIX.length if type == :detail + prefix = " " * OUTPUT_PREFIX.length if \ + type == :detail || type == :ask end # Fast-path if there is no prefix @@ -281,16 +285,17 @@ module Vagrant opts[:color] = :green if type == :success opts[:color] = :yellow if type == :warn - # If there is no color specified, exit early - return message if !opts.has_key?(:color) - # If it is a detail, it is not bold. Every other message type # is bolded. bold = !!opts[:bold] - color = COLORS[opts[:color]] + colorseq = "#{bold ? 1 : 0 }" + if opts[:color] + color = COLORS[opts[:color]] + colorseq += ";#{color}" + end # Color the message and make sure to reset the color at the end - "\033[#{bold ? 1 : 0};#{color}m#{message}\033[0m" + "\033[#{colorseq}m#{message}\033[0m" end end end diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index 3858936db..69f1e8700 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -92,7 +92,7 @@ module Vagrant output = "Progress: #{columns[0]}% (Rate: #{columns[11]}/s, Estimated time remaining: #{columns[10]})" @ui.clear_line - @ui.info(output, :new_line => false) + @ui.detail(output, :new_line => false) end end end @@ -111,7 +111,7 @@ module Vagrant # Windows doesn't clear properly for some reason, so we just # output one more newline. - @ui.info("") if Platform.windows? + @ui.detail("") if Platform.windows? end end diff --git a/plugins/commands/box/command/add.rb b/plugins/commands/box/command/add.rb index c94e10013..976700ce9 100644 --- a/plugins/commands/box/command/add.rb +++ b/plugins/commands/box/command/add.rb @@ -84,6 +84,7 @@ module VagrantPlugins box_download_ca_cert: options[:ca_cert], box_download_client_cert: options[:client_cert], box_download_insecure: options[:insecure], + ui: Vagrant::UI::Prefixed.new(@env.ui, "box"), }) # Success, exit status 0 diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 611367c29..1ef3bd7b1 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -18,6 +18,8 @@ en: Adding box '%{name}' (v%{version}) for '%{provider}' provider... box_added: |- Successfully added box '%{name}' for '%{provider}'! + box_loading_metadata: |- + Loading metadata for box '%{name}' cfengine_bootstrapping: |- Bootstrapping CFEngine with policy server: %{policy_server}... cfengine_bootstrapping_policy_hub: |- diff --git a/test/unit/vagrant/ui_test.rb b/test/unit/vagrant/ui_test.rb index 735d41da8..b8cc122da 100644 --- a/test/unit/vagrant/ui_test.rb +++ b/test/unit/vagrant/ui_test.rb @@ -65,7 +65,7 @@ describe Vagrant::UI::Colored do describe "#detail" do it "colors output nothing by default" do - subject.should_receive(:safe_puts).with("foo", anything) + subject.should_receive(:safe_puts).with("\033[0mfoo\033[0m", anything) subject.detail("foo") end @@ -91,11 +91,16 @@ describe Vagrant::UI::Colored do end describe "#output" do - it "colors output nothing by default" do - subject.should_receive(:safe_puts).with("foo", anything) + it "colors output nothing by default, no bold" do + subject.should_receive(:safe_puts).with("\033[0mfoo\033[0m", anything) subject.output("foo") end + it "bolds output without color if specified" do + subject.should_receive(:safe_puts).with("\033[1mfoo\033[0m", anything) + subject.output("foo", bold: true) + end + it "colors output to color specified in global opts" do subject.opts[:color] = :red @@ -232,6 +237,13 @@ describe Vagrant::UI::Prefixed do subject { described_class.new(ui, prefix) } + describe "#ask" do + it "does not request bolding" do + ui.should_receive(:ask).with(" #{prefix}: foo", bold: false) + subject.ask("foo") + end + end + describe "#detail" do it "prefixes with spaces and the message" do ui.should_receive(:safe_puts).with(" #{prefix}: foo", anything) From 7b8002eba75e424cb6c79eb532041bf15107b8ec Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 22:19:04 -0800 Subject: [PATCH 40/90] core: don't prefix ask statements --- lib/vagrant/action/builtin/box_add.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index ab9946c02..3451bffa8 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -115,11 +115,12 @@ module Vagrant # We have more than one provider, ask the user what they want choice = env[:ui].ask(I18n.t( "vagrant.box_add_choose_provider", - options: options) + " ") + options: options) + " ", prefix: false) choice = choice.to_i if choice while !choice || choice <= 0 || choice > providers.length choice = env[:ui].ask(I18n.t( - "vagrant.box_add_choose_provider_again") + " ") + "vagrant.box_add_choose_provider_again") + " ", + prefix: false) choice = choice.to_i if choice end From f9fe025d100815ba62571d6d58b9654891a25fc7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 22:22:11 -0800 Subject: [PATCH 41/90] core: helper to get the server URL --- lib/vagrant/shared_helpers.rb | 8 ++++++++ test/unit/vagrant/shared_helpers_test.rb | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/vagrant/shared_helpers.rb b/lib/vagrant/shared_helpers.rb index f1385987d..00c4a4bac 100644 --- a/lib/vagrant/shared_helpers.rb +++ b/lib/vagrant/shared_helpers.rb @@ -8,6 +8,14 @@ module Vagrant !ENV["VAGRANT_NO_PLUGINS"] end + # Returns the URL prefix to the server. + # + # @return [String] + def self.server_url + # TODO: default + ENV["VAGRANT_SERVER_URL"] + end + # The source root is the path to the root directory of the Vagrant source. # # @return [Pathname] diff --git a/test/unit/vagrant/shared_helpers_test.rb b/test/unit/vagrant/shared_helpers_test.rb index c11408802..b3561c9a9 100644 --- a/test/unit/vagrant/shared_helpers_test.rb +++ b/test/unit/vagrant/shared_helpers_test.rb @@ -22,6 +22,14 @@ describe Vagrant do end end + describe "#server_url" do + it "is the VAGRANT_SERVER_URL value" do + with_temp_env("VAGRANT_SERVER_URL" => "foo") do + expect(subject.server_url).to eq("foo") + end + end + end + describe "#user_data_path" do around do |example| env = { From 5aba445cac489891fb01488b9ac85f367e902838 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 22:37:13 -0800 Subject: [PATCH 42/90] core: shorthand box adds wor --- lib/vagrant/action/builtin/box_add.rb | 9 ++++ .../vagrant/action/builtin/box_add_test.rb | 47 ++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 3451bffa8..534f6f568 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -23,6 +23,15 @@ module Vagrant @download_interrupted = false url = env[:box_url] + + # If we received a shorthand URL ("mitchellh/precise64"), + # then expand it properly. + uri = URI.parse(url) + if !uri.scheme && !File.file?(url) + url = "#{Vagrant.server_url}/#{url}" + env[:box_url] = url + end + if metadata_url?(url, env) add_from_metadata(env) else diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index 0940bd0b7..def9a1d4e 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -171,7 +171,6 @@ describe Vagrant::Action::Builtin::BoxAdd do f.close end - md_path = Pathname.new(tf.path) with_web_server(md_path) do |port| env[:box_url] = "http://127.0.0.1:#{port}/#{md_path.basename}" @@ -189,6 +188,52 @@ describe Vagrant::Action::Builtin::BoxAdd do end end + it "adds from shorthand path" do + box_path = iso_env.box2_file(:virtualbox) + td = Pathname.new(Dir.mktmpdir) + tf = td.join("mitchellh", "precise64.json") + tf.dirname.mkpath + tf.open("w") do |f| + f.write(<<-RAW) + { + "name": "mitchellh/precise64", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + end + + with_web_server(tf.dirname) do |port| + env[:box_url] = "mitchellh/precise64.json" + + box_collection.should_receive(:add).with do |path, name, version| + expect(name).to eq("mitchellh/precise64") + expect(version).to eq("0.7") + expect(checksum(path)).to eq(checksum(box_path)) + true + end.and_return(box) + + app.should_receive(:call).with(env) + + url = "http://127.0.0.1:#{port}" + with_temp_env("VAGRANT_SERVER_URL" => url) do + subject.call(env) + end + end + end + it "adds the latest version of a box with only one provider" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new("vagrant").tap do |f| From 1bd3275b3b7095674086f6119b5b79fc0893585e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 22:40:12 -0800 Subject: [PATCH 43/90] core: output the expanded URL --- lib/vagrant/action/builtin/box_add.rb | 19 +++++++++++-------- templates/locales/en.yml | 2 ++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 534f6f568..a1e71f18c 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -29,13 +29,12 @@ module Vagrant uri = URI.parse(url) if !uri.scheme && !File.file?(url) url = "#{Vagrant.server_url}/#{url}" - env[:box_url] = url end if metadata_url?(url, env) - add_from_metadata(env) + add_from_metadata(url, env) else - add_direct(env) + add_direct(url, env) end @app.call(env) @@ -43,13 +42,12 @@ module Vagrant # Adds a box file directly (no metadata component, versioning, # etc.) - def add_direct(env) + def add_direct(url, env) name = env[:box_name] if !name || name == "" raise Errors::BoxAddNameRequired end - url = env[:box_url] provider = env[:box_provider] provider = Array(provider) if provider @@ -62,14 +60,19 @@ module Vagrant end # Adds a box given that the URL is a metadata document. - def add_from_metadata(env) + def add_from_metadata(url, env) + original_url = env[:box_url] provider = env[:box_provider] provider = Array(provider) if provider - url = env[:box_url] version = env[:box_version] env[:ui].output(I18n.t( - "vagrant.box_loading_metadata", name: url)) + "vagrant.box_loading_metadata", name: original_url)) + if original_url != url + env[:ui].detail(I18n.t( + "vagrant.box_expanding_url", url: url)) + end + metadata = nil begin metadata_path = download(url, env, ui: false) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 1ef3bd7b1..8528460e9 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -18,6 +18,8 @@ en: Adding box '%{name}' (v%{version}) for '%{provider}' provider... box_added: |- Successfully added box '%{name}' for '%{provider}'! + box_expanding_url: |- + URL: %{url} box_loading_metadata: |- Loading metadata for box '%{name}' cfengine_bootstrapping: |- From d08866e9f316b2bd08f2079a4d27ca27748b8d16 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 22:48:15 -0800 Subject: [PATCH 44/90] core: nicer error if shorthand can't be found --- lib/vagrant/action/builtin/box_add.rb | 15 ++++++++++++++- lib/vagrant/errors.rb | 4 ++++ templates/locales/en.yml | 7 +++++++ test/unit/vagrant/action/builtin/box_add_test.rb | 15 +++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index a1e71f18c..407e1a8e2 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -26,12 +26,25 @@ module Vagrant # If we received a shorthand URL ("mitchellh/precise64"), # then expand it properly. + expanded = false uri = URI.parse(url) if !uri.scheme && !File.file?(url) + expanded = true url = "#{Vagrant.server_url}/#{url}" end - if metadata_url?(url, env) + is_metadata = false + begin + is_metadata = metadata_url?(url, env) + rescue Errors::DownloaderError => e + raise if !expanded + raise Errors::BoxAddShortNotFound, + error: e.extra_data[:message], + name: env[:box_url], + url: url + end + + if is_metadata add_from_metadata(url, env) else add_direct(url, env) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 1984c5efd..4474854b8 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -132,6 +132,10 @@ module Vagrant error_key(:box_add_no_matching_version) end + class BoxAddShortNotFound < VagrantError + error_key(:box_add_short_not_found) + end + class BoxAlreadyExists < VagrantError error_key(:box_add_exists) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 8528460e9..d68beeb2d 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -247,6 +247,13 @@ en: Address: %{url} Constraints: %{constraints} Available versions: %{versions} + box_add_short_not_found: |- + The box '%{name}' could not be found or could not be accessed + in the remote catalog. Please double-check the name. The + expanded URL and error message are shown below. + + URL: %{url} + Error: %{error} boot_bad_state: |- The guest machine entered an invalid state while waiting for it to boot. Valid states are '%{valid}'. The machine is in the diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index def9a1d4e..ea9e8484f 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -234,6 +234,21 @@ describe Vagrant::Action::Builtin::BoxAdd do end end + it "raises an error if shorthand is invalid" do + tf = Tempfile.new("foo") + tf.close + + with_web_server(Pathname.new(tf.path)) do |port| + env[:box_url] = "mitchellh/precise64.json" + + box_collection.should_receive(:add).never + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxAddShortNotFound) + end + end + it "adds the latest version of a box with only one provider" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new("vagrant").tap do |f| From 7e847a36b600ce8c530a9a66185b4a874ded7c30 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Jan 2014 22:48:48 -0800 Subject: [PATCH 45/90] core: update error message for shorthand is bad --- templates/locales/en.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index d68beeb2d..4ec7ade1f 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -248,9 +248,10 @@ en: Constraints: %{constraints} Available versions: %{versions} box_add_short_not_found: |- - The box '%{name}' could not be found or could not be accessed - in the remote catalog. Please double-check the name. The - expanded URL and error message are shown below. + The box '%{name}' could not be found or + could not be accessed in the remote catalog. Please + double-check the name. The expanded URL and error message + are shown below. URL: %{url} Error: %{error} From b2fa785d07c69a2f1c51ed69407c380208af8f7b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 09:16:37 -0800 Subject: [PATCH 46/90] core: Environment loads proper version of box --- lib/vagrant/action.rb | 1 + lib/vagrant/action/builtin/handle_box.rb | 131 ++++ lib/vagrant/box_collection.rb | 12 +- lib/vagrant/environment.rb | 3 +- plugins/kernel_v2/config/vm.rb | 16 +- templates/locales/en.yml | 3 +- test/unit/plugins/kernel_v2/config/vm_test.rb | 42 ++ .../vagrant/action/builtin/handle_box_test.rb | 31 + test/unit/vagrant/box_collection_test.rb | 13 + test/unit/vagrant/environment_test.rb | 627 +++++++++--------- 10 files changed, 572 insertions(+), 307 deletions(-) create mode 100644 lib/vagrant/action/builtin/handle_box.rb create mode 100644 test/unit/vagrant/action/builtin/handle_box_test.rb diff --git a/lib/vagrant/action.rb b/lib/vagrant/action.rb index d13b6d105..627be5ab2 100644 --- a/lib/vagrant/action.rb +++ b/lib/vagrant/action.rb @@ -16,6 +16,7 @@ module Vagrant autoload :DestroyConfirm, "vagrant/action/builtin/destroy_confirm" autoload :EnvSet, "vagrant/action/builtin/env_set" autoload :GracefulHalt, "vagrant/action/builtin/graceful_halt" + autoload :HandleBox, "vagrant/action/builtin/handle_box" autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url" autoload :HandleForwardedPortCollisions, "vagrant/action/builtin/handle_forwarded_port_collisions" autoload :Lock, "vagrant/action/builtin/lock" diff --git a/lib/vagrant/action/builtin/handle_box.rb b/lib/vagrant/action/builtin/handle_box.rb new file mode 100644 index 000000000..932125798 --- /dev/null +++ b/lib/vagrant/action/builtin/handle_box.rb @@ -0,0 +1,131 @@ +require "thread" + +require "log4r" + +module Vagrant + module Action + module Builtin + # This built-in middleware handles the `box` setting by verifying + # the box is already installed, dowloading the box if it isn't, + # updating the box if it is requested, etc. + class HandleBox + @@big_lock = Mutex.new + @@small_locks = Hash.new { |h,k| h[k] = Mutex.new } + + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::action::builtin::handle_box") + end + + def call(env) + machine = env[:machine] + + if !machine.config.vm.box + @logger.info("Skipping HandleBox because no box is set") + return @app.call(env) + end + + # Acquire a lock for this box to handle multi-threaded + # environments. + lock = nil + @@big_lock.synchronize do + lock = @@small_locks[machine.config.vm.box] + end + + lock.synchronize do + if !machine.config.vm.box_url + handle_metadata_box(env) + else + handle_direct_box(env) + end + end + + @app.call(env) + end + + def handle_direct_box(env) + end + + def handle_metadata_box(env) + end + +=begin + def call(env) + if !env[:machine].config.vm.box || !env[:machine].config.vm.box_url + @logger.info("Skipping HandleBoxUrl because box or box_url not set.") + @app.call(env) + return + end + + if env[:machine].box + @logger.info("Skipping HandleBoxUrl because box is already available") + @app.call(env) + return + end + + # Get a "big lock" to make sure that our more fine grained + # lock access is thread safe. + lock = nil + @@big_lock.synchronize do + lock = @@handle_box_url_locks[env[:machine].config.vm.box] + end + + box_name = env[:machine].config.vm.box + box_url = env[:machine].config.vm.box_url + box_download_ca_cert = env[:machine].config.vm.box_download_ca_cert + box_download_checksum = env[:machine].config.vm.box_download_checksum + box_download_checksum_type = env[:machine].config.vm.box_download_checksum_type + box_download_client_cert = env[:machine].config.vm.box_download_client_cert + box_download_insecure = env[:machine].config.vm.box_download_insecure + + # Expand the CA cert file relative to the Vagrantfile path, if + # there is one. + if box_download_ca_cert + box_download_ca_cert = File.expand_path( + box_download_ca_cert, env[:machine].env.root_path) + end + + lock.synchronize do + # Check that we don't already have the box, which can happen + # if we're slow to acquire the lock because of another thread + box_formats = env[:machine].provider_options[:box_format] || + env[:machine].provider_name + if env[:box_collection].find(box_name, box_formats) + break + end + + # Add the box then reload the box collection so that it becomes + # aware of it. + env[:ui].info I18n.t( + "vagrant.actions.vm.check_box.not_found", + :name => box_name, + :provider => env[:machine].provider_name) + + begin + env[:action_runner].run(Vagrant::Action.action_box_add, { + :box_checksum => box_download_checksum, + :box_checksum_type => box_download_checksum_type, + :box_client_cert => box_download_client_cert, + :box_download_ca_cert => box_download_ca_cert, + :box_download_insecure => box_download_insecure, + :box_name => box_name, + :box_provider => box_formats, + :box_url => box_url, + }) + rescue Errors::BoxAlreadyExists + # Just ignore this, since it means the next part will succeed! + # This can happen in a multi-threaded environment. + end + end + + # Reload the environment and set the VM to be the new loaded VM. + env[:machine] = env[:machine].env.machine( + env[:machine].name, env[:machine].provider_name, true) + + @app.call(env) + end +=end + end + end + end +end diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index a108d84d3..c299ab162 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -245,18 +245,24 @@ module Vagrant return nil end - box_directory.children(true).each do |versiondir| + versions = box_directory.children(true).map do |versiondir| version = versiondir.basename.to_s - if !requirements.all? { |r| r.satisfied_by?(Gem::Version.new(version)) } + Gem::Version.new(version) + end + + # Traverse through versions with the latest version first + versions.sort.reverse.each do |v| + if !requirements.all? { |r| r.satisfied_by?(v) } # Unsatisfied version requirements next end + versiondir = box_directory.join(v.to_s) providers.each do |provider| provider_dir = versiondir.join(provider.to_s) next if !provider_dir.directory? @logger.info("Box found: #{name} (#{provider})") - return Box.new(name, provider, version, provider_dir) + return Box.new(name, provider, v.to_s, provider_dir) end end end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index dff913c39..db0d79812 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -349,8 +349,7 @@ module Vagrant box = nil if config.vm.box begin - # TODO: support real version constraints - box = boxes.find(config.vm.box, box_formats, ">= 0") + box = boxes.find(config.vm.box, box_formats, config.vm.box_version) rescue Errors::BoxUpgradeRequired # Upgrade the box if we must @logger.info("Upgrading box during config load: #{config.vm.box}") diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 1e92f68f3..55d8dc752 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -18,6 +18,7 @@ module VagrantPlugins attr_accessor :boot_timeout attr_accessor :box attr_accessor :box_url + attr_accessor :box_version attr_accessor :box_download_ca_cert attr_accessor :box_download_checksum attr_accessor :box_download_checksum_type @@ -38,6 +39,7 @@ module VagrantPlugins @box_download_client_cert = UNSET_VALUE @box_download_insecure = UNSET_VALUE @box_url = UNSET_VALUE + @box_version = UNSET_VALUE @graceful_halt_timeout = UNSET_VALUE @guest = UNSET_VALUE @hostname = UNSET_VALUE @@ -309,6 +311,7 @@ module VagrantPlugins @box_download_client_cert = nil if @box_download_client_cert == UNSET_VALUE @box_download_insecure = false if @box_download_insecure == UNSET_VALUE @box_url = nil if @box_url == UNSET_VALUE + @box_version = ">= 0" if @box_version == UNSET_VALUE @graceful_halt_timeout = 60 if @graceful_halt_timeout == UNSET_VALUE @guest = nil if @guest == UNSET_VALUE @hostname = nil if @hostname == UNSET_VALUE @@ -449,11 +452,20 @@ module VagrantPlugins def validate(machine) errors = _detected_errors errors << I18n.t("vagrant.config.vm.box_missing") if !box - errors << I18n.t("vagrant.config.vm.box_not_found", :name => box) if \ - box && !box_url && !machine.box errors << I18n.t("vagrant.config.vm.hostname_invalid_characters") if \ @hostname && @hostname !~ /^[a-z0-9][-.a-z0-9]+$/i + if @box_version + @box_version.split(",").each do |v| + begin + Gem::Requirement.new(v.strip) + rescue Gem::Requirement::BadRequirementError + errors << I18n.t( + "vagrant.config.vm.bad_version", version: v) + end + end + end + if box_download_ca_cert path = Pathname.new(box_download_ca_cert). expand_path(machine.env.root_path) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 4ec7ade1f..044c649ff 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -950,6 +950,8 @@ en: ssh: private_key_missing: "`private_key_path` file must exist: %{path}" vm: + bad_version: |- + Invalid box version constraints: %{version} box_download_ca_cert_not_found: |- "box_download_ca_cert" file not found: %{path} box_download_checksum_blank: |- @@ -957,7 +959,6 @@ en: box_download_checksum_notblank: |- Checksum specified but must also specify "box_download_checksum_type" box_missing: "A box must be specified." - box_not_found: "The box '%{name}' could not be found." hostname_invalid_characters: |- The hostname set for the VM should only contain letters, numbers, hyphens or dots. It cannot start with a hyphen or dot. diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index 99afefdab..9eaf0473c 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -5,6 +5,26 @@ require Vagrant.source_root.join("plugins/kernel_v2/config/vm") describe VagrantPlugins::Kernel_V2::VMConfig do subject { described_class.new } + let(:machine) { double("machine") } + + def assert_valid + errors = subject.validate(machine) + if !errors.values.all? { |v| v.empty? } + raise "Errors: #{errors.inspect}" + end + end + + before do + machine.stub(provider_config: nil) + + subject.box = "foo" + end + + it "is valid with test defaults" do + subject.finalize! + assert_valid + end + describe "#base_mac" do it "defaults properly" do subject.finalize! @@ -19,6 +39,28 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end end + context "#box_version" do + it "defaults to >= 0" do + subject.finalize! + + expect(subject.box_version).to eq(">= 0") + end + + it "errors if invalid version" do + subject.box_version = "nope" + subject.finalize! + + expect { assert_valid }.to raise_error(RuntimeError) + end + + it "can have complex constraints" do + subject.box_version = ">= 0, ~> 1.0" + subject.finalize! + + assert_valid + end + end + describe "#network(s)" do it "defaults to forwarding SSH" do subject.finalize! diff --git a/test/unit/vagrant/action/builtin/handle_box_test.rb b/test/unit/vagrant/action/builtin/handle_box_test.rb new file mode 100644 index 000000000..510ab0368 --- /dev/null +++ b/test/unit/vagrant/action/builtin/handle_box_test.rb @@ -0,0 +1,31 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Action::Builtin::HandleBox do + include_context "unit" + + let(:app) { lambda { |env| } } + let(:env) { { + machine: machine, + ui: Vagrant::UI::Silent.new, + } } + + subject { described_class.new(app, env) } + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + + it "works if there is no box set" do + machine.config.vm.box = nil + machine.config.vm.box_url = nil + + app.should_receive(:call).with(env) + + subject.call(env) + end +end diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index 6a83f847d..d2ac1dac1 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -58,6 +58,19 @@ describe Vagrant::BoxCollection do expect(result.name).to eq("foo") end + it "returns latest version matching constraint" do + # Create the "box" + environment.box3("foo", "1.0", :virtualbox) + environment.box3("foo", "1.5", :virtualbox) + + # Actual test + result = subject.find("foo", :virtualbox, ">= 0") + expect(result).to_not be_nil + expect(result).to be_kind_of(box_class) + expect(result.name).to eq("foo") + expect(result.version).to eq("1.5") + end + it "can satisfy complex constraints" do # Create the "box" environment.box3("foo", "0.1", :virtualbox) diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index 5dadef024..2e7758dcd 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -194,6 +194,334 @@ describe Vagrant::Environment do end end + describe "#machine" do + # A helper to register a provider for use in tests. + def register_provider(name, config_class=nil, options=nil) + provider_cls = Class.new(Vagrant.plugin("2", :provider)) + + register_plugin("2") do |p| + p.provider(name, options) { provider_cls } + + if config_class + p.config(name, :provider) { config_class } + end + end + + provider_cls + end + + it "should return a machine object with the correct provider" do + # Create a provider + foo_provider = register_provider("foo") + + # Create the configuration + isolated_env = isolated_environment do |e| + e.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" + config.vm.define "foo" +end +VF + + e.box3("base", "1.0", :foo) + end + + # Verify that we can get the machine + env = isolated_env.create_vagrant_env + machine = env.machine(:foo, :foo) + machine.should be_kind_of(Vagrant::Machine) + machine.name.should == :foo + machine.provider.should be_kind_of(foo_provider) + machine.provider_config.should be_nil + end + + it "should return a machine object with the machine configuration" do + # Create a provider + foo_config = Class.new(Vagrant.plugin("2", :config)) do + attr_accessor :value + end + + foo_provider = register_provider("foo", foo_config) + + # Create the configuration + isolated_env = isolated_environment do |e| + e.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" + config.vm.define "foo" + + config.vm.provider :foo do |fooconfig| + fooconfig.value = 100 + end +end +VF + + e.box3("base", "1.0", :foo) + end + + # Verify that we can get the machine + env = isolated_env.create_vagrant_env + machine = env.machine(:foo, :foo) + machine.should be_kind_of(Vagrant::Machine) + machine.name.should == :foo + machine.provider.should be_kind_of(foo_provider) + machine.provider_config.value.should == 100 + end + + it "should cache the machine objects by name and provider" do + # Create a provider + foo_provider = register_provider("foo") + bar_provider = register_provider("bar") + + # Create the configuration + isolated_env = isolated_environment do |e| + e.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" + config.vm.define "vm1" + config.vm.define "vm2" +end +VF + + e.box3("base", "1.0", :foo) + e.box3("base", "1.0", :bar) + end + + env = isolated_env.create_vagrant_env + vm1_foo = env.machine(:vm1, :foo) + vm1_bar = env.machine(:vm1, :bar) + vm2_foo = env.machine(:vm2, :foo) + + vm1_foo.should eql(env.machine(:vm1, :foo)) + vm1_bar.should eql(env.machine(:vm1, :bar)) + vm1_foo.should_not eql(vm1_bar) + vm2_foo.should eql(env.machine(:vm2, :foo)) + end + + it "should load a machine without a box" do + register_provider("foo") + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "i-dont-exist" +end +VF + end + + env = environment.create_vagrant_env + machine = env.machine(:default, :foo) + machine.box.should be_nil + end + + it "should load the machine configuration" do + register_provider("foo") + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 1 + config.vm.box = "base" + + config.vm.define "vm1" do |inner| + inner.ssh.port = 100 + end +end +VF + + env.box3("base", "1.0", :foo) + end + + env = environment.create_vagrant_env + machine = env.machine(:vm1, :foo) + machine.config.ssh.port.should == 100 + machine.config.vm.box.should == "base" + end + + it "should load the box configuration for a box" do + register_provider("foo") + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" +end +VF + + env.box3("base", "1.0", :foo, :vagrantfile => <<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 100 +end +VF + end + + env = environment.create_vagrant_env + machine = env.machine(:default, :foo) + machine.config.ssh.port.should == 100 + end + + it "should load the box configuration for a box and custom Vagrantfile name" do + register_provider("foo") + + environment = isolated_environment do |env| + env.file("some_other_name", <<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" +end +VF + + env.box3("base", "1.0", :foo, :vagrantfile => <<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 100 +end +VF + end + + env = with_temp_env("VAGRANT_VAGRANTFILE" => "some_other_name") do + environment.create_vagrant_env + end + + machine = env.machine(:default, :foo) + machine.config.ssh.port.should == 100 + end + + it "should load the box configuration for other formats for a box" do + register_provider("foo", nil, box_format: "bar") + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" +end +VF + + env.box3("base", "1.0", :bar, :vagrantfile => <<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 100 +end +VF + end + + env = environment.create_vagrant_env + machine = env.machine(:default, :foo) + machine.config.ssh.port.should == 100 + end + + it "prefer sooner formats when multiple box formats are available" do + register_provider("foo", nil, box_format: ["fA", "fB"]) + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" +end +VF + + env.box3("base", "1.0", :fA, :vagrantfile => <<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 100 +end +VF + + env.box3("base", "1.0", :fB, :vagrantfile => <<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 200 +end +VF + end + + env = environment.create_vagrant_env + machine = env.machine(:default, :foo) + machine.config.ssh.port.should == 100 + end + + it "should load the proper version of a box" do + register_provider("foo") + + environment = isolated_environment do |env| + env.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" + config.vm.box_version = "~> 1.2" +end +VF + + env.box3("base", "1.0", :foo, :vagrantfile => <<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 100 +end +VF + + env.box3("base", "1.5", :foo, :vagrantfile => <<-VF) +Vagrant.configure("2") do |config| + config.ssh.port = 200 +end +VF + end + + env = environment.create_vagrant_env + machine = env.machine(:default, :foo) + machine.config.ssh.port.should == 200 + end + + it "should load the provider override if set" do + register_provider("bar") + register_provider("foo") + + isolated_env = isolated_environment do |e| + e.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "foo" + + config.vm.provider :foo do |_, c| + c.vm.box = "bar" + end +end +VF + end + + env = isolated_env.create_vagrant_env + foo_vm = env.machine(:default, :foo) + bar_vm = env.machine(:default, :bar) + foo_vm.config.vm.box.should == "bar" + bar_vm.config.vm.box.should == "foo" + end + + it "should reload the cache if refresh is set" do + # Create a provider + foo_provider = register_provider("foo") + + # Create the configuration + isolated_env = isolated_environment do |e| + e.vagrantfile(<<-VF) +Vagrant.configure("2") do |config| + config.vm.box = "base" +end +VF + + e.box3("base", "1.0", :foo) + end + + env = isolated_env.create_vagrant_env + vm1 = env.machine(:default, :foo) + vm2 = env.machine(:default, :foo, true) + vm3 = env.machine(:default, :foo) + + vm1.should_not eql(vm2) + vm2.should eql(vm3) + end + + it "should raise an error if the VM is not found" do + expect { instance.machine("i-definitely-dont-exist", :virtualbox) }. + to raise_error(Vagrant::Errors::MachineNotFound) + end + + it "should raise an error if the provider is not found" do + expect { instance.machine(:default, :lol_no) }. + to raise_error(Vagrant::Errors::ProviderNotFound) + end + end + describe "active machines" do it "should be empty if the machines folder doesn't exist" do folder = instance.local_data_path.join("machines") @@ -559,305 +887,6 @@ VF end end - describe "getting a machine" do - # A helper to register a provider for use in tests. - def register_provider(name, config_class=nil, options=nil) - provider_cls = Class.new(Vagrant.plugin("2", :provider)) - - register_plugin("2") do |p| - p.provider(name, options) { provider_cls } - - if config_class - p.config(name, :provider) { config_class } - end - end - - provider_cls - end - - it "should return a machine object with the correct provider" do - # Create a provider - foo_provider = register_provider("foo") - - # Create the configuration - isolated_env = isolated_environment do |e| - e.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" - config.vm.define "foo" -end -VF - - e.box3("base", "1.0", :foo) - end - - # Verify that we can get the machine - env = isolated_env.create_vagrant_env - machine = env.machine(:foo, :foo) - machine.should be_kind_of(Vagrant::Machine) - machine.name.should == :foo - machine.provider.should be_kind_of(foo_provider) - machine.provider_config.should be_nil - end - - it "should return a machine object with the machine configuration" do - # Create a provider - foo_config = Class.new(Vagrant.plugin("2", :config)) do - attr_accessor :value - end - - foo_provider = register_provider("foo", foo_config) - - # Create the configuration - isolated_env = isolated_environment do |e| - e.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" - config.vm.define "foo" - - config.vm.provider :foo do |fooconfig| - fooconfig.value = 100 - end -end -VF - - e.box3("base", "1.0", :foo) - end - - # Verify that we can get the machine - env = isolated_env.create_vagrant_env - machine = env.machine(:foo, :foo) - machine.should be_kind_of(Vagrant::Machine) - machine.name.should == :foo - machine.provider.should be_kind_of(foo_provider) - machine.provider_config.value.should == 100 - end - - it "should cache the machine objects by name and provider" do - # Create a provider - foo_provider = register_provider("foo") - bar_provider = register_provider("bar") - - # Create the configuration - isolated_env = isolated_environment do |e| - e.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" - config.vm.define "vm1" - config.vm.define "vm2" -end -VF - - e.box3("base", "1.0", :foo) - e.box3("base", "1.0", :bar) - end - - env = isolated_env.create_vagrant_env - vm1_foo = env.machine(:vm1, :foo) - vm1_bar = env.machine(:vm1, :bar) - vm2_foo = env.machine(:vm2, :foo) - - vm1_foo.should eql(env.machine(:vm1, :foo)) - vm1_bar.should eql(env.machine(:vm1, :bar)) - vm1_foo.should_not eql(vm1_bar) - vm2_foo.should eql(env.machine(:vm2, :foo)) - end - - it "should load a machine without a box" do - register_provider("foo") - - environment = isolated_environment do |env| - env.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "i-dont-exist" -end -VF - end - - env = environment.create_vagrant_env - machine = env.machine(:default, :foo) - machine.box.should be_nil - end - - it "should load the machine configuration" do - register_provider("foo") - - environment = isolated_environment do |env| - env.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.ssh.port = 1 - config.vm.box = "base" - - config.vm.define "vm1" do |inner| - inner.ssh.port = 100 - end -end -VF - - env.box3("base", "1.0", :foo) - end - - env = environment.create_vagrant_env - machine = env.machine(:vm1, :foo) - machine.config.ssh.port.should == 100 - machine.config.vm.box.should == "base" - end - - it "should load the box configuration for a V2 box" do - register_provider("foo") - - environment = isolated_environment do |env| - env.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" -end -VF - - env.box3("base", "1.0", :foo, :vagrantfile => <<-VF) -Vagrant.configure("2") do |config| - config.ssh.port = 100 -end -VF - end - - env = environment.create_vagrant_env - machine = env.machine(:default, :foo) - machine.config.ssh.port.should == 100 - end - - it "should load the box configuration for a V2 box and custom Vagrantfile name" do - register_provider("foo") - - environment = isolated_environment do |env| - env.file("some_other_name", <<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" -end -VF - - env.box3("base", "1.0", :foo, :vagrantfile => <<-VF) -Vagrant.configure("2") do |config| - config.ssh.port = 100 -end -VF - end - - env = with_temp_env("VAGRANT_VAGRANTFILE" => "some_other_name") do - environment.create_vagrant_env - end - - machine = env.machine(:default, :foo) - machine.config.ssh.port.should == 100 - end - - it "should load the box configuration for other formats for a V2 box" do - register_provider("foo", nil, box_format: "bar") - - environment = isolated_environment do |env| - env.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" -end -VF - - env.box3("base", "1.0", :bar, :vagrantfile => <<-VF) -Vagrant.configure("2") do |config| - config.ssh.port = 100 -end -VF - end - - env = environment.create_vagrant_env - machine = env.machine(:default, :foo) - machine.config.ssh.port.should == 100 - end - - it "prefer sooner formats when multiple box formats are available" do - register_provider("foo", nil, box_format: ["fA", "fB"]) - - environment = isolated_environment do |env| - env.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" -end -VF - - env.box3("base", "1.0", :fA, :vagrantfile => <<-VF) -Vagrant.configure("2") do |config| - config.ssh.port = 100 -end -VF - - env.box3("base", "1.0", :fB, :vagrantfile => <<-VF) -Vagrant.configure("2") do |config| - config.ssh.port = 200 -end -VF - end - - env = environment.create_vagrant_env - machine = env.machine(:default, :foo) - machine.config.ssh.port.should == 100 - end - - it "should load the provider override if set" do - register_provider("bar") - register_provider("foo") - - isolated_env = isolated_environment do |e| - e.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "foo" - - config.vm.provider :foo do |_, c| - c.vm.box = "bar" - end -end -VF - end - - env = isolated_env.create_vagrant_env - foo_vm = env.machine(:default, :foo) - bar_vm = env.machine(:default, :bar) - foo_vm.config.vm.box.should == "bar" - bar_vm.config.vm.box.should == "foo" - end - - it "should reload the cache if refresh is set" do - # Create a provider - foo_provider = register_provider("foo") - - # Create the configuration - isolated_env = isolated_environment do |e| - e.vagrantfile(<<-VF) -Vagrant.configure("2") do |config| - config.vm.box = "base" -end -VF - - e.box3("base", "1.0", :foo) - end - - env = isolated_env.create_vagrant_env - vm1 = env.machine(:default, :foo) - vm2 = env.machine(:default, :foo, true) - vm3 = env.machine(:default, :foo) - - vm1.should_not eql(vm2) - vm2.should eql(vm3) - end - - it "should raise an error if the VM is not found" do - expect { instance.machine("i-definitely-dont-exist", :virtualbox) }. - to raise_error(Vagrant::Errors::MachineNotFound) - end - - it "should raise an error if the provider is not found" do - expect { instance.machine(:default, :lol_no) }. - to raise_error(Vagrant::Errors::ProviderNotFound) - end - end - describe "getting machine names" do it "should return the default machine if no multi-VM is used" do # Create the config From a1157504012c3df5499602cdc060e4e8248589bb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 09:41:12 -0800 Subject: [PATCH 47/90] core: HandleBox works with metadata boxes --- lib/vagrant/action/builtin/handle_box.rb | 32 +++++++ lib/vagrant/action/builtin/handle_box_url.rb | 94 +------------------ templates/locales/en.yml | 2 + .../vagrant/action/builtin/handle_box_test.rb | 61 +++++++++++- 4 files changed, 96 insertions(+), 93 deletions(-) diff --git a/lib/vagrant/action/builtin/handle_box.rb b/lib/vagrant/action/builtin/handle_box.rb index 932125798..bf13880e0 100644 --- a/lib/vagrant/action/builtin/handle_box.rb +++ b/lib/vagrant/action/builtin/handle_box.rb @@ -47,6 +47,38 @@ module Vagrant end def handle_metadata_box(env) + machine = env[:machine] + + if machine.box + @logger.info("Machine already has box. HandleBox will not run.") + return + end + + # Determine the set of formats that this box can be in + box_download_ca_cert = env[:machine].config.vm.box_download_ca_cert + box_download_client_cert = env[:machine].config.vm.box_download_client_cert + box_download_insecure = env[:machine].config.vm.box_download_insecure + box_formats = env[:machine].provider_options[:box_format] || + env[:machine].provider_name + + env[:ui].output(I18n.t( + "vagrant.box_auto_adding", name: machine.config.vm.box)) + env[:ui].detail("Box Provider: #{Array(box_formats).join(", ")}") + env[:ui].detail("Box Version: #{machine.config.vm.box_version}") + + begin + env[:action_runner].run(Vagrant::Action.action_box_add, { + box_url: machine.config.vm.box, + box_provider: box_formats, + box_version: machine.config.vm.box_version, + box_client_cert: box_download_client_cert, + box_download_ca_cert: box_download_ca_cert, + box_download_insecure: box_download_insecure, + }) + rescue Errors::BoxAlreadyExists + # Just ignore this, since it means the next part will succeed! + # This can happen in a multi-threaded environment. + end end =begin diff --git a/lib/vagrant/action/builtin/handle_box_url.rb b/lib/vagrant/action/builtin/handle_box_url.rb index 6a71be6a5..b77937883 100644 --- a/lib/vagrant/action/builtin/handle_box_url.rb +++ b/lib/vagrant/action/builtin/handle_box_url.rb @@ -1,96 +1,12 @@ -require "thread" - -require "log4r" - module Vagrant module Action module Builtin - # This built-in middleware handles the `box_url` setting, downloading - # the box if necessary. You should place this early in your middleware - # sequence for a provider after configuration validation but before - # you attempt to use any box. - class HandleBoxUrl - @@big_lock = Mutex.new - @@handle_box_url_locks = Hash.new { |h,k| h[k] = Mutex.new } - - def initialize(app, env) - @app = app - @logger = Log4r::Logger.new("vagrant::action::builtin::handle_box_url") - end - + class HandleBoxUrl < HandleBox def call(env) - if !env[:machine].config.vm.box || !env[:machine].config.vm.box_url - @logger.info("Skipping HandleBoxUrl because box or box_url not set.") - @app.call(env) - return - end - - if env[:machine].box - @logger.info("Skipping HandleBoxUrl because box is already available") - @app.call(env) - return - end - - # Get a "big lock" to make sure that our more fine grained - # lock access is thread safe. - lock = nil - @@big_lock.synchronize do - lock = @@handle_box_url_locks[env[:machine].config.vm.box] - end - - box_name = env[:machine].config.vm.box - box_url = env[:machine].config.vm.box_url - box_download_ca_cert = env[:machine].config.vm.box_download_ca_cert - box_download_checksum = env[:machine].config.vm.box_download_checksum - box_download_checksum_type = env[:machine].config.vm.box_download_checksum_type - box_download_client_cert = env[:machine].config.vm.box_download_client_cert - box_download_insecure = env[:machine].config.vm.box_download_insecure - - # Expand the CA cert file relative to the Vagrantfile path, if - # there is one. - if box_download_ca_cert - box_download_ca_cert = File.expand_path( - box_download_ca_cert, env[:machine].env.root_path) - end - - lock.synchronize do - # Check that we don't already have the box, which can happen - # if we're slow to acquire the lock because of another thread - box_formats = env[:machine].provider_options[:box_format] || - env[:machine].provider_name - if env[:box_collection].find(box_name, box_formats) - break - end - - # Add the box then reload the box collection so that it becomes - # aware of it. - env[:ui].info I18n.t( - "vagrant.actions.vm.check_box.not_found", - :name => box_name, - :provider => env[:machine].provider_name) - - begin - env[:action_runner].run(Vagrant::Action.action_box_add, { - :box_checksum => box_download_checksum, - :box_checksum_type => box_download_checksum_type, - :box_client_cert => box_download_client_cert, - :box_download_ca_cert => box_download_ca_cert, - :box_download_insecure => box_download_insecure, - :box_name => box_name, - :box_provider => box_formats, - :box_url => box_url, - }) - rescue Errors::BoxAlreadyExists - # Just ignore this, since it means the next part will succeed! - # This can happen in a multi-threaded environment. - end - end - - # Reload the environment and set the VM to be the new loaded VM. - env[:machine] = env[:machine].env.machine( - env[:machine].name, env[:machine].provider_name, true) - - @app.call(env) + env[:ui].warn("HandleBoxUrl middleware is deprecated. Use HandleBox instead.") + env[:ui].warn("This is a bug with the provider. Please contact the creator") + env[:ui].warn("of the provider you use to fix this.") + super end end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 044c649ff..accb87fa0 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -4,6 +4,8 @@ en: Machine booted and ready! boot_waiting: |- Waiting for machine to boot. This may take a few minutes... + box_auto_adding: |- + Box '%{name}' could not be found. Attempting to find and install... box_add_choose_provider: |- This box can work with multiple providers! The providers that it can work with are listed below. Please review the list and choose diff --git a/test/unit/vagrant/action/builtin/handle_box_test.rb b/test/unit/vagrant/action/builtin/handle_box_test.rb index 510ab0368..b2a53800a 100644 --- a/test/unit/vagrant/action/builtin/handle_box_test.rb +++ b/test/unit/vagrant/action/builtin/handle_box_test.rb @@ -5,6 +5,7 @@ describe Vagrant::Action::Builtin::HandleBox do let(:app) { lambda { |env| } } let(:env) { { + action_runner: action_runner, machine: machine, ui: Vagrant::UI::Silent.new, } } @@ -13,12 +14,19 @@ describe Vagrant::Action::Builtin::HandleBox do let(:iso_env) do # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env + isolated_environment.tap do |env| + env.vagrantfile("") + end end - let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + let(:iso_vagrant_env) { iso_env.create_vagrant_env } + + let(:action_runner) { double("action_runner") } + let(:box) do + box_dir = iso_env.box3("foo", "1.0", :virtualbox) + Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) + end + let(:machine) { iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy) } it "works if there is no box set" do machine.config.vm.box = nil @@ -28,4 +36,49 @@ describe Vagrant::Action::Builtin::HandleBox do subject.call(env) end + + context "without a box_url" do + before do + machine.stub(box: nil) + + machine.config.vm.box = "foo" + end + + it "doesn't do anything if a box exists" do + machine.stub(box: box) + + action_runner.should_receive(:run).never + app.should_receive(:call).with(env) + + subject.call(env) + end + + it "adds a box that doesn't exist" do + action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_url]).to eq(machine.config.vm.box) + expect(opts[:box_provider]).to eq(:dummy) + expect(opts[:box_version]).to eq(machine.config.vm.box_version) + true + end + + app.should_receive(:call).with(env) + + subject.call(env) + end + + it "adds a box using any format the provider allows" do + machine.provider_options[:box_format] = [:foo, :bar] + + action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_url]).to eq(machine.config.vm.box) + expect(opts[:box_provider]).to eq([:foo, :bar]) + expect(opts[:box_version]).to eq(machine.config.vm.box_version) + true + end + + app.should_receive(:call).with(env) + + subject.call(env) + end + end end From a73ea6ab7ac591625f62b59d3e23ec3f4d0a4f8c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 09:51:31 -0800 Subject: [PATCH 48/90] core: fix hang in test --- test/unit/vagrant/action/builtin/box_add_test.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index ea9e8484f..f761bb35d 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -244,8 +244,11 @@ describe Vagrant::Action::Builtin::BoxAdd do box_collection.should_receive(:add).never app.should_receive(:call).never - expect { subject.call(env) }. - to raise_error(Vagrant::Errors::BoxAddShortNotFound) + url = "http://127.0.0.1:#{port}" + with_temp_env("VAGRANT_SERVER_URL" => url) do + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxAddShortNotFound) + end end end From e316e10552d2dd58da1d410df1cd633a4dc8a2af Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 09:56:37 -0800 Subject: [PATCH 49/90] core: make sure the env has the UI in it for adding boxes --- lib/vagrant/action/builtin/handle_box.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/action/builtin/handle_box.rb b/lib/vagrant/action/builtin/handle_box.rb index bf13880e0..eab2ed410 100644 --- a/lib/vagrant/action/builtin/handle_box.rb +++ b/lib/vagrant/action/builtin/handle_box.rb @@ -67,14 +67,14 @@ module Vagrant env[:ui].detail("Box Version: #{machine.config.vm.box_version}") begin - env[:action_runner].run(Vagrant::Action.action_box_add, { + env[:action_runner].run(Vagrant::Action.action_box_add, env.merge({ box_url: machine.config.vm.box, box_provider: box_formats, box_version: machine.config.vm.box_version, box_client_cert: box_download_client_cert, box_download_ca_cert: box_download_ca_cert, box_download_insecure: box_download_insecure, - }) + })) rescue Errors::BoxAlreadyExists # Just ignore this, since it means the next part will succeed! # This can happen in a multi-threaded environment. From f2c4adc4ee7bc2576c4149751667169ffe387073 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 10:02:16 -0800 Subject: [PATCH 50/90] core: use symbol names for providers everywhere --- lib/vagrant/box_metadata.rb | 10 +++++----- test/unit/vagrant/box_metadata_test.rb | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/vagrant/box_metadata.rb b/lib/vagrant/box_metadata.rb index e0431b4f4..14ac62ee7 100644 --- a/lib/vagrant/box_metadata.rb +++ b/lib/vagrant/box_metadata.rb @@ -51,7 +51,7 @@ module Vagrant end providers = nil - providers = Array(opts[:provider]) if opts[:provider] + providers = Array(opts[:provider]).map(&:to_sym) if opts[:provider] @version_map.keys.sort.reverse.each do |v| next if !requirements.all? { |r| r.satisfied_by?(v) } @@ -84,7 +84,7 @@ module Vagrant @version = raw["version"] @provider_map = (raw["providers"] || []).map do |p| - [p["name"], p] + [p["name"].to_sym, p] end @provider_map = Hash[@provider_map] end @@ -92,7 +92,7 @@ module Vagrant # Returns a [Provider] for the given name, or nil if it isn't # supported by this version. def provider(name) - p = @provider_map[name] + p = @provider_map[name.to_sym] return nil if !p Provider.new(p) end @@ -100,9 +100,9 @@ module Vagrant # Returns the providers that are available for this version # of the box. # - # @return [Provider] + # @return [Array] def providers - @provider_map.keys + @provider_map.keys.map(&:to_sym) end end diff --git a/test/unit/vagrant/box_metadata_test.rb b/test/unit/vagrant/box_metadata_test.rb index 45d6e7974..4ace29922 100644 --- a/test/unit/vagrant/box_metadata_test.rb +++ b/test/unit/vagrant/box_metadata_test.rb @@ -77,7 +77,7 @@ describe Vagrant::BoxMetadata do end it "matches the constraint that has the given provider" do - result = subject.version(">= 0", provider: "vmware") + result = subject.version(">= 0", provider: :vmware) expect(result).to_not be_nil expect(result).to be_kind_of(Vagrant::BoxMetadata::Version) expect(result.version).to eq("1.1.0") @@ -131,7 +131,7 @@ describe Vagrant::BoxMetadata::Version do describe "#providers" do it "returns the providers available" do expect(subject.providers.sort).to eq( - ["virtualbox", "vmware"]) + [:virtualbox, :vmware]) end end end From b23ad41c401b415e439744aa292448e52b220a72 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 11:17:23 -0800 Subject: [PATCH 51/90] core: HandleBox reloads the box --- lib/vagrant/action/builtin/handle_box.rb | 95 ++----------------- .../vagrant/action/builtin/handle_box_test.rb | 45 +++++++-- 2 files changed, 43 insertions(+), 97 deletions(-) diff --git a/lib/vagrant/action/builtin/handle_box.rb b/lib/vagrant/action/builtin/handle_box.rb index eab2ed410..6aea7d9ea 100644 --- a/lib/vagrant/action/builtin/handle_box.rb +++ b/lib/vagrant/action/builtin/handle_box.rb @@ -33,20 +33,17 @@ module Vagrant end lock.synchronize do - if !machine.config.vm.box_url - handle_metadata_box(env) - else - handle_direct_box(env) - end + handle_box(env) end + # Reload the environment and set the VM to be the new loaded VM. + env[:machine] = env[:machine].env.machine( + env[:machine].name, env[:machine].provider_name, true) + @app.call(env) end - def handle_direct_box(env) - end - - def handle_metadata_box(env) + def handle_box(env) machine = env[:machine] if machine.box @@ -68,7 +65,8 @@ module Vagrant begin env[:action_runner].run(Vagrant::Action.action_box_add, env.merge({ - box_url: machine.config.vm.box, + box_name: machine.config.vm.box, + box_url: machine.config.vm.box_url || machine.config.vm.box, box_provider: box_formats, box_version: machine.config.vm.box_version, box_client_cert: box_download_client_cert, @@ -80,83 +78,6 @@ module Vagrant # This can happen in a multi-threaded environment. end end - -=begin - def call(env) - if !env[:machine].config.vm.box || !env[:machine].config.vm.box_url - @logger.info("Skipping HandleBoxUrl because box or box_url not set.") - @app.call(env) - return - end - - if env[:machine].box - @logger.info("Skipping HandleBoxUrl because box is already available") - @app.call(env) - return - end - - # Get a "big lock" to make sure that our more fine grained - # lock access is thread safe. - lock = nil - @@big_lock.synchronize do - lock = @@handle_box_url_locks[env[:machine].config.vm.box] - end - - box_name = env[:machine].config.vm.box - box_url = env[:machine].config.vm.box_url - box_download_ca_cert = env[:machine].config.vm.box_download_ca_cert - box_download_checksum = env[:machine].config.vm.box_download_checksum - box_download_checksum_type = env[:machine].config.vm.box_download_checksum_type - box_download_client_cert = env[:machine].config.vm.box_download_client_cert - box_download_insecure = env[:machine].config.vm.box_download_insecure - - # Expand the CA cert file relative to the Vagrantfile path, if - # there is one. - if box_download_ca_cert - box_download_ca_cert = File.expand_path( - box_download_ca_cert, env[:machine].env.root_path) - end - - lock.synchronize do - # Check that we don't already have the box, which can happen - # if we're slow to acquire the lock because of another thread - box_formats = env[:machine].provider_options[:box_format] || - env[:machine].provider_name - if env[:box_collection].find(box_name, box_formats) - break - end - - # Add the box then reload the box collection so that it becomes - # aware of it. - env[:ui].info I18n.t( - "vagrant.actions.vm.check_box.not_found", - :name => box_name, - :provider => env[:machine].provider_name) - - begin - env[:action_runner].run(Vagrant::Action.action_box_add, { - :box_checksum => box_download_checksum, - :box_checksum_type => box_download_checksum_type, - :box_client_cert => box_download_client_cert, - :box_download_ca_cert => box_download_ca_cert, - :box_download_insecure => box_download_insecure, - :box_name => box_name, - :box_provider => box_formats, - :box_url => box_url, - }) - rescue Errors::BoxAlreadyExists - # Just ignore this, since it means the next part will succeed! - # This can happen in a multi-threaded environment. - end - end - - # Reload the environment and set the VM to be the new loaded VM. - env[:machine] = env[:machine].env.machine( - env[:machine].name, env[:machine].provider_name, true) - - @app.call(env) - end -=end end end end diff --git a/test/unit/vagrant/action/builtin/handle_box_test.rb b/test/unit/vagrant/action/builtin/handle_box_test.rb index b2a53800a..5ddbf7b7f 100644 --- a/test/unit/vagrant/action/builtin/handle_box_test.rb +++ b/test/unit/vagrant/action/builtin/handle_box_test.rb @@ -37,24 +37,25 @@ describe Vagrant::Action::Builtin::HandleBox do subject.call(env) end - context "without a box_url" do + it "doesn't do anything if a box exists" do + machine.stub(box: box) + + action_runner.should_receive(:run).never + app.should_receive(:call).with(env) + + subject.call(env) + end + + context "with a box set and no box_url" do before do machine.stub(box: nil) machine.config.vm.box = "foo" end - it "doesn't do anything if a box exists" do - machine.stub(box: box) - - action_runner.should_receive(:run).never - app.should_receive(:call).with(env) - - subject.call(env) - end - it "adds a box that doesn't exist" do action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_name]).to eq(machine.config.vm.box) expect(opts[:box_url]).to eq(machine.config.vm.box) expect(opts[:box_provider]).to eq(:dummy) expect(opts[:box_version]).to eq(machine.config.vm.box_version) @@ -70,6 +71,7 @@ describe Vagrant::Action::Builtin::HandleBox do machine.provider_options[:box_format] = [:foo, :bar] action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_name]).to eq(machine.config.vm.box) expect(opts[:box_url]).to eq(machine.config.vm.box) expect(opts[:box_provider]).to eq([:foo, :bar]) expect(opts[:box_version]).to eq(machine.config.vm.box_version) @@ -81,4 +83,27 @@ describe Vagrant::Action::Builtin::HandleBox do subject.call(env) end end + + context "with a box and box_url set" do + before do + machine.stub(box: nil) + + machine.config.vm.box = "foo" + machine.config.vm.box_url = "bar" + end + + it "adds a box that doesn't exist" do + action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_name]).to eq(machine.config.vm.box) + expect(opts[:box_url]).to eq(machine.config.vm.box_url) + expect(opts[:box_provider]).to eq(:dummy) + expect(opts[:box_version]).to eq(machine.config.vm.box_version) + true + end + + app.should_receive(:call).with(env) + + subject.call(env) + end + end end From 38248c240c26c6c7f3ebaf9754bac8c0a1de511d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 11:24:06 -0800 Subject: [PATCH 52/90] core: BoxAdd errors if name doesn't match metadata --- lib/vagrant/action/builtin/box_add.rb | 6 ++++ lib/vagrant/errors.rb | 4 +++ templates/locales/en.yml | 9 +++++ .../vagrant/action/builtin/box_add_test.rb | 35 +++++++++++++++++++ 4 files changed, 54 insertions(+) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 407e1a8e2..f35b3f5ec 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -97,6 +97,12 @@ module Vagrant metadata_path.delete if metadata_path && metadata_path.file? end + if env[:box_name] && metadata.name != env[:box_name] + raise Errors::BoxAddNameMismatch, + actual_name: metadata.name, + requested_name: env[:box_name] + end + metadata_version = metadata.version( version || ">= 0", provider: provider) if !metadata_version diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 4474854b8..54a63ab26 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -120,6 +120,10 @@ module Vagrant error_key(:batch_multi_error) end + class BoxAddNameMismatch < VagrantError + error_key(:box_add_name_mismatch) + end + class BoxAddNameRequired < VagrantError error_key(:box_add_name_required) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index accb87fa0..4caa5da4f 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -289,6 +289,15 @@ en: Name: %{name} Provider: %{provider} Version: %{version} + box_add_name_mismatch: |- + The box you're adding has a name different from the name you + requested. For boxes with metadata, you cannot override the name. + If you're adding a box using `vagrant box add`, don't specify + the `--name` parameter. If the box is being added via a Vagrantfile, + change the `config.vm.box` value to match the name below. + + Requested name: %{requested_name} + Actual name: %{actual_name} box_add_name_required: |- A name is required when adding a box file directly. Please pass the `--name` parameter to `vagrant box add`. See diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index f761bb35d..e821d25ff 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -558,6 +558,41 @@ describe Vagrant::Action::Builtin::BoxAdd do subject.call(env) end + it "raises an exception if the name doesn't match a requested name" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + env[:box_name] = "foo" + env[:box_url] = tf.path + + box_collection.should_receive(:add).never + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxAddNameMismatch) + end + it "raises an exception if no matching version" do box_path = iso_env.box2_file(:vmware) tf = Tempfile.new("vagrant").tap do |f| From f0607c6df07040ea233ceea3251e1e6380afc7a0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 12:50:07 -0800 Subject: [PATCH 53/90] core: BoxAdd supports multiple URLs properly --- lib/vagrant/action/builtin/box_add.rb | 88 ++++++++++++++----- lib/vagrant/errors.rb | 4 + templates/locales/en.yml | 11 +++ .../vagrant/action/builtin/box_add_test.rb | 57 ++++++++++++ 4 files changed, 137 insertions(+), 23 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index f35b3f5ec..e185b82ea 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -22,30 +22,49 @@ module Vagrant def call(env) @download_interrupted = false - url = env[:box_url] + url = Array(env[:box_url]) # If we received a shorthand URL ("mitchellh/precise64"), # then expand it properly. expanded = false - uri = URI.parse(url) - if !uri.scheme && !File.file?(url) - expanded = true - url = "#{Vagrant.server_url}/#{url}" + url.each_index do |i| + uri = URI.parse(url[i]) + if !uri.scheme && !File.file?(url[i]) + expanded = true + url[i] = "#{Vagrant.server_url}/#{url[i]}" + end end - is_metadata = false - begin - is_metadata = metadata_url?(url, env) - rescue Errors::DownloaderError => e - raise if !expanded - raise Errors::BoxAddShortNotFound, - error: e.extra_data[:message], - name: env[:box_url], - url: url + # Test if any of our URLs point to metadata + is_metadata_results = url.map do |u| + begin + metadata_url?(u, env) + rescue Errors::DownloaderError => e + e + end + end + + if expanded && url.length == 1 + is_error = is_metadata_results.find do |b| + b.is_a?(Errors::DownloaderError) + end + + if is_error + raise Errors::BoxAddShortNotFound, + error: is_error.extra_data[:message], + name: env[:box_url], + url: url + end + end + + is_metadata = is_metadata_results.any? { |b| b === true } + if is_metadata && url.length > 1 + raise Errors::BoxAddMetadataMultiURL, + urls: url.join(", ") end if is_metadata - add_from_metadata(url, env) + add_from_metadata(url.first, env, expanded) else add_direct(url, env) end @@ -55,7 +74,10 @@ module Vagrant # Adds a box file directly (no metadata component, versioning, # etc.) - def add_direct(url, env) + # + # @param [Array] urls + # @param [Hash] env + def add_direct(urls, env) name = env[:box_name] if !name || name == "" raise Errors::BoxAddNameRequired @@ -65,7 +87,7 @@ module Vagrant provider = Array(provider) if provider box_add( - url, + urls, name, "0", provider, @@ -73,7 +95,7 @@ module Vagrant end # Adds a box given that the URL is a metadata document. - def add_from_metadata(url, env) + def add_from_metadata(url, env, expanded) original_url = env[:box_url] provider = env[:box_provider] provider = Array(provider) if provider @@ -93,6 +115,12 @@ module Vagrant File.open(metadata_path) do |f| metadata = BoxMetadata.new(f) end + rescue Errors::DownloaderError => e + raise if !expanded + raise Errors::BoxAddShortNotFound, + error: e.extra_data[:message], + name: original_url, + url: url ensure metadata_path.delete if metadata_path && metadata_path.file? end @@ -160,7 +188,7 @@ module Vagrant end box_add( - metadata_provider.url, + [metadata_provider.url], metadata.name, metadata_version.version, metadata_provider.name, env) @@ -232,13 +260,13 @@ module Vagrant # Shared helper to add a box once you know various details # about it. Shared between adding via metadata or by direct. # - # @param [String] url + # @param [Array] urls # @param [String] name # @param [String] version # @param [String] provider # @param [Hash] env # @return [Box] - def box_add(url, name, version, provider, env, **opts) + def box_add(urls, name, version, provider, env, **opts) env[:ui].output(I18n.t( "vagrant.box_add_with_version", name: name, @@ -260,7 +288,21 @@ module Vagrant # Now we have a URL, we have to download this URL. box = nil begin - box_url = download(url, env) + box_url = nil + + urls.each do |url| + env[:ui].detail(I18n.t( + "vagrant.box_downloading", url: url)) + + begin + box_url = download(url, env) + break + rescue Errors::DownloaderError => e + env[:ui].error(I18n.t( + "vagrant.box_download_error", message: e.message)) + box_url = nil + end + end # Add the box! box = env[:box_collection].add( @@ -273,7 +315,7 @@ module Vagrant # so we can resume the download later. if !@download_interrupted @logger.debug("Deleting temporary box: #{box_url}") - box_url.delete + box_url.delete if box_url end end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 54a63ab26..596f073d9 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -120,6 +120,10 @@ module Vagrant error_key(:batch_multi_error) end + class BoxAddMetadataMultiURL < VagrantError + error_key(:box_add_metadata_multi_url) + end + class BoxAddNameMismatch < VagrantError error_key(:box_add_name_mismatch) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 4caa5da4f..e0ce29031 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -20,6 +20,10 @@ en: Adding box '%{name}' (v%{version}) for '%{provider}' provider... box_added: |- Successfully added box '%{name}' for '%{provider}'! + box_downloading: |- + Downloading: %{url} + box_download_error: |- + Error downloading: %{message} box_expanding_url: |- URL: %{url} box_loading_metadata: |- @@ -289,6 +293,13 @@ en: Name: %{name} Provider: %{provider} Version: %{version} + box_add_metadata_multi_url: |- + Multiple URLs for a box can't be specified when adding + versioned boxes. Please specify a single URL to the box + metadata (JSON) information. The full list of URLs you + specified is shown below: + + %{urls} box_add_name_mismatch: |- The box you're adding has a name different from the name you requested. For boxes with metadata, you cannot override the name. diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index e821d25ff..152f60c84 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -77,6 +77,27 @@ describe Vagrant::Action::Builtin::BoxAdd do subject.call(env) end + it "adds from multiple URLs" do + box_path = iso_env.box2_file(:virtualbox) + + env[:box_name] = "foo" + env[:box_url] = [ + "/foo/bar/baz", + box_path.to_s, + ] + + box_collection.should_receive(:add).with do |path, name, version| + expect(checksum(path)).to eq(checksum(box_path)) + expect(name).to eq("foo") + expect(version).to eq("0") + true + end.and_return(box) + + app.should_receive(:call).with(env) + + subject.call(env) + end + it "adds from HTTP URL" do box_path = iso_env.box2_file(:virtualbox) with_web_server(box_path) do |port| @@ -252,6 +273,42 @@ describe Vagrant::Action::Builtin::BoxAdd do end end + it "raises an error if multiple metadata URLs are given" do + box_path = iso_env.box2_file(:virtualbox) + tf = Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "0.5" + }, + { + "version": "0.7", + "providers": [ + { + "name": "virtualbox", + "url": "#{box_path}" + } + ] + } + ] + } + RAW + f.close + end + + env[:box_url] = [ + "/foo/bar/baz", + tf.path, + ] + box_collection.should_receive(:add).never + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxAddMetadataMultiURL) + end + it "adds the latest version of a box with only one provider" do box_path = iso_env.box2_file(:virtualbox) tf = Tempfile.new("vagrant").tap do |f| From 8ab3ae1bef1df5854e919f92d04f49f490d35e66 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 12:54:31 -0800 Subject: [PATCH 54/90] core: BoxAdd UI looks better --- lib/vagrant/action/builtin/box_add.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index e185b82ea..0db0ba31d 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -102,7 +102,8 @@ module Vagrant version = env[:box_version] env[:ui].output(I18n.t( - "vagrant.box_loading_metadata", name: original_url)) + "vagrant.box_loading_metadata", + name: Array(original_url).first)) if original_url != url env[:ui].detail(I18n.t( "vagrant.box_expanding_url", url: url)) @@ -291,9 +292,6 @@ module Vagrant box_url = nil urls.each do |url| - env[:ui].detail(I18n.t( - "vagrant.box_downloading", url: url)) - begin box_url = download(url, env) break @@ -379,8 +377,8 @@ module Vagrant # path as an instance variable so that the `#recover` method can # access it. if opts[:ui] - env[:ui].info(I18n.t( - "vagrant.actions.box.download.downloading", + env[:ui].detail(I18n.t( + "vagrant.box_downloading", url: url)) if File.file?(d.destination) env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) From d40ff9c3903daaa3a1fdf48d78bf3aba22674e8e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 12:58:01 -0800 Subject: [PATCH 55/90] kernel_v2: test for box_url --- plugins/kernel_v2/config/vm.rb | 4 +--- templates/locales/en.yml | 1 - test/unit/plugins/kernel_v2/config/vm_test.rb | 19 ++++++++++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 55d8dc752..308e6a0af 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -329,9 +329,7 @@ module VagrantPlugins end # Make sure the box URL is an array if it is set - if @box_url && !@box_url.is_a?(Array) - @box_url = [@box_url] - end + @box_url = Array(@box_url) if @box_url # Set the guest properly @guest = @guest.to_sym if @guest diff --git a/templates/locales/en.yml b/templates/locales/en.yml index e0ce29031..0a7763c43 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1329,7 +1329,6 @@ en: destroying: "Deleting box '%{name}'..." download: cleaning: "Cleaning up downloaded box..." - downloading: "Downloading box from URL: %{url}" download_failed: |- Download failed. Will try another box URL if there is one. interrupted: "Box download was interrupted. Exiting." diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index 9eaf0473c..205597f09 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -33,10 +33,27 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end describe "#box_url" do - it "defaults properly" do + it "defaults to nil" do subject.finalize! + expect(subject.box_url).to be_nil end + + it "turns into an array" do + subject.box_url = "foo" + subject.finalize! + + expect(subject.box_url).to eq( + ["foo"]) + end + + it "keeps in array" do + subject.box_url = ["foo", "bar"] + subject.finalize! + + expect(subject.box_url).to eq( + ["foo", "bar"]) + end end context "#box_version" do From fa77d6810f81a13dc6d1ab1aa35cc64376e053b5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 13:10:27 -0800 Subject: [PATCH 56/90] core: BoxAdd expands URL paths --- lib/vagrant/action/builtin/box_add.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 0db0ba31d..8a5b011f7 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -22,7 +22,16 @@ module Vagrant def call(env) @download_interrupted = false - url = Array(env[:box_url]) + url = Array(env[:box_url]).map do |u| + next u if u =~ /^[a-z0-9]+:.*$/i + + # Expand the path and try to use that, if possible + p = File.expand_path(u) + p = Util::Platform.cygwin_windows_path(p) + next p if File.file?(p) + + u + end # If we received a shorthand URL ("mitchellh/precise64"), # then expand it properly. From 2ea6f92a6c51a5311aa7fea0f2adcafd10b411ca Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 13:12:11 -0800 Subject: [PATCH 57/90] core: Cleaner output for BoxAdd --- lib/vagrant/action/builtin/box_add.rb | 2 +- templates/locales/en.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 8a5b011f7..8ea51e9f2 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -281,7 +281,7 @@ module Vagrant "vagrant.box_add_with_version", name: name, version: version, - provider: provider)) + providers: Array(provider).join(", "))) # Verify the box we're adding doesn't already exist if provider && !env[:box_force] diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 0a7763c43..e5e1da6e2 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -17,7 +17,7 @@ en: box_add_choose_provider_again: |- Invalid choice. Try again: box_add_with_version: |- - Adding box '%{name}' (v%{version}) for '%{provider}' provider... + Adding box '%{name}' (v%{version}) for provider: %{providers} box_added: |- Successfully added box '%{name}' for '%{provider}'! box_downloading: |- From d278fb266123e98a586be9bfbdaece96dfe7fefe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 13:16:07 -0800 Subject: [PATCH 58/90] core: catch ENOENT as not a metadata URL --- lib/vagrant/action/builtin/box_add.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 8ea51e9f2..105080bbc 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -433,6 +433,8 @@ module Vagrant return true rescue Errors::BoxMetadataMalformed return false + rescue Errno::ENOENT + return false end end From 4cedd590d9ca51fd98dfa52135a904679e948313 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 13:21:19 -0800 Subject: [PATCH 59/90] providers/virtualbox: use new HandleBox middleware --- plugins/providers/virtualbox/action.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index a0297cb83..34b1b08e0 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -302,7 +302,7 @@ module VagrantPlugins # works fine. b.use Call, Created do |env, b2| if !env[:result] - b2.use HandleBoxUrl + b2.use HandleBox end end From e9afe386c1a80a53f19cd638a2466f74e9a64048 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 13:23:26 -0800 Subject: [PATCH 60/90] core: remove BoxUpgradeRequired exception --- lib/vagrant/box_collection.rb | 2 -- lib/vagrant/environment.rb | 10 ++-------- lib/vagrant/errors.rb | 4 ---- templates/locales/en.yml | 4 ---- 4 files changed, 2 insertions(+), 18 deletions(-) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index c299ab162..3765d2f47 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -56,8 +56,6 @@ module Vagrant # * BoxProviderDoesntMatch - If the given box provider doesn't match the # actual box provider in the untarred box. # * BoxUnpackageFailure - An invalid tar file. - # * BoxUpgradeRequired - You're attempting to add a box when there is a - # V1 box with the same name that must first be upgraded. # # Preconditions: # * File given in `path` must exist. diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index db0d79812..38ced5377 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -348,14 +348,8 @@ module Vagrant load_box_and_overrides = lambda do box = nil if config.vm.box - begin - box = boxes.find(config.vm.box, box_formats, config.vm.box_version) - rescue Errors::BoxUpgradeRequired - # Upgrade the box if we must - @logger.info("Upgrading box during config load: #{config.vm.box}") - boxes.upgrade(config.vm.box) - retry - end + box = boxes.find( + config.vm.box, box_formats, config.vm.box_version) end # If a box was found, then we attempt to load the Vagrantfile for diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 596f073d9..e42a7d402 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -196,10 +196,6 @@ module Vagrant error_key(:untar_failure, "vagrant.actions.box.unpackage") end - class BoxUpgradeRequired < VagrantError - error_key(:box_upgrade_required) - end - class BoxVerificationFailed < VagrantError error_key(:failed, "vagrant.actions.box.verify") end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index e5e1da6e2..90969d57a 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -376,10 +376,6 @@ en: the provider specified. Please double-check and try again. The providers for this are: %{providers} - box_upgrade_required: |- - 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_disabled: |- Vagrant's built-in bundler management mechanism is disabled because Vagrant is running in an external bundler environment. In these From bd9f375263caecaa5c4f4cc2b93822bfad24e140 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 13:40:11 -0800 Subject: [PATCH 61/90] core: Box can store a metadata URL --- lib/vagrant/box.rb | 9 ++++++++- lib/vagrant/box_collection.rb | 22 +++++++++++++++++++--- test/unit/vagrant/box_collection_test.rb | 13 ++++++++++++- test/unit/vagrant/box_test.rb | 12 ++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/lib/vagrant/box.rb b/lib/vagrant/box.rb index 254c0c393..2b848da7c 100644 --- a/lib/vagrant/box.rb +++ b/lib/vagrant/box.rb @@ -39,17 +39,24 @@ module Vagrant # @return [Hash] attr_reader :metadata + # This is the URL to the version info and other metadata for this + # box. + # + # @return [String] + attr_reader :metadata_url + # This is used to initialize a box. # # @param [String] name Logical name of the box. # @param [Symbol] provider The provider that this box implements. # @param [Pathname] directory The directory where this box exists on # disk. - def initialize(name, provider, version, directory) + def initialize(name, provider, version, directory, **opts) @name = name @version = version @provider = provider @directory = directory + @metadata_url = opts[:metadata_url] metadata_file = directory.join("metadata.json") raise Errors::BoxMetadataFileNotFound, :name => @name if !metadata_file.file? diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index 3765d2f47..fc5e1eda6 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -151,7 +151,8 @@ module Vagrant provider = box_provider.to_sym # Create the directory for this box, not including the provider - box_dir = @directory.join(dir_name(name), version) + root_box_dir = @directory.join(dir_name(name)) + box_dir = root_box_dir.join(version) box_dir.mkpath @logger.debug("Box directory: #{box_dir}") @@ -173,6 +174,12 @@ module Vagrant @logger.debug("Moving: #{f} => #{destination}") FileUtils.mv(f, destination) end + + if opts[:metadata_url] + root_box_dir.join("metadata_url").open("w") do |f| + f.write(opts[:metadata_url]) + end + end end end end @@ -244,9 +251,10 @@ module Vagrant end versions = box_directory.children(true).map do |versiondir| + next if !versiondir.directory? version = versiondir.basename.to_s Gem::Version.new(version) - end + end.compact # Traverse through versions with the latest version first versions.sort.reverse.each do |v| @@ -260,7 +268,15 @@ module Vagrant provider_dir = versiondir.join(provider.to_s) next if !provider_dir.directory? @logger.info("Box found: #{name} (#{provider})") - return Box.new(name, provider, v.to_s, provider_dir) + + metadata_url = nil + metadata_url_file = box_directory.join("metadata_url") + metadata_url = metadata_url_file.read if metadata_url_file.file? + + return Box.new( + name, provider, v.to_s, provider_dir, + metadata_url: metadata_url, + ) end end end diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index d2ac1dac1..7cdb242bd 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -97,7 +97,7 @@ describe Vagrant::BoxCollection do end end - describe "adding" do + describe "#add" do it "should add a valid box to the system" do box_path = environment.box2_file(:virtualbox) @@ -134,6 +134,17 @@ describe Vagrant::BoxCollection do expect(box.provider).to eq(:vmware) end + it "should store a metadata URL" do + box_path = environment.box2_file(:virtualbox) + + subject.add( + box_path, "foo", "1.0", + metadata_url: "bar") + + box = subject.find("foo", :virtualbox, "1.0") + expect(box.metadata_url).to eq("bar") + end + it "should add a V1 box" do # Create a V1 box. box_path = environment.box1_file diff --git a/test/unit/vagrant/box_test.rb b/test/unit/vagrant/box_test.rb index a540476f7..6832e3ca4 100644 --- a/test/unit/vagrant/box_test.rb +++ b/test/unit/vagrant/box_test.rb @@ -15,6 +15,8 @@ describe Vagrant::Box do let(:directory) { environment.box3("foo", "1.0", :virtualbox) } subject { described_class.new(name, provider, version, directory) } + its(:metadata_url) { should be_nil } + it "provides the name" do subject.name.should == name end @@ -39,6 +41,16 @@ describe Vagrant::Box do subject.metadata.should == data end + context "with a metadata URL" do + subject do + described_class.new( + name, provider, version, directory, + metadata_url: "foo") + end + + its(:metadata_url) { should eq("foo") } + end + context "with a corrupt metadata file" do before do directory.join("metadata.json").open("w") do |f| From b81686e2c9f54b66ac8c7821c066f0bd12934627 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 13:44:50 -0800 Subject: [PATCH 62/90] core: BoxAdd adds the metadata URL --- lib/vagrant/action/builtin/box_add.rb | 8 +++- .../vagrant/action/builtin/box_add_test.rb | 43 +++++++++++++------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 105080bbc..c5bca8721 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -100,6 +100,7 @@ module Vagrant name, "0", provider, + nil, env) end @@ -201,7 +202,9 @@ module Vagrant [metadata_provider.url], metadata.name, metadata_version.version, - metadata_provider.name, env) + metadata_provider.name, + url, + env) end =begin @@ -276,7 +279,7 @@ module Vagrant # @param [String] provider # @param [Hash] env # @return [Box] - def box_add(urls, name, version, provider, env, **opts) + def box_add(urls, name, version, provider, md_url, env, **opts) env[:ui].output(I18n.t( "vagrant.box_add_with_version", name: name, @@ -315,6 +318,7 @@ module Vagrant box = env[:box_collection].add( box_url, name, version, force: env[:box_force], + metadata_url: md_url, providers: provider) ensure # Make sure we delete the temporary file after we add it, diff --git a/test/unit/vagrant/action/builtin/box_add_test.rb b/test/unit/vagrant/action/builtin/box_add_test.rb index 152f60c84..ccd52ae58 100644 --- a/test/unit/vagrant/action/builtin/box_add_test.rb +++ b/test/unit/vagrant/action/builtin/box_add_test.rb @@ -65,10 +65,11 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:box_name] = "foo" env[:box_url] = box_path.to_s - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") + expect(opts[:metadata_url]).to be_nil true end.and_return(box) @@ -86,10 +87,11 @@ describe Vagrant::Action::Builtin::BoxAdd do box_path.to_s, ] - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") + expect(opts[:metadata_url]).to be_nil true end.and_return(box) @@ -104,10 +106,11 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:box_name] = "foo" env[:box_url] = "http://127.0.0.1:#{port}/#{box_path.basename}" - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") + expect(opts[:metadata_url]).to be_nil true end.and_return(box) @@ -154,10 +157,11 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:box_provider] = "virtualbox" box_collection.stub(find: box) - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo") expect(version).to eq("0") + expect(opts[:metadata_url]).to be_nil true end.and_return(box) app.should_receive(:call).with(env).once @@ -196,10 +200,11 @@ describe Vagrant::Action::Builtin::BoxAdd do with_web_server(md_path) do |port| env[:box_url] = "http://127.0.0.1:#{port}/#{md_path.basename}" - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(checksum(path)).to eq(checksum(box_path)) + expect(opts[:metadata_url]).to eq(env[:box_url]) true end.and_return(box) @@ -237,18 +242,20 @@ describe Vagrant::Action::Builtin::BoxAdd do end with_web_server(tf.dirname) do |port| + url = "http://127.0.0.1:#{port}" env[:box_url] = "mitchellh/precise64.json" - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(name).to eq("mitchellh/precise64") expect(version).to eq("0.7") expect(checksum(path)).to eq(checksum(box_path)) + expect(opts[:metadata_url]).to eq( + "#{url}/#{env[:box_url]}") true end.and_return(box) app.should_receive(:call).with(env) - url = "http://127.0.0.1:#{port}" with_temp_env("VAGRANT_SERVER_URL" => url) do subject.call(env) end @@ -335,10 +342,11 @@ describe Vagrant::Action::Builtin::BoxAdd do end env[:box_url] = tf.path - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") + expect(opts[:metadata_url]).to eq(tf.path) true end.and_return(box) @@ -378,10 +386,11 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:box_url] = tf.path env[:box_provider] = "vmware" - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") + expect(opts[:metadata_url]).to eq(tf.path) true end.and_return(box) @@ -426,10 +435,11 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:box_url] = tf.path env[:box_provider] = "vmware" - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") + expect(opts[:metadata_url]).to eq(tf.path) true end.and_return(box) @@ -465,10 +475,11 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:box_url] = tf.path env[:box_version] = "~> 0.1" - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.5") + expect(opts[:metadata_url]).to eq(tf.path) true end.and_return(box) @@ -509,10 +520,11 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:box_url] = tf.path env[:box_provider] = "vmware" env[:box_version] = "~> 0.1" - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.5") + expect(opts[:metadata_url]).to eq(tf.path) true end.and_return(box) @@ -556,10 +568,11 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:box_url] = tf.path env[:box_provider] = ["virtualbox", "vmware"] - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") + expect(opts[:metadata_url]).to eq(tf.path) true end.and_return(box) @@ -603,10 +616,11 @@ describe Vagrant::Action::Builtin::BoxAdd do env[:ui].should_receive(:ask).and_return("1") - box_collection.should_receive(:add).with do |path, name, version| + box_collection.should_receive(:add).with do |path, name, version, **opts| expect(checksum(path)).to eq(checksum(box_path)) expect(name).to eq("foo/bar") expect(version).to eq("0.7") + expect(opts[:metadata_url]).to eq(tf.path) true end.and_return(box) @@ -783,6 +797,7 @@ describe Vagrant::Action::Builtin::BoxAdd do expect(name).to eq("foo/bar") expect(version).to eq("0.7") expect(opts[:force]).to be_true + expect(opts[:metadata_url]).to eq(tf.path) true end.and_return(box) From f2509f5c65ad28140f1c4c2a82687a00b99ecd06 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 14:14:05 -0800 Subject: [PATCH 63/90] commands/box: outdated command to find outdated boxes --- lib/vagrant/box.rb | 24 +++++++++- lib/vagrant/box_collection.rb | 2 + lib/vagrant/box_metadata.rb | 2 +- plugins/commands/box/command/outdated.rb | 59 ++++++++++++++++++++++++ plugins/commands/box/command/root.rb | 5 ++ templates/locales/en.yml | 8 ++++ test/unit/vagrant/box_test.rb | 27 +++++++++++ 7 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 plugins/commands/box/command/outdated.rb diff --git a/lib/vagrant/box.rb b/lib/vagrant/box.rb index 2b848da7c..c09d59424 100644 --- a/lib/vagrant/box.rb +++ b/lib/vagrant/box.rb @@ -1,8 +1,11 @@ require 'fileutils' +require "tempfile" require "json" require "log4r" +require "vagrant/box_metadata" +require "vagrant/util/downloader" require "vagrant/util/platform" require "vagrant/util/safe_chdir" require "vagrant/util/subprocess" @@ -82,6 +85,25 @@ module Vagrant return true end + # Loads the metadata URL and returns the latest metadata associated + # with this box. + # + # @return [BoxMetadata] + def load_metadata + tf = Tempfile.new("vagrant") + tf.close + + url = @metadata_url + if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i + url = File.expand_path(url) + url = Util::Platform.cygwin_windows_path(url) + url = "file:#{url}" + end + + Util::Downloader.new(url, tf.path).download! + BoxMetadata.new(File.open(tf.path, "r")) + end + # This repackages this box and outputs it to the given path. # # @param [Pathname] path The full path (filename included) of where @@ -110,7 +132,7 @@ module Vagrant # Comparison is done by composing the name and provider "#{@name}-#{@version}-#{@provider}" <=> - "#{other.name}-#{other.version}-#{other.provider}" + "#{other.name}-#{other.version}-#{other.provider}" end end end diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index fc5e1eda6..62f942389 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -208,6 +208,8 @@ module Vagrant # Otherwise, traverse the subdirectories and see what versions # we have. child.children(true).each do |versiondir| + next if !versiondir.directory? + version = versiondir.basename.to_s versiondir.children(true).each do |provider| diff --git a/lib/vagrant/box_metadata.rb b/lib/vagrant/box_metadata.rb index 14ac62ee7..0366246f1 100644 --- a/lib/vagrant/box_metadata.rb +++ b/lib/vagrant/box_metadata.rb @@ -29,7 +29,7 @@ module Vagrant @name = @raw["name"] @description = @raw["description"] - @version_map = @raw["versions"].map do |v| + @version_map = (@raw["versions"] || []).map do |v| [Gem::Version.new(v["version"]), v] end @version_map = Hash[@version_map] diff --git a/plugins/commands/box/command/outdated.rb b/plugins/commands/box/command/outdated.rb new file mode 100644 index 000000000..818a478a9 --- /dev/null +++ b/plugins/commands/box/command/outdated.rb @@ -0,0 +1,59 @@ +require 'optparse' + +module VagrantPlugins + module CommandBox + module Command + class Outdated < Vagrant.plugin("2", :command) + def execute + OptionParser.new do |o| + o.banner = "Usage: vagrant box outdated" + end + + boxes = {} + @env.boxes.all.reverse.each do |name, version, provider| + next if boxes[name] + boxes[name] = @env.boxes.find(name, provider, version) + end + + boxes.values.each do |box| + if !box.metadata_url + @env.ui.output(I18n.t( + "vagrant.box_outdated_no_metadata", + name: box.name)) + next + end + + md = nil + begin + md = box.load_metadata + rescue Vagrant::Errors::DownloaderError => e + @env.ui.error(I18n.t( + "vagrant.box_outdated_metadata_error", + name: box.name, + message: e.extra_data[:message])) + next + end + + current = Gem::Version.new(box.version) + latest = Gem::Version.new(md.versions.last) + if latest <= current + @env.ui.success(I18n.t( + "vagrant.box_up_to_date", + name: box.name, + version: box.version)) + else + @env.ui.warn(I18n.t( + "vagrant.box_outdated", + name: box.name, + current: box.version, + latest: latest.to_s,)) + end + end + + # Success, exit status 0 + 0 + end + end + end + end +end diff --git a/plugins/commands/box/command/root.rb b/plugins/commands/box/command/root.rb index 4150fd659..96f599308 100644 --- a/plugins/commands/box/command/root.rb +++ b/plugins/commands/box/command/root.rb @@ -24,6 +24,11 @@ module VagrantPlugins List end + @subcommands.register(:outdated) do + require_relative "outdated" + Outdated + end + @subcommands.register(:remove) do require File.expand_path("../remove", __FILE__) Remove diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 90969d57a..43398e490 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -28,6 +28,14 @@ en: URL: %{url} box_loading_metadata: |- Loading metadata for box '%{name}' + box_outdated: |- + * '%{name}' is outdated! Current: %{current}. Latest: %{latest} + box_outdated_metadata_error: |- + * '%{name}': Error loading metadata: %{message} + box_outdated_no_metadata: |- + * '%{name}' wasn't added from a catalog, no version information + box_up_to_date: |- + * '%{name}' (v%{version}) is up to date cfengine_bootstrapping: |- Bootstrapping CFEngine with policy server: %{policy_server}... cfengine_bootstrapping_policy_hub: |- diff --git a/test/unit/vagrant/box_test.rb b/test/unit/vagrant/box_test.rb index 6832e3ca4..d682875be 100644 --- a/test/unit/vagrant/box_test.rb +++ b/test/unit/vagrant/box_test.rb @@ -1,6 +1,7 @@ require File.expand_path("../../base", __FILE__) require "pathname" +require "tempfile" describe Vagrant::Box do include_context "unit" @@ -75,6 +76,32 @@ describe Vagrant::Box do end end + context "#load_metadata" do + let(:metadata_url) do + Tempfile.new("vagrant").tap do |f| + f.write(<<-RAW) + { + "name": "foo", + "description": "bar" + } + RAW + f.close + end + end + + subject do + described_class.new( + name, provider, version, directory, + metadata_url: metadata_url.path) + end + + it "loads the url and returns the data" do + result = subject.load_metadata + expect(result.name).to eq("foo") + expect(result.description).to eq("bar") + end + end + describe "destroying" do it "should destroy an existing box" do # Verify that our "box" exists From e537e02d9d829e71a0f818a8659a805b93d9de14 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 14:50:55 -0800 Subject: [PATCH 64/90] core: BoxCheckOutdated can check if a box is outdated --- lib/vagrant/action.rb | 9 + .../action/builtin/box_check_outdated.rb | 65 +++++++ lib/vagrant/errors.rb | 8 + plugins/commands/box/command/outdated.rb | 29 +++- templates/locales/en.yml | 15 ++ .../action/builtin/box_check_outdated_test.rb | 159 ++++++++++++++++++ 6 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 lib/vagrant/action/builtin/box_check_outdated.rb create mode 100644 test/unit/vagrant/action/builtin/box_check_outdated_test.rb diff --git a/lib/vagrant/action.rb b/lib/vagrant/action.rb index 627be5ab2..d32a34b99 100644 --- a/lib/vagrant/action.rb +++ b/lib/vagrant/action.rb @@ -9,6 +9,7 @@ module Vagrant # and are thus available to all plugins as a "standard library" of sorts. module Builtin autoload :BoxAdd, "vagrant/action/builtin/box_add" + autoload :BoxCheckOutdated, "vagrant/action/builtin/box_check_outdated" autoload :BoxRemove, "vagrant/action/builtin/box_remove" autoload :Call, "vagrant/action/builtin/call" autoload :Confirm, "vagrant/action/builtin/confirm" @@ -45,6 +46,14 @@ module Vagrant end end + # This actions checks if a box is outdated in a given Vagrant + # environment for a single machine. + def self.action_box_outdated + Builder.new.tap do |b| + b.use Builtin::BoxCheckOutdated + end + end + # This is the action that will remove a box given a name (and optionally # a provider). This middleware sequence is built-in to Vagrant. Plugins # can hook into this like any other middleware sequence. diff --git a/lib/vagrant/action/builtin/box_check_outdated.rb b/lib/vagrant/action/builtin/box_check_outdated.rb new file mode 100644 index 000000000..c33e02605 --- /dev/null +++ b/lib/vagrant/action/builtin/box_check_outdated.rb @@ -0,0 +1,65 @@ +require "digest/sha1" +require "log4r" +require "pathname" +require "uri" + +require "vagrant/box_metadata" +require "vagrant/util/downloader" +require "vagrant/util/file_checksum" +require "vagrant/util/platform" + +module Vagrant + module Action + module Builtin + # This middleware checks if there are outdated boxes. By default, + # it only checks locally, but if `box_outdated_refresh` is set, it + # will refresh the metadata associated with a box. + class BoxCheckOutdated + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new( + "vagrant::action::builtin::box_check_outdated") + end + + def call(env) + machine = env[:machine] + + if !machine.box + # The box doesn't exist. I suppose technically that means + # that it is "outdated" but we show a specialized error + # message anyways. + raise Errors::BoxOutdatedNoBox, name: machine.config.vm.box + end + + if !machine.box.metadata_url + # This box doesn't have a metadata URL, so we can't + # possibly check the version information. + raise Errors::BoxOutdatedNoMetadata, name: machine.box.name + end + + md = machine.box.load_metadata + newer = md.version( + "> #{machine.box.version}", provider: machine.box.provider) + if !newer + env[:ui].success(I18n.t( + "vagrant.box_up_to_date_single", + name: machine.box.name, + version: machine.box.version)) + + env[:box_outdated] = false + return @app.call(env) + end + + env[:ui].warn(I18n.t( + "vagrant.box_outdated_single", + name: machine.box.name, + current: machine.box.version, + latest: newer.version)) + env[:box_outdated] = true + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index e42a7d402..516caea49 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -172,6 +172,14 @@ module Vagrant error_key(:box_metadata_malformed) end + class BoxOutdatedNoBox < VagrantError + error_key(:box_outdated_no_box) + end + + class BoxOutdatedNoMetadata < VagrantError + error_key(:box_outdated_no_metadata) + end + class BoxProviderDoesntMatch < VagrantError error_key(:box_provider_doesnt_match) end diff --git a/plugins/commands/box/command/outdated.rb b/plugins/commands/box/command/outdated.rb index 818a478a9..00bb0c848 100644 --- a/plugins/commands/box/command/outdated.rb +++ b/plugins/commands/box/command/outdated.rb @@ -5,10 +5,34 @@ module VagrantPlugins module Command class Outdated < Vagrant.plugin("2", :command) def execute - OptionParser.new do |o| + options = {} + + opts = OptionParser.new do |o| o.banner = "Usage: vagrant box outdated" + o.separator "" + + o.on("--global", "Check all boxes installed.") do |g| + options[:global] = g + end end + argv = parse_options(opts) + + # If we're checking the boxes globally, then do that. + if options[:global] + outdated_global + return 0 + end + + with_target_vms(argv) do |machine| + @env.action_runner.run(Vagrant::Action.action_box_outdated, { + box_outdated_refresh: true, + machine: machine, + }) + end + end + + def outdated_global boxes = {} @env.boxes.all.reverse.each do |name, version, provider| next if boxes[name] @@ -49,9 +73,6 @@ module VagrantPlugins latest: latest.to_s,)) end end - - # Success, exit status 0 - 0 end end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 43398e490..1a0c7d7df 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -30,12 +30,18 @@ en: Loading metadata for box '%{name}' box_outdated: |- * '%{name}' is outdated! Current: %{current}. Latest: %{latest} + box_outdated_single: |- + A newer version of the box '%{name}' is available! You currently + have version '%{current}'. The latest is version '%{latest}'. Run + `vagrant box update` to update. box_outdated_metadata_error: |- * '%{name}': Error loading metadata: %{message} box_outdated_no_metadata: |- * '%{name}' wasn't added from a catalog, no version information box_up_to_date: |- * '%{name}' (v%{version}) is up to date + box_up_to_date_single: |- + Your box '%{name}' (v%{version}) is running the latest version. cfengine_bootstrapping: |- Bootstrapping CFEngine with policy server: %{policy_server}... cfengine_bootstrapping_policy_hub: |- @@ -357,6 +363,15 @@ en: that this issue can be fixed. %{error} + box_outdated_no_metadata: |- + The box '%{name}' is not a versioned box. The box was added + directly instead of from a box catalog. Vagrant can only + check the versions of boxes that were added from a catalog + such as from the public Vagrant Server. + 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 + with `vagrant box add` to download an appropriate version. box_provider_doesnt_match: |- The box you attempted to add doesn't match the provider you specified. diff --git a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb new file mode 100644 index 000000000..651134b6c --- /dev/null +++ b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb @@ -0,0 +1,159 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Action::Builtin::BoxCheckOutdated do + include_context "unit" + + let(:app) { lambda { |env| } } + let(:env) { { + machine: machine, + } } + + subject { described_class.new(app, env) } + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + isolated_environment.tap do |env| + env.vagrantfile("") + end + end + + let(:iso_vagrant_env) { iso_env.create_vagrant_env } + + let(:box) do + box_dir = iso_env.box3("foo", "1.0", :virtualbox) + Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) + end + let(:machine) { iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy) } + + context "no box" do + it "raises an exception if the machine doesn't have a box yet" do + machine.stub(box: nil) + + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxOutdatedNoBox) + end + end + + context "box with no metadata_url" do + let(:box) do + box_dir = iso_env.box3("foo", "1.0", :virtualbox) + Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) + end + + before do + machine.stub(box: box) + end + + it "raises an exception" do + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxOutdatedNoMetadata) + end + end + + context "with a metadata URL" do + let(:metadata_url) do + Tempfile.new("vagrant").tap do |f| + f.close + end + end + + let(:box_dir) { iso_env.box3("foo", "1.0", :virtualbox) } + + it "isn't outdated if it isn't" do + File.open(metadata_url.path, "w") do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "1.0", + "providers": [ + { + "name": "virtualbox", + "url": "#{iso_env.box2_file(:virtualbox)}" + } + ] + } + ] + } + RAW + end + + box = Vagrant::Box.new( + "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) + machine.stub(box: box) + + subject.call(env) + + expect(env[:box_outdated]).to be_false + end + + it "is outdated if it is" do + File.open(metadata_url.path, "w") do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.5", + "providers": [ + { + "name": "virtualbox", + "url": "#{iso_env.box2_file(:virtualbox)}" + } + ] + } + ] + } + RAW + end + + box = Vagrant::Box.new( + "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) + machine.stub(box: box) + + subject.call(env) + + expect(env[:box_outdated]).to be_true + end + + it "isn't outdated if the newer box is for another provider" do + File.open(metadata_url.path, "w") do |f| + f.write(<<-RAW) + { + "name": "foo/bar", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.5", + "providers": [ + { + "name": "vmware", + "url": "#{iso_env.box2_file(:vmware)}" + } + ] + } + ] + } + RAW + end + + box = Vagrant::Box.new( + "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) + machine.stub(box: box) + + subject.call(env) + + expect(env[:box_outdated]).to be_false + end + end +end From d78194654deeca07ccbecbf713b3b15b0a2dc602 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 14:56:41 -0800 Subject: [PATCH 65/90] core: ability to hide success UI --- .../action/builtin/box_check_outdated.rb | 10 ++-- plugins/commands/box/command/outdated.rb | 1 + .../action/builtin/box_check_outdated_test.rb | 46 +++++++++++++++---- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/lib/vagrant/action/builtin/box_check_outdated.rb b/lib/vagrant/action/builtin/box_check_outdated.rb index c33e02605..0399a1686 100644 --- a/lib/vagrant/action/builtin/box_check_outdated.rb +++ b/lib/vagrant/action/builtin/box_check_outdated.rb @@ -41,10 +41,12 @@ module Vagrant newer = md.version( "> #{machine.box.version}", provider: machine.box.provider) if !newer - env[:ui].success(I18n.t( - "vagrant.box_up_to_date_single", - name: machine.box.name, - version: machine.box.version)) + if env[:box_outdated_success_ui] + env[:ui].success(I18n.t( + "vagrant.box_up_to_date_single", + name: machine.box.name, + version: machine.box.version)) + end env[:box_outdated] = false return @app.call(env) diff --git a/plugins/commands/box/command/outdated.rb b/plugins/commands/box/command/outdated.rb index 00bb0c848..aec4c94c7 100644 --- a/plugins/commands/box/command/outdated.rb +++ b/plugins/commands/box/command/outdated.rb @@ -27,6 +27,7 @@ module VagrantPlugins with_target_vms(argv) do |machine| @env.action_runner.run(Vagrant::Action.action_box_outdated, { box_outdated_refresh: true, + box_outdated_success_ui: true, machine: machine, }) end diff --git a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb index 651134b6c..e0ff697a7 100644 --- a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb +++ b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb @@ -6,6 +6,7 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do let(:app) { lambda { |env| } } let(:env) { { machine: machine, + ui: Vagrant::UI::Silent.new, } } subject { described_class.new(app, env) } @@ -63,9 +64,10 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do let(:box_dir) { iso_env.box3("foo", "1.0", :virtualbox) } - it "isn't outdated if it isn't" do - File.open(metadata_url.path, "w") do |f| - f.write(<<-RAW) + context "isn't outdated" do + before do + File.open(metadata_url.path, "w") do |f| + f.write(<<-RAW) { "name": "foo/bar", "versions": [ @@ -80,16 +82,42 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do } ] } - RAW + RAW + end + + box = Vagrant::Box.new( + "foo", :virtualbox, "1.0", box_dir, + metadata_url: metadata_url.path) + machine.stub(box: box) end - box = Vagrant::Box.new( - "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) - machine.stub(box: box) + it "marks it isn't outdated" do + app.should_receive(:call).with(env) - subject.call(env) + subject.call(env) - expect(env[:box_outdated]).to be_false + expect(env[:box_outdated]).to be_false + end + + it "talks to the UI" do + env[:box_outdated_success_ui] = true + + app.should_receive(:call).with(env) + env[:ui].should_receive(:success) + + subject.call(env) + + expect(env[:box_outdated]).to be_false + end + + it "doesn't talk to UI if it is told" do + app.should_receive(:call).with(env) + env[:ui].should_receive(:success).never + + subject.call(env) + + expect(env[:box_outdated]).to be_false + end end it "is outdated if it is" do From 8cc16d14fafbeb84c6df074d875d4ef1365a8f83 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 15:17:41 -0800 Subject: [PATCH 66/90] core: BoxCheckOutdated checks locally --- .../action/builtin/box_check_outdated.rb | 34 +++- templates/locales/en.yml | 5 + .../action/builtin/box_check_outdated_test.rb | 191 +++++++++++------- 3 files changed, 149 insertions(+), 81 deletions(-) diff --git a/lib/vagrant/action/builtin/box_check_outdated.rb b/lib/vagrant/action/builtin/box_check_outdated.rb index 0399a1686..eecad9911 100644 --- a/lib/vagrant/action/builtin/box_check_outdated.rb +++ b/lib/vagrant/action/builtin/box_check_outdated.rb @@ -31,6 +31,36 @@ module Vagrant raise Errors::BoxOutdatedNoBox, name: machine.config.vm.box end + if env[:box_outdated_refresh] + @logger.info( + "Checking if box is outdated by refreshing metadata") + check_outdated_refresh(env) + else + @logger.info("Checking if box is outdated locally") + check_outdated_local(env) + end + + @app.call(env) + end + + def check_outdated_local(env) + machine = env[:machine] + box = env[:box_collection].find( + machine.box.name, machine.box.provider, + "> #{machine.box.version}") + if box + env[:ui].warn(I18n.t( + "vagrant.box_outdated_local", + name: box.name, + old: machine.box.version, + new: box.version)) + env[:box_outdated] = true + end + end + + def check_outdated_refresh(env) + machine = env[:machine] + if !machine.box.metadata_url # This box doesn't have a metadata URL, so we can't # possibly check the version information. @@ -49,7 +79,7 @@ module Vagrant end env[:box_outdated] = false - return @app.call(env) + return end env[:ui].warn(I18n.t( @@ -58,8 +88,6 @@ module Vagrant current: machine.box.version, latest: newer.version)) env[:box_outdated] = true - - @app.call(env) end end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 1a0c7d7df..f41d6e559 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -30,6 +30,11 @@ en: Loading metadata for box '%{name}' box_outdated: |- * '%{name}' is outdated! Current: %{current}. Latest: %{latest} + box_outdated_local: |- + A newer version of the box '%{name}' is available and already + installed, but your Vagrant machine is running against + version '%{old}'. To update to version '%{new}', + destroy and recreate your machine. box_outdated_single: |- A newer version of the box '%{name}' is available! You currently have version '%{current}'. The latest is version '%{latest}'. Run diff --git a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb index e0ff697a7..3c08777c6 100644 --- a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb +++ b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb @@ -5,6 +5,7 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do let(:app) { lambda { |env| } } let(:env) { { + box_collection: iso_vagrant_env.boxes, machine: machine, ui: Vagrant::UI::Silent.new, } } @@ -37,37 +38,70 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do end end - context "box with no metadata_url" do - let(:box) do - box_dir = iso_env.box3("foo", "1.0", :virtualbox) - Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) - end - + context "without refreshing" do before do + env[:box_outdated_refresh] = false + machine.stub(box: box) end - it "raises an exception" do - app.should_receive(:call).never + it "isn't outdated if there are no newer boxes" do + iso_env.box3("foo", "0.5", :virtualbox) - expect { subject.call(env) }. - to raise_error(Vagrant::Errors::BoxOutdatedNoMetadata) + app.should_receive(:call).with(env) + + subject.call(env) + + expect(env[:box_outdated]).to be_false + end + + it "is outdated if there are newer boxes" do + iso_env.box3("foo", "1.5", :virtualbox) + + app.should_receive(:call).with(env) + + subject.call(env) + + expect(env[:box_outdated]).to be_true end end - context "with a metadata URL" do - let(:metadata_url) do - Tempfile.new("vagrant").tap do |f| - f.close + context "with refreshing" do + before do + env[:box_outdated_refresh] = true + end + + context "no metadata URL" do + let(:box) do + box_dir = iso_env.box3("foo", "1.0", :virtualbox) + Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) + end + + before do + machine.stub(box: box) + end + + it "raises an exception" do + app.should_receive(:call).never + + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::BoxOutdatedNoMetadata) end end - let(:box_dir) { iso_env.box3("foo", "1.0", :virtualbox) } + context "with metadata URL" do + let(:metadata_url) do + Tempfile.new("vagrant").tap do |f| + f.close + end + end - context "isn't outdated" do - before do - File.open(metadata_url.path, "w") do |f| - f.write(<<-RAW) + let(:box_dir) { iso_env.box3("foo", "1.0", :virtualbox) } + + context "isn't outdated" do + before do + File.open(metadata_url.path, "w") do |f| + f.write(<<-RAW) { "name": "foo/bar", "versions": [ @@ -82,47 +116,47 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do } ] } - RAW + RAW + end + + box = Vagrant::Box.new( + "foo", :virtualbox, "1.0", box_dir, + metadata_url: metadata_url.path) + machine.stub(box: box) end - box = Vagrant::Box.new( - "foo", :virtualbox, "1.0", box_dir, - metadata_url: metadata_url.path) - machine.stub(box: box) + it "marks it isn't outdated" do + app.should_receive(:call).with(env) + + subject.call(env) + + expect(env[:box_outdated]).to be_false + end + + it "talks to the UI" do + env[:box_outdated_success_ui] = true + + app.should_receive(:call).with(env) + env[:ui].should_receive(:success) + + subject.call(env) + + expect(env[:box_outdated]).to be_false + end + + it "doesn't talk to UI if it is told" do + app.should_receive(:call).with(env) + env[:ui].should_receive(:success).never + + subject.call(env) + + expect(env[:box_outdated]).to be_false + end end - it "marks it isn't outdated" do - app.should_receive(:call).with(env) - - subject.call(env) - - expect(env[:box_outdated]).to be_false - end - - it "talks to the UI" do - env[:box_outdated_success_ui] = true - - app.should_receive(:call).with(env) - env[:ui].should_receive(:success) - - subject.call(env) - - expect(env[:box_outdated]).to be_false - end - - it "doesn't talk to UI if it is told" do - app.should_receive(:call).with(env) - env[:ui].should_receive(:success).never - - subject.call(env) - - expect(env[:box_outdated]).to be_false - end - end - - it "is outdated if it is" do - File.open(metadata_url.path, "w") do |f| - f.write(<<-RAW) + it "is outdated if it is" do + File.open(metadata_url.path, "w") do |f| + f.write(<<-RAW) { "name": "foo/bar", "versions": [ @@ -140,21 +174,21 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do } ] } - RAW + RAW + end + + box = Vagrant::Box.new( + "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) + machine.stub(box: box) + + subject.call(env) + + expect(env[:box_outdated]).to be_true end - box = Vagrant::Box.new( - "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) - machine.stub(box: box) - - subject.call(env) - - expect(env[:box_outdated]).to be_true - end - - it "isn't outdated if the newer box is for another provider" do - File.open(metadata_url.path, "w") do |f| - f.write(<<-RAW) + it "isn't outdated if the newer box is for another provider" do + File.open(metadata_url.path, "w") do |f| + f.write(<<-RAW) { "name": "foo/bar", "versions": [ @@ -172,16 +206,17 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do } ] } - RAW + RAW + end + + box = Vagrant::Box.new( + "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) + machine.stub(box: box) + + subject.call(env) + + expect(env[:box_outdated]).to be_false end - - box = Vagrant::Box.new( - "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) - machine.stub(box: box) - - subject.call(env) - - expect(env[:box_outdated]).to be_false end end end From b5157df2ccffe109b04cfcc96131cbacafa5c41d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 15:27:01 -0800 Subject: [PATCH 67/90] core: BoxCheckOutdated only runs if told to --- .../action/builtin/box_check_outdated.rb | 9 +++++ plugins/commands/box/command/outdated.rb | 1 + plugins/kernel_v2/config/vm.rb | 3 ++ test/unit/plugins/kernel_v2/config/vm_test.rb | 8 +++++ .../action/builtin/box_check_outdated_test.rb | 33 ++++++++++++++++++- 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/action/builtin/box_check_outdated.rb b/lib/vagrant/action/builtin/box_check_outdated.rb index eecad9911..c703ddf58 100644 --- a/lib/vagrant/action/builtin/box_check_outdated.rb +++ b/lib/vagrant/action/builtin/box_check_outdated.rb @@ -24,6 +24,12 @@ module Vagrant def call(env) machine = env[:machine] + if !env[:box_outdated_force] + if !machine.config.vm.box_check_update + return @app.call(env) + end + end + if !machine.box # The box doesn't exist. I suppose technically that means # that it is "outdated" but we show a specialized error @@ -55,7 +61,10 @@ module Vagrant old: machine.box.version, new: box.version)) env[:box_outdated] = true + return end + + env[:box_outdated] = false end def check_outdated_refresh(env) diff --git a/plugins/commands/box/command/outdated.rb b/plugins/commands/box/command/outdated.rb index aec4c94c7..81f453dfe 100644 --- a/plugins/commands/box/command/outdated.rb +++ b/plugins/commands/box/command/outdated.rb @@ -26,6 +26,7 @@ module VagrantPlugins with_target_vms(argv) do |machine| @env.action_runner.run(Vagrant::Action.action_box_outdated, { + box_outdated_force: true, box_outdated_refresh: true, box_outdated_success_ui: true, machine: machine, diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 308e6a0af..563ed1719 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -17,6 +17,7 @@ module VagrantPlugins attr_accessor :base_mac attr_accessor :boot_timeout attr_accessor :box + attr_accessor :box_check_update attr_accessor :box_url attr_accessor :box_version attr_accessor :box_download_ca_cert @@ -33,6 +34,7 @@ module VagrantPlugins def initialize @base_mac = UNSET_VALUE @boot_timeout = UNSET_VALUE + @box_check_update = UNSET_VALUE @box_download_ca_cert = UNSET_VALUE @box_download_checksum = UNSET_VALUE @box_download_checksum_type = UNSET_VALUE @@ -305,6 +307,7 @@ module VagrantPlugins # Defaults @base_mac = nil if @base_mac == UNSET_VALUE @boot_timeout = 300 if @boot_timeout == UNSET_VALUE + @box_check_update = nil if @box_check_update == UNSET_VALUE @box_download_ca_cert = nil if @box_download_ca_cert == UNSET_VALUE @box_download_checksum = nil if @box_download_checksum == UNSET_VALUE @box_download_checksum_type = nil if @box_download_checksum_type == UNSET_VALUE diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index 205597f09..73f0a85fd 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -32,6 +32,14 @@ describe VagrantPlugins::Kernel_V2::VMConfig do end end + context "#box_check_update" do + it "defaults to nil" do + subject.finalize! + + expect(subject.box_check_update).to be_nil + end + end + describe "#box_url" do it "defaults to nil" do subject.finalize! diff --git a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb index 3c08777c6..4b67b9c89 100644 --- a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb +++ b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb @@ -25,7 +25,38 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do box_dir = iso_env.box3("foo", "1.0", :virtualbox) Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) end - let(:machine) { iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy) } + let(:machine) do + m = iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy) + m.config.vm.box_check_update = true + m + end + + before do + machine.stub(box: box) + end + + context "disabling outdated checking" do + it "doesn't check" do + machine.config.vm.box_check_update = false + + app.should_receive(:call).with(env).once + + subject.call(env) + + expect(env).to_not have_key(:box_outdated) + end + + it "checks if forced" do + machine.config.vm.box_check_update = false + env[:box_outdated_force] = true + + app.should_receive(:call).with(env).once + + subject.call(env) + + expect(env).to have_key(:box_outdated) + end + end context "no box" do it "raises an exception if the machine doesn't have a box yet" do From ea06202903a18d3f7831de8b94fa014d95791e3d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 15:27:57 -0800 Subject: [PATCH 68/90] core: get rid of unnecessary lines --- lib/vagrant/action/builtin/box_check_outdated.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/vagrant/action/builtin/box_check_outdated.rb b/lib/vagrant/action/builtin/box_check_outdated.rb index c703ddf58..87d19a5df 100644 --- a/lib/vagrant/action/builtin/box_check_outdated.rb +++ b/lib/vagrant/action/builtin/box_check_outdated.rb @@ -1,12 +1,4 @@ -require "digest/sha1" require "log4r" -require "pathname" -require "uri" - -require "vagrant/box_metadata" -require "vagrant/util/downloader" -require "vagrant/util/file_checksum" -require "vagrant/util/platform" module Vagrant module Action From bfca65b098013ea5698f21eabac890ad6ecf406a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 15:41:26 -0800 Subject: [PATCH 69/90] core: better output for BoxCheckOutdated --- .../action/builtin/box_check_outdated.rb | 19 ++++++++++++++++--- plugins/providers/virtualbox/action.rb | 1 + templates/locales/en.yml | 5 +++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/vagrant/action/builtin/box_check_outdated.rb b/lib/vagrant/action/builtin/box_check_outdated.rb index 87d19a5df..135da550c 100644 --- a/lib/vagrant/action/builtin/box_check_outdated.rb +++ b/lib/vagrant/action/builtin/box_check_outdated.rb @@ -20,6 +20,11 @@ module Vagrant if !machine.config.vm.box_check_update return @app.call(env) end + + if !env.has_key?(:box_outdated_refresh) + env[:box_outdated_refresh] = true + env[:box_outdated_ignore_errors] = true + end end if !machine.box @@ -30,9 +35,17 @@ module Vagrant end if env[:box_outdated_refresh] - @logger.info( - "Checking if box is outdated by refreshing metadata") - check_outdated_refresh(env) + env[:ui].output(I18n.t( + "vagrant.box_outdated_checking_with_refresh", + name: machine.box.name)) + begin + check_outdated_refresh(env) + rescue Errors::VagrantError => e + raise if !env[:box_outdated_ignore_errors] + env[:ui].detail(I18n.t( + "vagrant.box_outdated_metadata_error", + message: e.message)) + end else @logger.info("Checking if box is outdated locally") check_outdated_local(env) diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index 34b1b08e0..5339343f1 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -307,6 +307,7 @@ module VagrantPlugins end b.use ConfigValidate + b.use BoxCheckOutdated b.use Call, Created do |env, b2| # If the VM is NOT created yet, then do the setup steps if !env[:result] diff --git a/templates/locales/en.yml b/templates/locales/en.yml index f41d6e559..518640c3a 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -30,11 +30,16 @@ en: Loading metadata for box '%{name}' box_outdated: |- * '%{name}' is outdated! Current: %{current}. Latest: %{latest} + box_outdated_checking_with_refresh: |- + Checking if box '%{name}' is up to date... box_outdated_local: |- A newer version of the box '%{name}' is available and already installed, but your Vagrant machine is running against version '%{old}'. To update to version '%{new}', destroy and recreate your machine. + box_outdated_metadata_error: |- + Error loading box metadata while attempting to check for + updates: %{message} box_outdated_single: |- A newer version of the box '%{name}' is available! You currently have version '%{current}'. The latest is version '%{latest}'. Run From 1a5ad9f3d592987ca401ce9a26780159325c3261 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 15:44:26 -0800 Subject: [PATCH 70/90] providers/virtualbox: check for outdated on any start --- plugins/providers/virtualbox/action.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index 5339343f1..5831d9b9d 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -246,6 +246,7 @@ module VagrantPlugins Vagrant::Action::Builder.new.tap do |b| b.use CheckVirtualbox b.use ConfigValidate + b.use BoxCheckOutdated b.use Call, IsRunning do |env, b2| # If the VM is running, then our work here is done, exit if env[:result] @@ -307,7 +308,6 @@ module VagrantPlugins end b.use ConfigValidate - b.use BoxCheckOutdated b.use Call, Created do |env, b2| # If the VM is NOT created yet, then do the setup steps if !env[:result] From 61a1082d6514cf13793d68e373deb04eaf428f93 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 17:52:52 -0800 Subject: [PATCH 71/90] core: BoxCheckOutdated is *much* simpler, unified logic, tests --- .../action/builtin/box_check_outdated.rb | 73 ++----- lib/vagrant/action/builtin/box_update.rb | 20 ++ lib/vagrant/box.rb | 26 +++ lib/vagrant/errors.rb | 8 +- plugins/commands/box/command/outdated.rb | 9 +- plugins/commands/box/command/update.rb | 43 ++++ templates/locales/en.yml | 12 +- .../action/builtin/box_check_outdated_test.rb | 205 ++++-------------- test/unit/vagrant/box_test.rb | 113 ++++++++++ 9 files changed, 284 insertions(+), 225 deletions(-) create mode 100644 lib/vagrant/action/builtin/box_update.rb create mode 100644 plugins/commands/box/command/update.rb diff --git a/lib/vagrant/action/builtin/box_check_outdated.rb b/lib/vagrant/action/builtin/box_check_outdated.rb index 135da550c..9026d9737 100644 --- a/lib/vagrant/action/builtin/box_check_outdated.rb +++ b/lib/vagrant/action/builtin/box_check_outdated.rb @@ -20,11 +20,6 @@ module Vagrant if !machine.config.vm.box_check_update return @app.call(env) end - - if !env.has_key?(:box_outdated_refresh) - env[:box_outdated_refresh] = true - env[:box_outdated_ignore_errors] = true - end end if !machine.box @@ -33,22 +28,28 @@ module Vagrant # message anyways. raise Errors::BoxOutdatedNoBox, name: machine.config.vm.box end + box = machine.box + constraints = machine.config.vm.box_version - if env[:box_outdated_refresh] - env[:ui].output(I18n.t( - "vagrant.box_outdated_checking_with_refresh", - name: machine.box.name)) - begin - check_outdated_refresh(env) - rescue Errors::VagrantError => e - raise if !env[:box_outdated_ignore_errors] - env[:ui].detail(I18n.t( - "vagrant.box_outdated_metadata_error", - message: e.message)) - end - else - @logger.info("Checking if box is outdated locally") - check_outdated_local(env) + env[:ui].output(I18n.t( + "vagrant.box_outdated_checking_with_refresh", + name: box.name)) + update = nil + begin + update = box.has_update?(constraints) + rescue Errors::VagrantError => e + raise if !env[:box_outdated_ignore_errors] + env[:ui].detail(I18n.t( + "vagrant.box_outdated_metadata_error_single", + message: e.message)) + end + env[:box_outdated] = update != nil + if update + env[:ui].warn(I18n.t( + "vagrant.box_outdated_single", + name: update[0].name, + current: box.version, + latest: update[1].version)) end @app.call(env) @@ -71,38 +72,6 @@ module Vagrant env[:box_outdated] = false end - - def check_outdated_refresh(env) - machine = env[:machine] - - if !machine.box.metadata_url - # This box doesn't have a metadata URL, so we can't - # possibly check the version information. - raise Errors::BoxOutdatedNoMetadata, name: machine.box.name - end - - md = machine.box.load_metadata - newer = md.version( - "> #{machine.box.version}", provider: machine.box.provider) - if !newer - if env[:box_outdated_success_ui] - env[:ui].success(I18n.t( - "vagrant.box_up_to_date_single", - name: machine.box.name, - version: machine.box.version)) - end - - env[:box_outdated] = false - return - end - - env[:ui].warn(I18n.t( - "vagrant.box_outdated_single", - name: machine.box.name, - current: machine.box.version, - latest: newer.version)) - env[:box_outdated] = true - end end end end diff --git a/lib/vagrant/action/builtin/box_update.rb b/lib/vagrant/action/builtin/box_update.rb new file mode 100644 index 000000000..bd510445b --- /dev/null +++ b/lib/vagrant/action/builtin/box_update.rb @@ -0,0 +1,20 @@ +require "log4r" + +module Vagrant + module Action + module Builtin + # This middleware updates a specific box if there are updates available. + class BoxUpdate + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new( + "vagrant::action::builtin::box_update") + end + + def call(env) + machine = env[:machine] + end + end + end + end +end diff --git a/lib/vagrant/box.rb b/lib/vagrant/box.rb index c09d59424..1e1c26dba 100644 --- a/lib/vagrant/box.rb +++ b/lib/vagrant/box.rb @@ -104,6 +104,32 @@ module Vagrant BoxMetadata.new(File.open(tf.path, "r")) end + # Checks if the box has an update and returns the metadata, version, + # and provider. If the box doesn't have an update that satisfies the + # constraints, it will return nil. + # + # This will potentially make a network call if it has to load the + # metadata from the network. + # + # @param [String] version Version constraints the update must + # satisfy. If nil, the version constrain defaults to being a + # larger version than this box. + # @return [Array] + def has_update?(version=nil) + if !@metadata_url + raise Errors::BoxUpdateNoMetadata, name: @name + end + + version += ", " if version + version ||= "" + version += "> #{@version}" + md = self.load_metadata + newer = md.version(version, provider: @provider) + return nil if !newer + + [md, newer, newer.provider(@provider)] + end + # This repackages this box and outputs it to the given path. # # @param [Pathname] path The full path (filename included) of where diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 516caea49..f336cafd3 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -176,10 +176,6 @@ module Vagrant error_key(:box_outdated_no_box) end - class BoxOutdatedNoMetadata < VagrantError - error_key(:box_outdated_no_metadata) - end - class BoxProviderDoesntMatch < VagrantError error_key(:box_provider_doesnt_match) end @@ -204,6 +200,10 @@ module Vagrant error_key(:untar_failure, "vagrant.actions.box.unpackage") end + class BoxUpdateNoMetadata < VagrantError + error_key(:box_update_no_metadata) + end + class BoxVerificationFailed < VagrantError error_key(:failed, "vagrant.actions.box.verify") end diff --git a/plugins/commands/box/command/outdated.rb b/plugins/commands/box/command/outdated.rb index 81f453dfe..1dcd61de1 100644 --- a/plugins/commands/box/command/outdated.rb +++ b/plugins/commands/box/command/outdated.rb @@ -8,7 +8,13 @@ module VagrantPlugins options = {} opts = OptionParser.new do |o| - o.banner = "Usage: vagrant box outdated" + o.banner = "Usage: vagrant box outdated [options]" + o.separator "" + o.separator "Checks if there is a new version available for the box" + o.separator "that are you are using. If you pass in the --global flag," + o.separator "all boxes will be checked for updates." + o.separator "" + o.separator "Options:" o.separator "" o.on("--global", "Check all boxes installed.") do |g| @@ -17,6 +23,7 @@ module VagrantPlugins end argv = parse_options(opts) + return if !argv # If we're checking the boxes globally, then do that. if options[:global] diff --git a/plugins/commands/box/command/update.rb b/plugins/commands/box/command/update.rb new file mode 100644 index 000000000..a89ea4877 --- /dev/null +++ b/plugins/commands/box/command/update.rb @@ -0,0 +1,43 @@ +require 'optparse' + +module VagrantPlugins + module CommandBox + module Command + class Update < Vagrant.plugin("2", :command) + def execute + options = {} + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant box update [options]" + o.separator "" + o.separator "Updates the box that is in use in the current Vagrant environment," + o.separator "if there any updates available. This does not destroy/recreate the" + o.separator "machine, so you'll have to do that to see changes." + o.separator "" + o.separator "To update a specific box (not tied to a Vagrant environment), use the" + o.separator "--box flag." + o.separator "" + o.separator "Options:" + o.separator "" + + o.on("--box VALUE", String, "Update a specific box") do |b| + options[:box] = b + end + end + + argv = parse_options(opts) + return if !argv + + with_target_vms(argv) do |machine| + @env.action_runner.run(Vagrant::Action.action_box_update, { + box_outdated_force: true, + box_outdated_refresh: true, + box_outdated_success_ui: true, + machine: machine, + }) + end + end + end + end + end +end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 518640c3a..eaf799045 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -37,7 +37,7 @@ en: installed, but your Vagrant machine is running against version '%{old}'. To update to version '%{new}', destroy and recreate your machine. - box_outdated_metadata_error: |- + box_outdated_metadata_error_single: |- Error loading box metadata while attempting to check for updates: %{message} box_outdated_single: |- @@ -373,11 +373,6 @@ en: that this issue can be fixed. %{error} - box_outdated_no_metadata: |- - The box '%{name}' is not a versioned box. The box was added - directly instead of from a box catalog. Vagrant can only - check the versions of boxes that were added from a catalog - such as from the public Vagrant Server. 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 +404,11 @@ en: the provider specified. Please double-check and try again. The providers for this are: %{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 + check the versions of boxes that were added from a catalog + such as from the public Vagrant Server. bundler_disabled: |- Vagrant's built-in bundler management mechanism is disabled because Vagrant is running in an external bundler environment. In these diff --git a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb index 4b67b9c89..1d6ecf921 100644 --- a/test/unit/vagrant/action/builtin/box_check_outdated_test.rb +++ b/test/unit/vagrant/action/builtin/box_check_outdated_test.rb @@ -23,8 +23,11 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do let(:box) do box_dir = iso_env.box3("foo", "1.0", :virtualbox) - Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) + Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir).tap do |b| + b.stub(has_update?: nil) + end end + let(:machine) do m = iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy) m.config.vm.box_check_update = true @@ -69,185 +72,63 @@ describe Vagrant::Action::Builtin::BoxCheckOutdated do end end - context "without refreshing" do - before do - env[:box_outdated_refresh] = false + context "with a box" do + it "sets env if no update" do + box.should_receive(:has_update?).and_return(nil) - machine.stub(box: box) - end - - it "isn't outdated if there are no newer boxes" do - iso_env.box3("foo", "0.5", :virtualbox) - - app.should_receive(:call).with(env) + app.should_receive(:call).with(env).once subject.call(env) expect(env[:box_outdated]).to be_false end - it "is outdated if there are newer boxes" do - iso_env.box3("foo", "1.5", :virtualbox) + it "sets env if there is an update" do + md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW)) + { + "name": "foo", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.1", + "providers": [ + { + "name": "virtualbox", + "url": "bar" + } + ] + } + ] + } + RAW - app.should_receive(:call).with(env) + box.should_receive(:has_update?).with(machine.config.vm.box_version). + and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")]) + + app.should_receive(:call).with(env).once subject.call(env) expect(env[:box_outdated]).to be_true end - end - context "with refreshing" do - before do - env[:box_outdated_refresh] = true + it "raises error if has_update? errors" do + box.should_receive(:has_update?).and_raise(Vagrant::Errors::VagrantError) + + app.should_receive(:call).never + + expect { subject.call(env) }.to raise_error(Vagrant::Errors::VagrantError) end - context "no metadata URL" do - let(:box) do - box_dir = iso_env.box3("foo", "1.0", :virtualbox) - Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir) - end + it "doesn't raise an error if ignore errors is on" do + env[:box_outdated_ignore_errors] = true - before do - machine.stub(box: box) - end + box.should_receive(:has_update?).and_raise(Vagrant::Errors::VagrantError) + app.should_receive(:call).with(env).once - it "raises an exception" do - app.should_receive(:call).never - - expect { subject.call(env) }. - to raise_error(Vagrant::Errors::BoxOutdatedNoMetadata) - end - end - - context "with metadata URL" do - let(:metadata_url) do - Tempfile.new("vagrant").tap do |f| - f.close - end - end - - let(:box_dir) { iso_env.box3("foo", "1.0", :virtualbox) } - - context "isn't outdated" do - before do - File.open(metadata_url.path, "w") do |f| - f.write(<<-RAW) - { - "name": "foo/bar", - "versions": [ - { - "version": "1.0", - "providers": [ - { - "name": "virtualbox", - "url": "#{iso_env.box2_file(:virtualbox)}" - } - ] - } - ] - } - RAW - end - - box = Vagrant::Box.new( - "foo", :virtualbox, "1.0", box_dir, - metadata_url: metadata_url.path) - machine.stub(box: box) - end - - it "marks it isn't outdated" do - app.should_receive(:call).with(env) - - subject.call(env) - - expect(env[:box_outdated]).to be_false - end - - it "talks to the UI" do - env[:box_outdated_success_ui] = true - - app.should_receive(:call).with(env) - env[:ui].should_receive(:success) - - subject.call(env) - - expect(env[:box_outdated]).to be_false - end - - it "doesn't talk to UI if it is told" do - app.should_receive(:call).with(env) - env[:ui].should_receive(:success).never - - subject.call(env) - - expect(env[:box_outdated]).to be_false - end - end - - it "is outdated if it is" do - File.open(metadata_url.path, "w") do |f| - f.write(<<-RAW) - { - "name": "foo/bar", - "versions": [ - { - "version": "1.0" - }, - { - "version": "1.5", - "providers": [ - { - "name": "virtualbox", - "url": "#{iso_env.box2_file(:virtualbox)}" - } - ] - } - ] - } - RAW - end - - box = Vagrant::Box.new( - "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) - machine.stub(box: box) - - subject.call(env) - - expect(env[:box_outdated]).to be_true - end - - it "isn't outdated if the newer box is for another provider" do - File.open(metadata_url.path, "w") do |f| - f.write(<<-RAW) - { - "name": "foo/bar", - "versions": [ - { - "version": "1.0" - }, - { - "version": "1.5", - "providers": [ - { - "name": "vmware", - "url": "#{iso_env.box2_file(:vmware)}" - } - ] - } - ] - } - RAW - end - - box = Vagrant::Box.new( - "foo", :virtualbox, "1.0", box_dir, metadata_url: metadata_url.path) - machine.stub(box: box) - - subject.call(env) - - expect(env[:box_outdated]).to be_false - end + expect { subject.call(env) }.to_not raise_error end end end diff --git a/test/unit/vagrant/box_test.rb b/test/unit/vagrant/box_test.rb index d682875be..797115651 100644 --- a/test/unit/vagrant/box_test.rb +++ b/test/unit/vagrant/box_test.rb @@ -1,8 +1,11 @@ require File.expand_path("../../base", __FILE__) require "pathname" +require "stringio" require "tempfile" +require "vagrant/box_metadata" + describe Vagrant::Box do include_context "unit" @@ -76,6 +79,116 @@ describe Vagrant::Box do end end + context "#has_update?" do + subject do + described_class.new( + name, provider, version, directory, + metadata_url: "foo") + end + + it "raises an exception if no metadata_url is set" do + subject = described_class.new( + name, provider, version, directory) + + expect { subject.has_update?("> 0") }. + to raise_error(Vagrant::Errors::BoxUpdateNoMetadata) + end + + it "returns nil if there is no update" do + metadata = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW)) + { + "name": "foo", + "versions": [ + { "version": "1.0" } + ] + } + RAW + + subject.stub(load_metadata: metadata) + + expect(subject.has_update?).to be_nil + end + + it "returns the updated box info if there is an update available" do + metadata = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW)) + { + "name": "foo", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.1", + "providers": [ + { + "name": "virtualbox", + "url": "bar" + } + ] + } + ] + } + RAW + + subject.stub(load_metadata: metadata) + + result = subject.has_update? + expect(result).to_not be_nil + + expect(result[0]).to be_kind_of(Vagrant::BoxMetadata) + expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider) + + expect(result[0].name).to eq("foo") + expect(result[1].version).to eq("1.1") + expect(result[2].url).to eq("bar") + end + + it "returns the updated box info within constraints" do + metadata = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW)) + { + "name": "foo", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.1", + "providers": [ + { + "name": "virtualbox", + "url": "bar" + } + ] + }, + { + "version": "1.4", + "providers": [ + { + "name": "virtualbox", + "url": "bar" + } + ] + } + ] + } + RAW + + subject.stub(load_metadata: metadata) + + result = subject.has_update?(">= 1.1, < 1.4") + expect(result).to_not be_nil + + expect(result[0]).to be_kind_of(Vagrant::BoxMetadata) + expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version) + expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider) + + expect(result[0].name).to eq("foo") + expect(result[1].version).to eq("1.1") + expect(result[2].url).to eq("bar") + end + end + context "#load_metadata" do let(:metadata_url) do Tempfile.new("vagrant").tap do |f| From 88b64f93a4eb39cbb831197dfe6922229c65b470 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 18:13:37 -0800 Subject: [PATCH 72/90] commands/box: "update" command --- plugins/commands/box/command/root.rb | 5 + plugins/commands/box/command/update.rb | 26 ++++- templates/locales/en.yml | 2 +- .../commands/box/command/update_test.rb | 94 +++++++++++++++++++ 4 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 test/unit/plugins/commands/box/command/update_test.rb diff --git a/plugins/commands/box/command/root.rb b/plugins/commands/box/command/root.rb index 96f599308..138a10de9 100644 --- a/plugins/commands/box/command/root.rb +++ b/plugins/commands/box/command/root.rb @@ -38,6 +38,11 @@ module VagrantPlugins require File.expand_path("../repackage", __FILE__) Repackage end + + @subcommands.register(:update) do + require_relative "update" + Update + end end def execute diff --git a/plugins/commands/box/command/update.rb b/plugins/commands/box/command/update.rb index a89ea4877..0db5b5776 100644 --- a/plugins/commands/box/command/update.rb +++ b/plugins/commands/box/command/update.rb @@ -29,11 +29,27 @@ module VagrantPlugins return if !argv with_target_vms(argv) do |machine| - @env.action_runner.run(Vagrant::Action.action_box_update, { - box_outdated_force: true, - box_outdated_refresh: true, - box_outdated_success_ui: true, - machine: machine, + if !machine.box + machine.ui.output(I18n.t( + "vagrant.errors.box_update_no_box", + name: machine.config.vm.box)) + next + end + + box = machine.box + update = box.has_update?(machine.config.vm.box_version) + if !update + machine.ui.success(I18n.t( + "vagrant.box_up_to_date_single", + name: box.name, version: box.version)) + next + end + + @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, }) end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index eaf799045..9c52b26a5 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -51,7 +51,7 @@ en: box_up_to_date: |- * '%{name}' (v%{version}) is up to date box_up_to_date_single: |- - Your box '%{name}' (v%{version}) is running the latest version. + Box '%{name}' (v%{version}) is running the latest version. cfengine_bootstrapping: |- Bootstrapping CFEngine with policy server: %{policy_server}... cfengine_bootstrapping_policy_hub: |- diff --git a/test/unit/plugins/commands/box/command/update_test.rb b/test/unit/plugins/commands/box/command/update_test.rb new file mode 100644 index 000000000..3f2c5cd46 --- /dev/null +++ b/test/unit/plugins/commands/box/command/update_test.rb @@ -0,0 +1,94 @@ +require File.expand_path("../../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/box/command/update") + +describe VagrantPlugins::CommandBox::Command::Update do + include_context "unit" + + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + test_iso_env.vagrantfile("") + test_iso_env.create_vagrant_env + end + let(:test_iso_env) { isolated_environment } + + let(:action_runner) { double("action_runner") } + let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + + subject { described_class.new(argv, iso_env) } + + before do + iso_env.stub(action_runner: action_runner) + end + + describe "execute" do + context "updating environment machines" do + before do + subject.stub(:with_target_vms) { |&block| block.call machine } + end + + let(:box) do + box_dir = test_iso_env.box3("foo", "1.0", :virtualbox) + box = Vagrant::Box.new( + "foo", :virtualbox, "1.0", box_dir, metadata_url: "foo") + box.stub(has_update?: nil) + box + end + + it "ignores machines without boxes" do + action_runner.should_receive(:run).never + + subject.execute + end + + it "doesn't update boxes if they're up-to-date" do + machine.stub(box: box) + box.should_receive(:has_update?). + with(machine.config.vm.box_version). + and_return(nil) + + action_runner.should_receive(:run).never + + subject.execute + end + + it "updates boxes if they have an update" do + md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW)) + { + "name": "foo", + "versions": [ + { + "version": "1.0" + }, + { + "version": "1.1", + "providers": [ + { + "name": "virtualbox", + "url": "bar" + } + ] + } + ] + } + RAW + + machine.stub(box: box) + box.should_receive(:has_update?). + with(machine.config.vm.box_version). + and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")]) + + action_runner.should_receive(:run).with do |action, opts| + expect(opts[:box_url]).to eq(box.metadata_url) + expect(opts[:box_provider]).to eq("virtualbox") + expect(opts[:box_version]).to eq("1.1") + expect(opts[:ui]).to equal(machine.ui) + true + end + + subject.execute + end + end + end +end From 43157fba18d2ba4c720d55bab2a717c24dcd6712 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 18:15:13 -0800 Subject: [PATCH 73/90] core: output the version that is added with BoxAdd --- lib/vagrant/action/builtin/box_add.rb | 4 +++- templates/locales/en.yml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index c5bca8721..d53ffeaaa 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -332,7 +332,9 @@ module Vagrant env[:ui].success(I18n.t( "vagrant.box_added", - name: box.name, provider: box.provider)) + name: box.name, + version: box.version, + provider: box.provider)) # Store the added box in the env for future middleware env[:box_added] = box diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 9c52b26a5..6aba608f9 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -19,7 +19,7 @@ en: box_add_with_version: |- Adding box '%{name}' (v%{version}) for provider: %{providers} box_added: |- - Successfully added box '%{name}' for '%{provider}'! + Successfully added box '%{name}' (v%{version}) for '%{provider}'! box_downloading: |- Downloading: %{url} box_download_error: |- From cf9004241e500984fc1145c4f8e4042221906b5d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 18:17:12 -0800 Subject: [PATCH 74/90] core: output what we're updating and what we're not --- plugins/commands/box/command/update.rb | 6 ++++++ templates/locales/en.yml | 3 +++ 2 files changed, 9 insertions(+) diff --git a/plugins/commands/box/command/update.rb b/plugins/commands/box/command/update.rb index 0db5b5776..75fd3a07c 100644 --- a/plugins/commands/box/command/update.rb +++ b/plugins/commands/box/command/update.rb @@ -45,6 +45,12 @@ module VagrantPlugins 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, diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 6aba608f9..5616620ca 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -48,6 +48,9 @@ en: * '%{name}': Error loading metadata: %{message} box_outdated_no_metadata: |- * '%{name}' wasn't added from a catalog, no version information + box_updating: |- + Updating '%{name}' with provider '%{provider}' from version + '%{old}' to '%{new}'... box_up_to_date: |- * '%{name}' (v%{version}) is up to date box_up_to_date_single: |- From 386938f0b105f4c4982ebb1b2402564c633e68cb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jan 2014 18:23:14 -0800 Subject: [PATCH 75/90] commands/box: update looks prettier on output --- plugins/commands/box/command/update.rb | 8 +++++++- templates/locales/en.yml | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/commands/box/command/update.rb b/plugins/commands/box/command/update.rb index 75fd3a07c..6cfb85458 100644 --- a/plugins/commands/box/command/update.rb +++ b/plugins/commands/box/command/update.rb @@ -37,7 +37,13 @@ module VagrantPlugins end box = machine.box - update = box.has_update?(machine.config.vm.box_version) + 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", diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 5616620ca..c48c3ec07 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -51,6 +51,8 @@ en: box_updating: |- Updating '%{name}' with provider '%{provider}' from version '%{old}' to '%{new}'... + box_update_checking: |- + Checking for updates to '%{name}' box_up_to_date: |- * '%{name}' (v%{version}) is up to date box_up_to_date_single: |- From 7a6d1a3ff1dcbd61d79a77e9e60582faeb65f1bc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Jan 2014 10:03:33 -0800 Subject: [PATCH 76/90] commands/box: update command can update a specific box now --- lib/vagrant/box_metadata.rb | 1 + lib/vagrant/errors.rb | 12 ++ plugins/commands/box/command/update.rb | 104 +++++++++++---- templates/locales/en.yml | 16 +++ .../commands/box/command/update_test.rb | 123 ++++++++++++++++++ test/unit/support/isolated_environment.rb | 7 + 6 files changed, 238 insertions(+), 25 deletions(-) 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 From 286a503cb6a1444a40c6af72651198d79d48b1fb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 25 Jan 2014 12:18:41 -0800 Subject: [PATCH 77/90] website/docs: docs for box versioning --- website/docs/Vagrantfile | 23 ++++ website/docs/source/layouts/layout.erb | 1 + website/docs/source/stylesheets/_base.less | 2 +- website/docs/source/v2/boxes.html.md | 92 +++++--------- website/docs/source/v2/boxes/base.html.md | 9 ++ website/docs/source/v2/boxes/format.html.md | 85 +++++++++++-- .../docs/source/v2/boxes/versioning.html.md | 95 +++++++++++++++ website/docs/source/v2/cli/box.html.md | 115 ++++++++++++++---- 8 files changed, 325 insertions(+), 97 deletions(-) create mode 100644 website/docs/Vagrantfile create mode 100644 website/docs/source/v2/boxes/versioning.html.md diff --git a/website/docs/Vagrantfile b/website/docs/Vagrantfile new file mode 100644 index 000000000..bab5d6429 --- /dev/null +++ b/website/docs/Vagrantfile @@ -0,0 +1,23 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +$script = <