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:
parent
e7bed7c2ff
commit
da15105a8f
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
@name = name
|
||||
@directory = directory
|
||||
@action_runner = 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
|
||||
@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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
@directory = directory
|
||||
@boxes = []
|
||||
@action_runner = action_runner
|
||||
|
||||
reload!
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# Loads the list of all boxes from the source. This modifies the
|
||||
# current array.
|
||||
def reload!
|
||||
@boxes.clear
|
||||
protected
|
||||
|
||||
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
|
||||
# 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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,56 +1,162 @@
|
|||
require File.expand_path("../../base", __FILE__)
|
||||
|
||||
require "pathname"
|
||||
|
||||
describe Vagrant::BoxCollection do
|
||||
include_context "unit"
|
||||
|
||||
let(:environment) { isolated_environment }
|
||||
let(:action_runner) { double("action runner") }
|
||||
let(:instance) { described_class.new(environment.boxes_dir, action_runner) }
|
||||
let(:box_class) { Vagrant::Box }
|
||||
let(:environment) { isolated_environment }
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
describe "upgrading" do
|
||||
it "should upgrade a V1 box to V2" do
|
||||
# Create a V1 box
|
||||
environment.box1("foo")
|
||||
|
||||
it "should add the box" do
|
||||
name = "foo"
|
||||
url = "bar"
|
||||
# Verify that only a V1 box exists
|
||||
expect { instance.find("foo", :virtualbox) }.
|
||||
to raise_error(Vagrant::Errors::BoxUpgradeRequired)
|
||||
|
||||
# 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)
|
||||
# Upgrade the box
|
||||
instance.upgrade("foo").should be
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue