From 1d197d84c3b9a79a6bc2c79e0225bb9c3f469697 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 9 Jul 2012 20:32:53 -0700 Subject: [PATCH] BoxCollection#add transparently upgrades V1 boxes to V2 This means that V1 boxes can be added to a V2 box collection without issue. --- lib/vagrant/box_collection.rb | 95 ++++++++++++++--------- test/unit/support/isolated_environment.rb | 46 +++++++++++ test/unit/vagrant/box_collection_test.rb | 11 +++ 3 files changed, 117 insertions(+), 35 deletions(-) diff --git a/lib/vagrant/box_collection.rb b/lib/vagrant/box_collection.rb index d90bf42f1..68fbe3bd2 100644 --- a/lib/vagrant/box_collection.rb +++ b/lib/vagrant/box_collection.rb @@ -63,18 +63,25 @@ module Vagrant # 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) Dir.mktmpdir("vagrant-") do |temp_dir| + temp_dir = Pathname.new(temp_dir) + # Extract the box into a temporary directory. @logger.debug("Unpacking box into temporary directory: #{temp_dir}") - begin - Archive::Tar::Minitar.unpack(path.to_s, temp_dir) + Archive::Tar::Minitar.unpack(path.to_s, temp_dir.to_s) rescue SystemCallError raise Errors::BoxUnpackageFailure end + # If we get a V1 box, we want to update it in place + if v1_box?(temp_dir) + @logger.debug("Added box is a V1 box. Upgrading in place.") + temp_dir = v1_upgrade(temp_dir) + end + # 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, provider, Pathname.new(temp_dir)) + box = Box.new(name, provider, 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. @@ -174,7 +181,7 @@ module Vagrant # 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?(name) + if v1_box?(@directory.join(name)) @logger.warn("V1 box found: #{name}") raise Errors::BoxUpgradeRequired, :name => name end @@ -197,36 +204,11 @@ module Vagrant # If the box doesn't exist at all, raise an exception raise Errors::BoxNotFound, :name => name if !box_dir.directory? - if v1_box?(name) + if v1_box?(box_dir) @logger.debug("V1 box #{name} found. Upgrading!") - # First, we create a temporary directory within the box to store - # the intermediary moved files. We randomize this in case there is - # already a directory named "virtualbox" in here for some reason. - temp_dir = box_dir.join("vagrant-#{Digest::SHA1.hexdigest(name)}") - @logger.debug("Temporary directory for upgrading: #{temp_dir}") - - # Make the temporary directory - temp_dir.mkpath - - # Move all the things into the temporary directory - box_dir.children(true).each do |child| - # Don't move the temp_dir - next if child == temp_dir - - # Move every other directory into the temporary directory - @logger.debug("Copying to upgrade directory: #{child}") - FileUtils.mv(child, temp_dir.join(child.basename)) - end - - # If there is no metadata.json file, make one, since this is how - # we determine if the box is a V2 box. - metadata_file = temp_dir.join("metadata.json") - if !metadata_file.file? - metadata_file.open("w") do |f| - f.write(JSON.generate({})) - end - end + # First we actually perform the upgrade + temp_dir = v1_upgrade(box_dir) # Rename the temporary directory to the provider. temp_dir.rename(box_dir.join("virtualbox")) @@ -239,13 +221,56 @@ module Vagrant protected - # This checks if the given name represents a V1 box on the system. + # This checks if the given directory represents a V1 box on the + # system. # + # @param [Pathname] dir Directory where the box is unpacked. # @return [Boolean] - def v1_box?(name) + def v1_box?(dir) # We detect a V1 box given by whether there is a "box.ovf" which # is a heuristic but is pretty accurate. - @directory.join(name, "box.ovf").file? + dir.join("box.ovf").file? + end + + # This upgrades the V1 box contained unpacked in the given directory + # and returns the directory of the upgraded version. This is + # _destructive_ to the contents of the old directory. That is, the + # contents of the old V1 box will be destroyed or moved. + # + # Preconditions: + # * `dir` is a valid V1 box. Verify with {#v1_box?} + # + # @param [Pathname] dir Directory where the V1 box is unpacked. + # @return [Pathname] Path to the unpackaged V2 box. + def v1_upgrade(dir) + @logger.debug("Upgrading box in directory: #{dir}") + + temp_dir = Pathname.new(Dir.mktmpdir("vagrant-")) + @logger.debug("Temporary directory for upgrading: #{temp_dir}") + + # Move all the things into the temporary directory + dir.children(true).each do |child| + # Don't move the temp_dir + next if child == temp_dir + + # Move every other directory into the temporary directory + @logger.debug("Copying to upgrade directory: #{child}") + FileUtils.mv(child, temp_dir.join(child.basename)) + end + + # If there is no metadata.json file, make one, since this is how + # we determine if the box is a V2 box. + metadata_file = temp_dir.join("metadata.json") + if !metadata_file.file? + metadata_file.open("w") do |f| + f.write(JSON.generate({ + :provider => "virtualbox" + })) + end + end + + # Return the temporary directory + temp_dir end end end diff --git a/test/unit/support/isolated_environment.rb b/test/unit/support/isolated_environment.rb index 3d93d5015..9b8735369 100644 --- a/test/unit/support/isolated_environment.rb +++ b/test/unit/support/isolated_environment.rb @@ -86,6 +86,52 @@ module Unit box_dir end + # This creates a "box" file that is a valid V1 box. + # + # @return [Pathname] Path to the newly created box. + def box1_file + # Create a temporary directory to store our data we will tar up + td_source = Tempdir.new + td_dest = Tempdir.new + + # Store the temporary directory so it is not deleted until + # this instance is garbage collected. + @_box2_file_temp ||= [] + @_box2_file_temp << td_dest + + # The source as a Pathname, which is easier to work with + source = Pathname.new(td_source.path) + + # The destination file + result = Pathname.new(td_dest.path).join("temporary.box") + + File.open(result, Vagrant::Util::Platform.tar_file_options) do |tar| + Archive::Tar::Minitar::Output.open(tar) do |output| + begin + # Switch to the source directory so that Archive::Tar::Minitar + # can tar things up. + current_dir = FileUtils.pwd + FileUtils.cd(source) + + # Put a "box.ovf" in there. + source.join("box.ovf").open("w") do |f| + f.write("FOO!") + end + + # Add all the files + Dir.glob(File.join(".", "**", "*")).each do |entry| + Archive::Tar::Minitar.pack_file(entry, output) + end + ensure + FileUtils.cd(current_dir) + end + end + end + + # Resulting box + result + end + # This creates a "box" file with the given provider. # # @param [Symbol] provider Provider for the box. diff --git a/test/unit/vagrant/box_collection_test.rb b/test/unit/vagrant/box_collection_test.rb index f46804351..d1e44f42e 100644 --- a/test/unit/vagrant/box_collection_test.rb +++ b/test/unit/vagrant/box_collection_test.rb @@ -38,6 +38,17 @@ describe Vagrant::BoxCollection do box.provider.should == :vmware end + it "should add a V1 box" do + # Create a V1 box. + 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 + end + it "should raise an exception if the box already exists" do prev_box_name = "foo" prev_box_provider = :virtualbox