BoxCollection#add transparently upgrades V1 boxes to V2

This means that V1 boxes can be added to a V2 box collection without
issue.
This commit is contained in:
Mitchell Hashimoto 2012-07-09 20:32:53 -07:00
parent 8026715619
commit 1d197d84c3
3 changed files with 117 additions and 35 deletions

View File

@ -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

View File

@ -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.

View File

@ -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