Make Box2 the new Box

This involved defaulting all box searching at the moment to VirtualBox.
Additionally, box upgrading is not yet handled. This needs to be done at
some point.
This commit is contained in:
Mitchell Hashimoto 2012-07-09 18:31:53 -07:00
parent e7bed7c2ff
commit da15105a8f
13 changed files with 483 additions and 660 deletions

View File

@ -63,9 +63,7 @@ require "vagrant/registry"
module Vagrant
autoload :Action, 'vagrant/action'
autoload :Box, 'vagrant/box'
autoload :Box2, 'vagrant/box2'
autoload :BoxCollection, 'vagrant/box_collection'
autoload :BoxCollection2, 'vagrant/box_collection2'
autoload :CLI, 'vagrant/cli'
autoload :Command, 'vagrant/command'
autoload :Communication, 'vagrant/communication'

View File

@ -1,44 +1,64 @@
require "json"
module Vagrant
# Represents a "box," which is simply a packaged vagrant environment.
# Boxes are simply `tar` files which contain an exported VirtualBox
# virtual machine, at the least. They are created with `vagrant package`
# and may contain additional files if specified by the creator. This
# class serves to help manage these boxes, although most of the logic
# is kicked out to middlewares.
# Represents a "box," which is a package Vagrant environment that is used
# as a base image when creating a new guest machine.
class Box
# The name of the box.
include Comparable
# The box name. This is the logical name used when adding the box.
#
# @return [String]
attr_reader :name
# The directory where this box is stored
# This is the provider that this box is built for.
#
# @return [Symbol]
attr_reader :provider
# This is the directory on disk where this box exists.
#
# @return [Pathname]
attr_reader :directory
# Creates a new box instance. Given an optional `name` parameter,
# newly created instance will have that name, otherwise it defaults
# to `nil`.
# This is the metadata for the box. This is read from the "metadata.json"
# file that all boxes require.
#
# **Note:** This method does not actually _create_ the box, but merely
# returns a new, abstract representation of it. To add a box, see {#add}.
def initialize(name, directory, action_runner)
# @return [Hash]
attr_reader :metadata
# 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, directory)
@name = name
@provider = provider
@directory = directory
@action_runner = action_runner
@metadata = JSON.parse(directory.join("metadata.json").read)
end
# Begins the process of destroying this box. This cannot be undone!
def destroy
@action_runner.run(:box_remove, { :box_name => @name, :box_directory => @directory })
# This deletes the box. This is NOT undoable.
def destroy!
# Delete the directory to delete the box.
FileUtils.rm_r(@directory)
# Just return true always
true
rescue Errno::ENOENT
# This means the directory didn't exist. Not a problem.
return true
end
# Begins sequence to repackage this box.
def repackage(options=nil)
@action_runner.run(:box_repackage, { :box_name => @name, :box_directory => @directory })
end
# Implemented for comparison with other boxes. Comparison is implemented
# by simply comparing name.
# Implemented for comparison with other boxes. Comparison is
# implemented by comparing names and providers.
def <=>(other)
return super if !other.is_a?(self.class)
name <=> other.name
# Comparison is done by composing the name and provider
"#{@name}-#{@provider}" <=> "#{other.name}-#{other.provider}"
end
end
end

View File

@ -1,67 +0,0 @@
require "json"
module Vagrant
# Represents a "box," which is a package Vagrant environment that is used
# as a base image when creating a new guest machine.
#
# XXX: This will be renamed to "Box" when it is more stable and functional,
# and the old Box will be removed.
class Box2
include Comparable
# The box name. This is the logical name used when adding the box.
#
# @return [String]
attr_reader :name
# This is the provider that this box is built for.
#
# @return [Symbol]
attr_reader :provider
# This is the directory on disk where this box exists.
#
# @return [Pathname]
attr_reader :directory
# This is the metadata for the box. This is read from the "metadata.json"
# file that all boxes require.
#
# @return [Hash]
attr_reader :metadata
# 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, directory)
@name = name
@provider = provider
@directory = directory
@metadata = JSON.parse(directory.join("metadata.json").read)
end
# This deletes the box. This is NOT undoable.
def destroy!
# Delete the directory to delete the box.
FileUtils.rm_r(@directory)
# Just return true always
true
rescue Errno::ENOENT
# This means the directory didn't exist. Not a problem.
return true
end
# Implemented for comparison with other boxes. Comparison is
# implemented by comparing names and providers.
def <=>(other)
return super if !other.is_a?(self.class)
# Comparison is done by composing the name and provider
"#{@name}-#{@provider}" <=> "#{other.name}-#{other.provider}"
end
end
end

View File

@ -1,58 +1,222 @@
require 'forwardable'
require "digest/sha1"
require "archive/tar/minitar"
require "log4r"
module Vagrant
# Represents a collection of boxes, providing helpful methods for
# finding boxes.
# Represents a collection a boxes found on disk. This provides methods
# for accessing/finding individual boxes, adding new boxes, or deleting
# boxes.
class BoxCollection
include Enumerable
extend Forwardable
def_delegators :@boxes, :length, :each
# The directory that the boxes are being searched for.
# The directory where the boxes in this collection are stored.
#
# @return [Pathname]
attr_reader :directory
# Initializes the class to search for boxes in the given directory.
def initialize(directory, action_runner)
# Initializes the collection.
#
# @param [Pathname] directory The directory that contains the collection
# of boxes.
def initialize(directory)
@directory = directory
@boxes = []
@action_runner = action_runner
reload!
@logger = Log4r::Logger.new("vagrant::box_collection")
end
# Find a box in the collection by the given name. The name must
# be a string, for now.
def find(name)
@boxes.each do |box|
return box if box.name == name
# This adds a new box to the system.
#
# There are some exceptional cases:
# * BoxAlreadyExists - The box you're attempting to add already exists.
# * 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.
#
# @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
# meant as a basic check.
def add(path, name, provider)
@logger.debug("Adding box: #{name} (#{provider}) from #{path}")
if find(name, provider)
@logger.error("Box already exists, can't add: #{name} #{provider}")
raise Errors::BoxAlreadyExists, :name => name, :provider => provider
end
box_dir = @directory.join(name, provider.to_s)
@logger.debug("New box directory: #{box_dir}")
# Create the directory that'll store our box
box_dir.mkpath
# Change directory to the box directory and unpackage the tar
Dir.chdir(box_dir) do
@logger.debug("Unpacking box file into box directory...")
begin
Archive::Tar::Minitar.unpack(path.to_s, box_dir.to_s)
rescue SystemCallError
raise Errors::BoxUnpackageFailure
end
end
# Find the box we just added
box = find(name, provider)
# Verify that the provider matches. If not, then we need to rollback
box_provider = box.metadata["provider"]
if box_provider.to_sym != provider
@logger.error("Added box provider doesnt match expected: #{box_provider}")
# Delete the directory
@logger.debug("Deleting the added box directory...")
box_dir.rmtree
# Raise an exception
raise Errors::BoxProviderDoesntMatch, :expected => provider, :actual => box_provider
end
# Return the box
box
end
# 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.
def all
results = []
@logger.debug("Finding all boxes in: #{@directory}")
@directory.children(true).each do |child|
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
# 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}")
end
end
end
results
end
# Find a box in the collection with the given name and provider.
#
# @param [String] name Name of the box (logical name).
# @Param [String] provider Provider that the box implements.
# @return [Box] The box found, or `nil` if not found.
def find(name, 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.debug("Box found: #{name} (#{provider})")
return Box.new(name, provider, box_directory.dirname)
end
# 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?(name)
@logger.warn("V1 box found: #{name}")
raise Errors::BoxUpgradeRequired, :name => name
end
# Didn't find it, return nil
@logger.info("Box not found: #{name} (#{provider})")
nil
end
# Adds a box to this collection with the given name and located
# at the given URL.
def add(name, url)
raise Errors::BoxAlreadyExists, :name => name if find(name)
# 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.
#
# @return [Boolean] `true` otherwise an exception is raised.
def upgrade(name)
@logger.debug("Upgrade request for box: #{name}")
box_dir = @directory.join(name)
@action_runner.run(:box_add,
:box_name => name,
:box_url => url,
:box_directory => @directory.join(name))
# If the box doesn't exist at all, raise an exception
raise Errors::BoxNotFound, :name => name if !box_dir.directory?
if v1_box?(name)
@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
# Loads the list of all boxes from the source. This modifies the
# current array.
def reload!
@boxes.clear
Dir.open(@directory) do |dir|
dir.each do |d|
next if d == "." || d == ".." || !@directory.join(d).directory?
@boxes << Box.new(d, @directory.join(d), @action_runner)
end
end
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
# Rename the temporary directory to the provider.
temp_dir.rename(box_dir.join("virtualbox"))
@logger.info("Box '#{name}' upgraded from V1 to V2.")
end
# We did it! Or the v1 box didn't exist so it doesn't matter.
return true
end
protected
# This checks if the given name represents a V1 box on the system.
#
# @return [Boolean]
def v1_box?(name)
# 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?
end
end
end

View File

@ -1,220 +0,0 @@
require "digest/sha1"
require "archive/tar/minitar"
require "log4r"
module Vagrant
# Represents a collection a boxes found on disk. This provides methods
# for accessing/finding individual boxes, adding new boxes, or deleting
# boxes.
#
# XXX: This will be renamed to "BoxCollection" when it is more stable
# and functional, and the old BoxCollection will be removed.
class BoxCollection2
# Initializes the collection.
#
# @param [Pathname] directory The directory that contains the collection
# of boxes.
def initialize(directory)
@directory = directory
@logger = Log4r::Logger.new("vagrant::box_collection")
end
# This adds a new box to the system.
#
# There are some exceptional cases:
# * BoxAlreadyExists - The box you're attempting to add already exists.
# * 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.
#
# @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
# meant as a basic check.
def add(path, name, provider)
@logger.debug("Adding box: #{name} (#{provider}) from #{path}")
if find(name, provider)
@logger.error("Box already exists, can't add: #{name} #{provider}")
raise Errors::BoxAlreadyExists, :name => name, :provider => provider
end
box_dir = @directory.join(name, provider.to_s)
@logger.debug("New box directory: #{box_dir}")
# Create the directory that'll store our box
box_dir.mkpath
# Change directory to the box directory and unpackage the tar
Dir.chdir(box_dir) do
@logger.debug("Unpacking box file into box directory...")
begin
Archive::Tar::Minitar.unpack(path.to_s, box_dir.to_s)
rescue SystemCallError
raise Errors::BoxUnpackageFailure
end
end
# Find the box we just added
box = find(name, provider)
# Verify that the provider matches. If not, then we need to rollback
box_provider = box.metadata["provider"]
if box_provider.to_sym != provider
@logger.error("Added box provider doesnt match expected: #{box_provider}")
# Delete the directory
@logger.debug("Deleting the added box directory...")
box_dir.rmtree
# Raise an exception
raise Errors::BoxProviderDoesntMatch, :expected => provider, :actual => box_provider
end
# Return the box
box
end
# 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.
def all
results = []
@logger.debug("Finding all boxes in: #{@directory}")
@directory.children(true).each do |child|
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
# 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}")
end
end
end
results
end
# Find a box in the collection with the given name and provider.
#
# @param [String] name Name of the box (logical name).
# @Param [String] provider Provider that the box implements.
# @return [Box] The box found, or `nil` if not found.
def find(name, 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.debug("Box found: #{name} (#{provider})")
return Box2.new(name, provider, box_directory.dirname)
end
# 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?(name)
@logger.warn("V1 box found: #{name}")
raise Errors::BoxUpgradeRequired, :name => name
end
# Didn't find it, return nil
@logger.info("Box not found: #{name} (#{provider})")
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.
#
# @return [Boolean] `true` otherwise an exception is raised.
def upgrade(name)
@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 if !box_dir.directory?
if v1_box?(name)
@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
# Rename the temporary directory to the provider.
temp_dir.rename(box_dir.join("virtualbox"))
@logger.info("Box '#{name}' upgraded from V1 to V2.")
end
# We did it! Or the v1 box didn't exist so it doesn't matter.
return true
end
protected
# This checks if the given name represents a V1 box on the system.
#
# @return [Boolean]
def v1_box?(name)
# 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?
end
end
end

View File

@ -121,7 +121,7 @@ module Vagrant
#
# @return [BoxCollection]
def boxes
@_boxes ||= BoxCollection.new(boxes_path, action_runner)
@_boxes ||= BoxCollection.new(boxes_path)
end
# Returns the VMs associated with this environment.
@ -425,7 +425,9 @@ module Vagrant
config = inner_load[subvm]
# Second pass, with the box
inner_load[subvm, boxes.find(config.vm.box)]
box = nil
box = boxes.find(config.vm.box, :virtualbox) if config.vm.box
inner_load[subvm, box]
end
# Finally, we have our configuration. Set it and forget it.

View File

@ -19,7 +19,8 @@ module Vagrant
@vm = nil
@env = env
@config = config
@box = env.boxes.find(config.vm.box)
@box = nil
@box = env.boxes.find(config.vm.box, :virtualbox) if config.vm.box
opts ||= {}
if opts[:base]

View File

@ -60,7 +60,12 @@ module Unit
# @param [String] name Name of the box
# @param [Symbol] provider Provider the box was built for.
# @return [Pathname] Path to the box directory.
def box2(name, provider)
def box2(name, provider, options=nil)
# Default options
options = {
:vagrantfile => ""
}.merge(options || {})
# Make the box directory
box_dir = boxes_dir.join(name, provider.to_s)
box_dir.mkpath
@ -71,6 +76,12 @@ module Unit
f.write("{}")
end
# Create a Vagrantfile
box_vagrantfile = box_dir.join("Vagrantfile")
box_vagrantfile.open("w") do |f|
f.write(options[:vagrantfile])
end
# Return the box directory
box_dir
end

View File

@ -1,86 +0,0 @@
require File.expand_path("../../base", __FILE__)
require "pathname"
describe Vagrant::Box2 do
include_context "unit"
let(:environment) { isolated_environment }
let(:name) { "foo" }
let(:provider) { :virtualbox }
let(:directory) { environment.box2("foo", :virtualbox) }
let(:instance) { described_class.new(name, provider, directory) }
it "provides the name" do
instance.name.should == name
end
it "provides the provider" do
instance.provider.should == provider
end
it "provides the directory" do
instance.directory.should == directory
end
it "provides the metadata associated with a box" do
data = { "foo" => "bar" }
# Write the metadata
directory.join("metadata.json").open("w") do |f|
f.write(JSON.generate(data))
end
# Verify the metadata
instance.metadata.should == data
end
describe "destroying" do
it "should destroy an existing box" do
# Verify that our "box" exists
directory.exist?.should be
# Destroy it
instance.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
# Delete the directory
directory.rmtree
# Destroy it
box.destroy!.should be
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)
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)
a.should_not == 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)
[c, a, b].sort.should == [a, b, c]
end
end
end

View File

@ -1,158 +0,0 @@
require File.expand_path("../../base", __FILE__)
require "pathname"
describe Vagrant::BoxCollection2 do
include_context "unit"
let(:box_class) { Vagrant::Box2 }
let(:environment) { isolated_environment }
let(:instance) { described_class.new(environment.boxes_dir) }
describe "adding" do
it "should add a valid box to the system" 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
# Verify we can find it as well
box = instance.find("foo", :virtualbox)
box.should_not be_nil
end
it "should raise an exception if the box already exists" do
prev_box_name = "foo"
prev_box_provider = :virtualbox
# Create the box we're adding
environment.box2(prev_box_name, 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)
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, :vmware) }.
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
bad_provider = :vmware
# Create a VirtualBox box file
box_path = environment.box2_file(good_provider)
# Add the box but with an invalid provider, verify we get the proper
# error.
expect { instance.add(box_path, box_name, bad_provider) }.
to raise_error(Vagrant::Errors::BoxProviderDoesntMatch)
# Verify the box doesn't exist
instance.find(box_name, bad_provider).should be_nil
end
it "should raise an exception if you add an invalid box file" do
pending "I don't know how to generate an invalid tar."
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.sort
results.length.should == 3
results.should == [["foo", :virtualbox], ["foo", :vmware], ["bar", :ec2]].sort
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
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
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
end

View File

@ -1,56 +1,162 @@
require File.expand_path("../../base", __FILE__)
require "pathname"
describe Vagrant::BoxCollection do
include_context "unit"
let(:box_class) { Vagrant::Box }
let(:environment) { isolated_environment }
let(:action_runner) { double("action runner") }
let(:instance) { described_class.new(environment.boxes_dir, action_runner) }
let(:instance) { described_class.new(environment.boxes_dir) }
it "should list all available boxes" do
# No boxes yet.
instance.length.should == 0
it "should tell us the directory it is using" do
instance.directory.should == environment.boxes_dir
end
# Add some boxes to the environment and try again
environment.box("foo")
environment.box("bar")
instance.reload!
instance.length.should == 2
describe "adding" do
it "should add a valid box to the system" 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
# Verify we can find it as well
box = instance.find("foo", :virtualbox)
box.should_not be_nil
end
it "should raise an exception if the box already exists" do
prev_box_name = "foo"
prev_box_provider = :virtualbox
# Create the box we're adding
environment.box2(prev_box_name, 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)
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, :vmware) }.
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
bad_provider = :vmware
# Create a VirtualBox box file
box_path = environment.box2_file(good_provider)
# Add the box but with an invalid provider, verify we get the proper
# error.
expect { instance.add(box_path, box_name, bad_provider) }.
to raise_error(Vagrant::Errors::BoxProviderDoesntMatch)
# Verify the box doesn't exist
instance.find(box_name, bad_provider).should be_nil
end
it "should raise an exception if you add an invalid box file" do
pending "I don't know how to generate an invalid tar."
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.sort
results.length.should == 3
results.should == [["foo", :virtualbox], ["foo", :vmware], ["bar", :ec2]].sort
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
end
describe "finding" do
it "should return nil if it can't find the box" do
instance.find("foo").should be_nil
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 instance for any boxes it does find" do
environment.box("foo")
result = instance.find("foo")
result.should be_kind_of(Vagrant::Box)
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
end
it "should throw an error if the box already exists when adding" do
environment.box("foo")
expect { instance.add("foo", "bar") }.to raise_error(Vagrant::Errors::BoxAlreadyExists)
end
it "should throw an exception if it is a v1 box" do
# Create a V1 box
environment.box1("foo")
it "should add the box" do
name = "foo"
url = "bar"
# Test the invocation of the action runner with the proper name
# and parameters. We leave the testing of the actual stack to
# acceptance tests, and individual pieces to unit tests of each
# step.
options = {
:box_name => name,
:box_url => url,
:box_directory => instance.directory.join(name)
}
action_runner.should_receive(:run).with(:box_add, options)
instance.add(name, url)
# Test!
expect { instance.find("foo", :virtualbox) }.
to raise_error(Vagrant::Errors::BoxUpgradeRequired)
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
end

View File

@ -1,34 +1,86 @@
require File.expand_path("../../base", __FILE__)
require "pathname"
describe Vagrant::Box do
include_context "unit"
let(:environment) { isolated_environment }
let(:name) { "foo" }
let(:directory) { "bar" }
let(:action_runner) { double("action_runner") }
let(:instance) { described_class.new(name, directory, action_runner) }
let(:provider) { :virtualbox }
let(:directory) { environment.box2("foo", :virtualbox) }
let(:instance) { described_class.new(name, provider, directory) }
it "provides the name" do
instance.name.should == name
end
it "can destroy itself" do
# Simply test the messages to the action runner
options = {
:box_name => name,
:box_directory => directory
}
action_runner.should_receive(:run).with(:box_remove, options)
instance.destroy
it "provides the provider" do
instance.provider.should == provider
end
it "can repackage itself" do
# Simply test the messages to the action runner
options = {
:box_name => name,
:box_directory => directory
}
action_runner.should_receive(:run).with(:box_repackage, options)
it "provides the directory" do
instance.directory.should == directory
end
instance.repackage
it "provides the metadata associated with a box" do
data = { "foo" => "bar" }
# Write the metadata
directory.join("metadata.json").open("w") do |f|
f.write(JSON.generate(data))
end
# Verify the metadata
instance.metadata.should == data
end
describe "destroying" do
it "should destroy an existing box" do
# Verify that our "box" exists
directory.exist?.should be
# Destroy it
instance.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
# Delete the directory
directory.rmtree
# Destroy it
box.destroy!.should be
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)
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)
a.should_not == 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)
[c, a, b].sort.should == [a, b, c]
end
end
end

View File

@ -191,7 +191,7 @@ Vagrant::Config.run do |config|
end
VF
env.box("base", <<-VF)
env.box2("base", :virtualbox, :vagrantfile => <<-VF)
Vagrant::Config.run do |config|
config.ssh.port = 100
end