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 module Vagrant
autoload :Action, 'vagrant/action' autoload :Action, 'vagrant/action'
autoload :Box, 'vagrant/box' autoload :Box, 'vagrant/box'
autoload :Box2, 'vagrant/box2'
autoload :BoxCollection, 'vagrant/box_collection' autoload :BoxCollection, 'vagrant/box_collection'
autoload :BoxCollection2, 'vagrant/box_collection2'
autoload :CLI, 'vagrant/cli' autoload :CLI, 'vagrant/cli'
autoload :Command, 'vagrant/command' autoload :Command, 'vagrant/command'
autoload :Communication, 'vagrant/communication' autoload :Communication, 'vagrant/communication'

View File

@ -1,44 +1,64 @@
require "json"
module Vagrant module Vagrant
# Represents a "box," which is simply a packaged vagrant environment. # Represents a "box," which is a package Vagrant environment that is used
# Boxes are simply `tar` files which contain an exported VirtualBox # as a base image when creating a new guest machine.
# 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.
class Box 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 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 attr_reader :directory
# Creates a new box instance. Given an optional `name` parameter, # This is the metadata for the box. This is read from the "metadata.json"
# newly created instance will have that name, otherwise it defaults # file that all boxes require.
# to `nil`.
# #
# **Note:** This method does not actually _create_ the box, but merely # @return [Hash]
# returns a new, abstract representation of it. To add a box, see {#add}. attr_reader :metadata
def initialize(name, directory, action_runner)
@name = name # This is used to initialize a box.
@directory = directory #
@action_runner = action_runner # @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 end
# Begins the process of destroying this box. This cannot be undone! # This deletes the box. This is NOT undoable.
def destroy def destroy!
@action_runner.run(:box_remove, { :box_name => @name, :box_directory => @directory }) # 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 end
# Begins sequence to repackage this box. # Implemented for comparison with other boxes. Comparison is
def repackage(options=nil) # implemented by comparing names and providers.
@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.
def <=>(other) def <=>(other)
return super if !other.is_a?(self.class) 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 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 module Vagrant
# Represents a collection of boxes, providing helpful methods for # Represents a collection a boxes found on disk. This provides methods
# finding boxes. # for accessing/finding individual boxes, adding new boxes, or deleting
# boxes.
class BoxCollection class BoxCollection
include Enumerable # The directory where the boxes in this collection are stored.
extend Forwardable #
def_delegators :@boxes, :length, :each # @return [Pathname]
# The directory that the boxes are being searched for.
attr_reader :directory attr_reader :directory
# Initializes the class to search for boxes in the given directory. # Initializes the collection.
def initialize(directory, action_runner) #
@directory = directory # @param [Pathname] directory The directory that contains the collection
@boxes = [] # of boxes.
@action_runner = action_runner def initialize(directory)
@directory = directory
reload! @logger = Log4r::Logger.new("vagrant::box_collection")
end end
# Find a box in the collection by the given name. The name must # This adds a new box to the system.
# be a string, for now. #
def find(name) # There are some exceptional cases:
@boxes.each do |box| # * BoxAlreadyExists - The box you're attempting to add already exists.
return box if box.name == name # * 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 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 nil
end end
# Adds a box to this collection with the given name and located # Upgrades a V1 box with the given name to a V2 box. If a box with the
# at the given URL. # given name doesn't exist, then a `BoxNotFound` exception will be raised.
def add(name, url) # If the given box is found but is not a V1 box then `true` is returned
raise Errors::BoxAlreadyExists, :name => name if find(name) # 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, # If the box doesn't exist at all, raise an exception
:box_name => name, raise Errors::BoxNotFound, :name => name if !box_dir.directory?
:box_url => url,
:box_directory => @directory.join(name)) 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 end
# Loads the list of all boxes from the source. This modifies the protected
# current array.
def reload!
@boxes.clear
Dir.open(@directory) do |dir| # This checks if the given name represents a V1 box on the system.
dir.each do |d| #
next if d == "." || d == ".." || !@directory.join(d).directory? # @return [Boolean]
@boxes << Box.new(d, @directory.join(d), @action_runner) def v1_box?(name)
end # We detect a V1 box given by whether there is a "box.ovf" which
end # is a heuristic but is pretty accurate.
@directory.join(name, "box.ovf").file?
end end
end 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] # @return [BoxCollection]
def boxes def boxes
@_boxes ||= BoxCollection.new(boxes_path, action_runner) @_boxes ||= BoxCollection.new(boxes_path)
end end
# Returns the VMs associated with this environment. # Returns the VMs associated with this environment.
@ -425,7 +425,9 @@ module Vagrant
config = inner_load[subvm] config = inner_load[subvm]
# Second pass, with the box # 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 end
# Finally, we have our configuration. Set it and forget it. # Finally, we have our configuration. Set it and forget it.

View File

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

View File

@ -60,7 +60,12 @@ module Unit
# @param [String] name Name of the box # @param [String] name Name of the box
# @param [Symbol] provider Provider the box was built for. # @param [Symbol] provider Provider the box was built for.
# @return [Pathname] Path to the box directory. # @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 # Make the box directory
box_dir = boxes_dir.join(name, provider.to_s) box_dir = boxes_dir.join(name, provider.to_s)
box_dir.mkpath box_dir.mkpath
@ -71,6 +76,12 @@ module Unit
f.write("{}") f.write("{}")
end 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 # Return the box directory
box_dir box_dir
end 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 File.expand_path("../../base", __FILE__)
require "pathname"
describe Vagrant::BoxCollection do describe Vagrant::BoxCollection do
include_context "unit" include_context "unit"
let(:environment) { isolated_environment } let(:box_class) { Vagrant::Box }
let(:action_runner) { double("action runner") } let(:environment) { isolated_environment }
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 it "should tell us the directory it is using" do
# No boxes yet. instance.directory.should == environment.boxes_dir
instance.length.should == 0 end
# Add some boxes to the environment and try again describe "adding" do
environment.box("foo") it "should add a valid box to the system" do
environment.box("bar") box_path = environment.box2_file(:virtualbox)
instance.reload!
instance.length.should == 2 # 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 end
describe "finding" do describe "finding" do
it "should return nil if it can't find the box" do it "should return nil if the box does not exist" do
instance.find("foo").should be_nil instance.find("foo", :i_dont_exist).should be_nil
end end
it "should return a box instance for any boxes it does find" do it "should return a box if the box does exist" do
environment.box("foo") # Create the "box"
result = instance.find("foo") environment.box2("foo", :virtualbox)
result.should be_kind_of(Vagrant::Box)
# Actual test
result = instance.find("foo", :virtualbox)
result.should_not be_nil
result.should be_kind_of(box_class)
result.name.should == "foo" result.name.should == "foo"
end 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 end
it "should throw an error if the box already exists when adding" do describe "upgrading" do
environment.box("foo") it "should upgrade a V1 box to V2" do
expect { instance.add("foo", "bar") }.to raise_error(Vagrant::Errors::BoxAlreadyExists) # Create a V1 box
end environment.box1("foo")
it "should add the box" do # Verify that only a V1 box exists
name = "foo" expect { instance.find("foo", :virtualbox) }.
url = "bar" to raise_error(Vagrant::Errors::BoxUpgradeRequired)
# Test the invocation of the action runner with the proper name # Upgrade the box
# and parameters. We leave the testing of the actual stack to instance.upgrade("foo").should be
# 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) # 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
end end

View File

@ -1,34 +1,86 @@
require File.expand_path("../../base", __FILE__) require File.expand_path("../../base", __FILE__)
require "pathname"
describe Vagrant::Box do describe Vagrant::Box do
include_context "unit"
let(:environment) { isolated_environment }
let(:name) { "foo" } let(:name) { "foo" }
let(:directory) { "bar" } let(:provider) { :virtualbox }
let(:action_runner) { double("action_runner") } let(:directory) { environment.box2("foo", :virtualbox) }
let(:instance) { described_class.new(name, directory, action_runner) } let(:instance) { described_class.new(name, provider, directory) }
it "provides the name" do it "provides the name" do
instance.name.should == name instance.name.should == name
end end
it "can destroy itself" do it "provides the provider" do
# Simply test the messages to the action runner instance.provider.should == provider
options = {
:box_name => name,
:box_directory => directory
}
action_runner.should_receive(:run).with(:box_remove, options)
instance.destroy
end end
it "can repackage itself" do it "provides the directory" do
# Simply test the messages to the action runner instance.directory.should == directory
options = { end
:box_name => name,
:box_directory => directory
}
action_runner.should_receive(:run).with(:box_repackage, options)
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
end end

View File

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