Merge pull request #2943 from mitchellh/f-boxes-2

Boxes Revamp: Short URLs, Versions, Updates!

* **Box versioning** - All boxes can have versions now. This version information is available in metadata requested by Vagrant. New commands such as `vagrant box outdated` and new settings such as `config.vm.box_check_update` will configure how/when Vagrant checks for box updates. Boxes that don't support versions will always have version "0".

* **Shortnames for boxes** - You can now do things like `vagrant box add hashicorp/precise64`. The name and the URL are one and the same. This requires a backend. We'll be hosting one [for free public access] in the coming month or two. Of course, you don't need to use the backend we'll provide to support versions or unversioned boxes. You would need to for shortnames.

* **Multiple providers at one URL** - A single URL can now represent multiple providers. In this case, Vagrant will ask you what provider you want to download if it can't figure it out on its own. 

All of this is _fully backwards compatible_. I verified this as best I could with the tests in vagrant-spec. I have  to write new acceptance tests for the new features, however. Also, this PR adds around 100 unit tests.
This commit is contained in:
Mitchell Hashimoto 2014-02-06 20:09:56 -08:00
commit 0f2fff2420
52 changed files with 4686 additions and 1058 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ acceptance_config.yml
boxes/*
/Vagrantfile
/.vagrant
/website/docs/.vagrant
/website/www/.vagrant
/vagrant-spec.config.rb

View File

@ -9,6 +9,7 @@ module Vagrant
# and are thus available to all plugins as a "standard library" of sorts.
module Builtin
autoload :BoxAdd, "vagrant/action/builtin/box_add"
autoload :BoxCheckOutdated, "vagrant/action/builtin/box_check_outdated"
autoload :BoxRemove, "vagrant/action/builtin/box_remove"
autoload :Call, "vagrant/action/builtin/call"
autoload :Confirm, "vagrant/action/builtin/confirm"
@ -16,6 +17,7 @@ module Vagrant
autoload :DestroyConfirm, "vagrant/action/builtin/destroy_confirm"
autoload :EnvSet, "vagrant/action/builtin/env_set"
autoload :GracefulHalt, "vagrant/action/builtin/graceful_halt"
autoload :HandleBox, "vagrant/action/builtin/handle_box"
autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url"
autoload :HandleForwardedPortCollisions, "vagrant/action/builtin/handle_forwarded_port_collisions"
autoload :Lock, "vagrant/action/builtin/lock"
@ -44,6 +46,14 @@ module Vagrant
end
end
# This actions checks if a box is outdated in a given Vagrant
# environment for a single machine.
def self.action_box_outdated
Builder.new.tap do |b|
b.use Builtin::BoxCheckOutdated
end
end
# This is the action that will remove a box given a name (and optionally
# a provider). This middleware sequence is built-in to Vagrant. Plugins
# can hook into this like any other middleware sequence.

View File

@ -1,6 +1,9 @@
require "digest/sha1"
require "log4r"
require "pathname"
require "uri"
require "vagrant/box_metadata"
require "vagrant/util/downloader"
require "vagrant/util/file_checksum"
require "vagrant/util/platform"
@ -19,34 +22,205 @@ module Vagrant
def call(env)
@download_interrupted = false
box_name = env[:box_name]
box_formats = env[:box_provider]
if box_formats
# Determine the formats a box can support and allow the box to
# be any of those formats.
provider_plugin = Vagrant.plugin("2").manager.providers[env[:box_provider]]
if provider_plugin
box_formats = provider_plugin[1][:box_format]
box_formats ||= env[:box_provider]
url = Array(env[:box_url]).map do |u|
u = u.gsub("\\", "/")
if Util::Platform.windows? && u =~ /^[a-z]:/i
# On Windows, we need to be careful about drive letters
u = "file://#{u}"
end
if u =~ /^[a-z0-9]+:.*$/i && !u.start_with?("file://")
# This is not a file URL... carry on
next u
end
# Expand the path and try to use that, if possible
p = File.expand_path(u.gsub(/^file:\/\//, ""))
p = Util::Platform.cygwin_windows_path(p)
next "file://#{p}" if File.file?(p)
u
end
# If we received a shorthand URL ("mitchellh/precise64"),
# then expand it properly.
expanded = false
url.each_index do |i|
next if url[i] !~ /^[^\/]+\/[^\/]+$/
if !File.file?(url[i])
server = Vagrant.server_url
raise Errors::BoxServerNotSet if !server
expanded = true
url[i] = "#{server}/#{url[i]}"
end
end
# Determine if we already have the box before downloading
# it again. We can only do this if we specify a format
if box_formats && !env[:box_force]
# Test if any of our URLs point to metadata
is_metadata_results = url.map do |u|
begin
if env[:box_collection].find(box_name, box_formats)
raise Errors::BoxAlreadyExists,
:name => box_name,
:formats => [box_formats].flatten.join(", ")
end
rescue Vagrant::Errors::BoxUpgradeRequired
# If the box needs to be upgraded, do it.
env[:box_collection].upgrade(box_name)
retry
metadata_url?(u, env)
rescue Errors::DownloaderError => e
e
end
end
if expanded && url.length == 1
is_error = is_metadata_results.find do |b|
b.is_a?(Errors::DownloaderError)
end
if is_error
raise Errors::BoxAddShortNotFound,
error: is_error.extra_data[:message],
name: env[:box_url],
url: url
end
end
is_metadata = is_metadata_results.any? { |b| b === true }
if is_metadata && url.length > 1
raise Errors::BoxAddMetadataMultiURL,
urls: url.join(", ")
end
if is_metadata
add_from_metadata(url.first, env, expanded)
else
add_direct(url, env)
end
@app.call(env)
end
# Adds a box file directly (no metadata component, versioning,
# etc.)
#
# @param [Array<String>] urls
# @param [Hash] env
def add_direct(urls, env)
name = env[:box_name]
if !name || name == ""
raise Errors::BoxAddNameRequired
end
provider = env[:box_provider]
provider = Array(provider) if provider
box_add(
urls,
name,
"0",
provider,
nil,
env)
end
# Adds a box given that the URL is a metadata document.
def add_from_metadata(url, env, expanded)
original_url = env[:box_url]
provider = env[:box_provider]
provider = Array(provider) if provider
version = env[:box_version]
env[:ui].output(I18n.t(
"vagrant.box_loading_metadata",
name: Array(original_url).first))
if original_url != url
env[:ui].detail(I18n.t(
"vagrant.box_expanding_url", url: url))
end
metadata = nil
begin
metadata_path = download(url, env, ui: false)
File.open(metadata_path) do |f|
metadata = BoxMetadata.new(f)
end
rescue Errors::DownloaderError => e
raise if !expanded
raise Errors::BoxAddShortNotFound,
error: e.extra_data[:message],
name: original_url,
url: url
ensure
metadata_path.delete if metadata_path && metadata_path.file?
end
if env[:box_name] && metadata.name != env[:box_name]
raise Errors::BoxAddNameMismatch,
actual_name: metadata.name,
requested_name: env[:box_name]
end
metadata_version = metadata.version(
version || ">= 0", provider: provider)
if !metadata_version
if !provider
raise Errors::BoxAddNoMatchingVersion,
constraints: version || ">= 0",
name: metadata.name,
url: url,
versions: metadata.versions.join(", ")
else
# TODO: show supported providers
raise Errors::BoxAddNoMatchingProvider,
name: metadata.name,
requested: provider,
url: url
end
end
metadata_provider = nil
if provider
# If a provider was specified, make sure we get that specific
# version.
provider.each do |p|
metadata_provider = metadata_version.provider(p)
break if metadata_provider
end
elsif metadata_version.providers.length == 1
# If we have only one provider in the metadata, just use that
# provider.
metadata_provider = metadata_version.provider(
metadata_version.providers.first)
else
providers = metadata_version.providers.sort
choice = 0
options = providers.map do |p|
choice += 1
"#{choice}) #{p}"
end.join("\n")
# We have more than one provider, ask the user what they want
choice = env[:ui].ask(I18n.t(
"vagrant.box_add_choose_provider",
options: options) + " ", prefix: false)
choice = choice.to_i if choice
while !choice || choice <= 0 || choice > providers.length
choice = env[:ui].ask(I18n.t(
"vagrant.box_add_choose_provider_again") + " ",
prefix: false)
choice = choice.to_i if choice
end
metadata_provider = metadata_version.provider(
providers[choice-1])
end
box_add(
[metadata_provider.url],
metadata.name,
metadata_version.version,
metadata_provider.name,
url,
env)
end
=begin
# Determine the checksum type to use
checksum = (env[:box_checksum] || "").to_s
checksum_klass = nil
@ -104,46 +278,89 @@ module Vagrant
expected: checksum
end
end
end
=end
# Add the box
env[:ui].info I18n.t("vagrant.actions.box.add.adding", :name => box_name)
box_added = nil
protected
# Shared helper to add a box once you know various details
# about it. Shared between adding via metadata or by direct.
#
# @param [Array<String>] urls
# @param [String] name
# @param [String] version
# @param [String] provider
# @param [Hash] env
# @return [Box]
def box_add(urls, name, version, provider, md_url, env, **opts)
env[:ui].output(I18n.t(
"vagrant.box_add_with_version",
name: name,
version: version,
providers: Array(provider).join(", ")))
# Verify the box we're adding doesn't already exist
if provider && !env[:box_force]
box = env[:box_collection].find(
name, provider, version)
if box
raise Errors::BoxAlreadyExists,
name: name,
provider: provider,
version: version
end
end
# Now we have a URL, we have to download this URL.
box = nil
begin
box_added = env[:box_collection].add(
@temp_path, box_name, box_formats, env[:box_force])
rescue Vagrant::Errors::BoxUpgradeRequired
# Upgrade the box
env[:box_collection].upgrade(box_name)
box_url = nil
# Try adding it again
retry
urls.each do |url|
begin
box_url = download(url, env)
break
rescue Errors::DownloaderError => e
env[:ui].error(I18n.t(
"vagrant.box_download_error", message: e.message))
box_url = nil
end
end
# Add the box!
box = env[:box_collection].add(
box_url, name, version,
force: env[:box_force],
metadata_url: md_url,
providers: provider)
ensure
# Make sure we delete the temporary file after we add it,
# unless we were interrupted, in which case we keep it around
# so we can resume the download later.
if !@download_interrupted
@logger.debug("Deleting temporary box: #{box_url}")
box_url.delete if box_url
end
end
# Call the 'recover' method in all cases to clean up the
# downloaded temporary file.
recover(env)
env[:ui].success(I18n.t(
"vagrant.box_added",
name: box.name,
version: box.version,
provider: box.provider))
# Success, we added a box!
env[:ui].success(
I18n.t("vagrant.actions.box.add.added", name: box_added.name, provider: box_added.provider))
# Store the added box in the env for future middleware
env[:box_added] = box
# Persists URL used on download and the time it was added
write_extra_info(box_added, download_url)
# Passes on the newly added box to the rest of the middleware chain
env[:box_added] = box_added
# Carry on!
@app.call(env)
box
end
def recover(env)
if @temp_path && File.exist?(@temp_path) && !@download_interrupted
File.unlink(@temp_path)
end
end
# Returns the download options for the download.
#
# @return [Hash]
def downloader(url, env, **opts)
opts[:ui] = true if !opts.has_key?(:ui)
def download_box_url(url, env)
temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url))
@logger.info("Downloading box: #{url} => #{temp_path}")
@ -154,13 +371,6 @@ module Vagrant
url = "file:#{file_path}"
end
downloader_options = {}
downloader_options[:ca_cert] = env[:box_download_ca_cert]
downloader_options[:continue] = true
downloader_options[:insecure] = env[:box_download_insecure]
downloader_options[:ui] = env[:ui]
downloader_options[:client_cert] = env[:box_client_cert]
# If the temporary path exists, verify it is not too old. If its
# too old, delete it first because the data may have changed.
if temp_path.file?
@ -176,19 +386,35 @@ module Vagrant
temp_path.unlink if delete
end
downloader_options = {}
downloader_options[:ca_cert] = env[:box_download_ca_cert]
downloader_options[:continue] = true
downloader_options[:insecure] = env[:box_download_insecure]
downloader_options[:client_cert] = env[:box_client_cert]
downloader_options[:ui] = env[:ui] if opts[:ui]
Util::Downloader.new(url, temp_path, downloader_options)
end
def download(url, env, **opts)
opts[:ui] = true if !opts.has_key?(:ui)
d = downloader(url, env, **opts)
# Download the box to a temporary path. We store the temporary
# path as an instance variable so that the `#recover` method can
# access it.
env[:ui].info(I18n.t(
"vagrant.actions.box.download.downloading",
url: url))
if temp_path.file?
env[:ui].info(I18n.t("vagrant.actions.box.download.resuming"))
if opts[:ui]
env[:ui].detail(I18n.t(
"vagrant.box_downloading",
url: url))
if File.file?(d.destination)
env[:ui].info(I18n.t("vagrant.actions.box.download.resuming"))
end
end
begin
downloader = Util::Downloader.new(url, temp_path, downloader_options)
downloader.download!
d.download!
rescue Errors::DownloaderInterrupted
# The downloader was interrupted, so just return, because that
# means we were interrupted as well.
@ -196,18 +422,45 @@ module Vagrant
env[:ui].info(I18n.t("vagrant.actions.box.download.interrupted"))
rescue Errors::DownloaderError
# The download failed for some reason, clean out the temp path
temp_path.unlink if temp_path.file?
File.unlink(d.destination) if File.file?(d.destination)
raise
end
temp_path
Pathname.new(d.destination)
end
def write_extra_info(box_added, url)
info = {'url' => url, 'downloaded_at' => Time.now.utc}
box_added.directory.join('info.json').open("w+") do |f|
f.write(JSON.dump(info))
# Tests whether the given URL points to a metadata file or a
# box file without completely downloading the file.
#
# @param [String] url
# @return [Boolean] true if metadata
def metadata_url?(url, env)
d = downloader(url, env, ui: false)
# If we're downloading a file, cURL just returns no
# content-type (makes sense), so we just test if it is JSON
# by trying to parse JSON!
uri = URI.parse(d.source)
if uri.scheme == "file"
url = uri.path
url ||= uri.opaque
begin
File.open(url, "r") do |f|
BoxMetadata.new(f)
end
return true
rescue Errors::BoxMetadataMalformed
return false
rescue Errno::ENOENT
return false
end
end
output = d.head
match = output.scan(/^Content-Type: (.+?)$/).last
return false if !match
match.last.chomp == "application/json"
end
end
end

View File

@ -0,0 +1,83 @@
require "log4r"
module Vagrant
module Action
module Builtin
# This middleware checks if there are outdated boxes. By default,
# it only checks locally, but if `box_outdated_refresh` is set, it
# will refresh the metadata associated with a box.
class BoxCheckOutdated
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new(
"vagrant::action::builtin::box_check_outdated")
end
def call(env)
machine = env[:machine]
if !env[:box_outdated_force]
if !machine.config.vm.box_check_update
return @app.call(env)
end
end
if !machine.box
# The box doesn't exist. I suppose technically that means
# that it is "outdated" but we show a specialized error
# message anyways.
raise Errors::BoxOutdatedNoBox, name: machine.config.vm.box
end
box = machine.box
if box.version == "0" && !box.metadata_url
return @app.call(env)
end
constraints = machine.config.vm.box_version
env[:ui].output(I18n.t(
"vagrant.box_outdated_checking_with_refresh",
name: box.name))
update = nil
begin
update = box.has_update?(constraints)
rescue Errors::VagrantError => e
raise if !env[:box_outdated_ignore_errors]
env[:ui].detail(I18n.t(
"vagrant.box_outdated_metadata_error_single",
message: e.message))
end
env[:box_outdated] = update != nil
if update
env[:ui].warn(I18n.t(
"vagrant.box_outdated_single",
name: update[0].name,
current: box.version,
latest: update[1].version))
end
@app.call(env)
end
def check_outdated_local(env)
machine = env[:machine]
box = env[:box_collection].find(
machine.box.name, machine.box.provider,
"> #{machine.box.version}")
if box
env[:ui].warn(I18n.t(
"vagrant.box_outdated_local",
name: box.name,
old: machine.box.version,
new: box.version))
env[:box_outdated] = true
return
end
env[:box_outdated] = false
end
end
end
end
end

View File

@ -12,24 +12,67 @@ module Vagrant
def call(env)
box_name = env[:box_name]
box_provider = env[:box_provider].to_sym
box_provider = env[:box_provider]
box_provider = box_provider.to_sym if box_provider
box_version = env[:box_version]
box = nil
begin
box = env[:box_collection].find(box_name, box_provider)
rescue Vagrant::Errors::BoxUpgradeRequired
env[:box_collection].upgrade(box_name)
retry
boxes = {}
env[:box_collection].all.each do |n, v, p|
boxes[n] ||= {}
boxes[n][p] ||= []
boxes[n][p] << v
end
raise Vagrant::Errors::BoxNotFound, :name => box_name, :provider => box_provider if !box
all_box = boxes[box_name]
if !all_box
raise Errors::BoxRemoveNotFound, name: box_name
end
all_versions = nil
if !box_provider
if all_box.length == 1
# There is only one provider, just use that.
all_versions = all_box.values.first
box_provider = all_box.keys.first
else
raise Errors::BoxRemoveMultiProvider,
name: box_name,
providers: all_box.keys.map(&:to_s).sort.join(", ")
end
else
all_versions = all_box[box_provider]
if !all_versions
raise Errors::BoxRemoveProviderNotFound,
name: box_name,
provider: box_provider.to_s,
providers: all_box.keys.map(&:to_s).sort.join(", ")
end
end
if !box_version
if all_versions.length == 1
# There is only one version, just use that.
box_version = all_versions.first
else
# There are multiple versions, we can't choose.
raise Errors::BoxRemoveMultiVersion,
name: box_name,
provider: box_provider.to_s,
versions: all_versions.join(", ")
end
end
box = env[:box_collection].find(
box_name, box_provider, box_version)
env[:ui].info(I18n.t("vagrant.commands.box.removing",
:name => box_name,
:provider => box_provider))
:name => box.name,
:provider => box.provider))
box.destroy!
# Passes on the removed box to the rest of the middleware chain
env[:box_removed] = box
@app.call(env)
end
end

View File

@ -0,0 +1,20 @@
require "log4r"
module Vagrant
module Action
module Builtin
# This middleware updates a specific box if there are updates available.
class BoxUpdate
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new(
"vagrant::action::builtin::box_update")
end
def call(env)
machine = env[:machine]
end
end
end
end
end

View File

@ -0,0 +1,84 @@
require "thread"
require "log4r"
module Vagrant
module Action
module Builtin
# This built-in middleware handles the `box` setting by verifying
# the box is already installed, dowloading the box if it isn't,
# updating the box if it is requested, etc.
class HandleBox
@@big_lock = Mutex.new
@@small_locks = Hash.new { |h,k| h[k] = Mutex.new }
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::action::builtin::handle_box")
end
def call(env)
machine = env[:machine]
if !machine.config.vm.box
@logger.info("Skipping HandleBox because no box is set")
return @app.call(env)
end
# Acquire a lock for this box to handle multi-threaded
# environments.
lock = nil
@@big_lock.synchronize do
lock = @@small_locks[machine.config.vm.box]
end
lock.synchronize do
handle_box(env)
end
# Reload the environment and set the VM to be the new loaded VM.
env[:machine] = env[:machine].env.machine(
env[:machine].name, env[:machine].provider_name, true)
@app.call(env)
end
def handle_box(env)
machine = env[:machine]
if machine.box
@logger.info("Machine already has box. HandleBox will not run.")
return
end
# Determine the set of formats that this box can be in
box_download_ca_cert = env[:machine].config.vm.box_download_ca_cert
box_download_client_cert = env[:machine].config.vm.box_download_client_cert
box_download_insecure = env[:machine].config.vm.box_download_insecure
box_formats = env[:machine].provider_options[:box_format] ||
env[:machine].provider_name
env[:ui].output(I18n.t(
"vagrant.box_auto_adding", name: machine.config.vm.box))
env[:ui].detail("Box Provider: #{Array(box_formats).join(", ")}")
env[:ui].detail("Box Version: #{machine.config.vm.box_version}")
begin
env[:action_runner].run(Vagrant::Action.action_box_add, env.merge({
box_name: machine.config.vm.box,
box_url: machine.config.vm.box_url || machine.config.vm.box,
box_provider: box_formats,
box_version: machine.config.vm.box_version,
box_client_cert: box_download_client_cert,
box_download_ca_cert: box_download_ca_cert,
box_download_insecure: box_download_insecure,
}))
rescue Errors::BoxAlreadyExists
# Just ignore this, since it means the next part will succeed!
# This can happen in a multi-threaded environment.
end
end
end
end
end
end

View File

@ -1,96 +1,12 @@
require "thread"
require "log4r"
module Vagrant
module Action
module Builtin
# This built-in middleware handles the `box_url` setting, downloading
# the box if necessary. You should place this early in your middleware
# sequence for a provider after configuration validation but before
# you attempt to use any box.
class HandleBoxUrl
@@big_lock = Mutex.new
@@handle_box_url_locks = Hash.new { |h,k| h[k] = Mutex.new }
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::action::builtin::handle_box_url")
end
class HandleBoxUrl < HandleBox
def call(env)
if !env[:machine].config.vm.box || !env[:machine].config.vm.box_url
@logger.info("Skipping HandleBoxUrl because box or box_url not set.")
@app.call(env)
return
end
if env[:machine].box
@logger.info("Skipping HandleBoxUrl because box is already available")
@app.call(env)
return
end
# Get a "big lock" to make sure that our more fine grained
# lock access is thread safe.
lock = nil
@@big_lock.synchronize do
lock = @@handle_box_url_locks[env[:machine].config.vm.box]
end
box_name = env[:machine].config.vm.box
box_url = env[:machine].config.vm.box_url
box_download_ca_cert = env[:machine].config.vm.box_download_ca_cert
box_download_checksum = env[:machine].config.vm.box_download_checksum
box_download_checksum_type = env[:machine].config.vm.box_download_checksum_type
box_download_client_cert = env[:machine].config.vm.box_download_client_cert
box_download_insecure = env[:machine].config.vm.box_download_insecure
# Expand the CA cert file relative to the Vagrantfile path, if
# there is one.
if box_download_ca_cert
box_download_ca_cert = File.expand_path(
box_download_ca_cert, env[:machine].env.root_path)
end
lock.synchronize do
# Check that we don't already have the box, which can happen
# if we're slow to acquire the lock because of another thread
box_formats = env[:machine].provider_options[:box_format] ||
env[:machine].provider_name
if env[:box_collection].find(box_name, box_formats)
break
end
# Add the box then reload the box collection so that it becomes
# aware of it.
env[:ui].info I18n.t(
"vagrant.actions.vm.check_box.not_found",
:name => box_name,
:provider => env[:machine].provider_name)
begin
env[:action_runner].run(Vagrant::Action.action_box_add, {
:box_checksum => box_download_checksum,
:box_checksum_type => box_download_checksum_type,
:box_client_cert => box_download_client_cert,
:box_download_ca_cert => box_download_ca_cert,
:box_download_insecure => box_download_insecure,
:box_name => box_name,
:box_provider => box_formats,
:box_url => box_url,
})
rescue Errors::BoxAlreadyExists
# Just ignore this, since it means the next part will succeed!
# This can happen in a multi-threaded environment.
end
end
# Reload the environment and set the VM to be the new loaded VM.
env[:machine] = env[:machine].env.machine(
env[:machine].name, env[:machine].provider_name, true)
@app.call(env)
env[:ui].warn("HandleBoxUrl middleware is deprecated. Use HandleBox instead.")
env[:ui].warn("This is a bug with the provider. Please contact the creator")
env[:ui].warn("of the provider you use to fix this.")
super
end
end
end

View File

@ -1,8 +1,11 @@
require 'fileutils'
require "tempfile"
require "json"
require "log4r"
require "vagrant/box_metadata"
require "vagrant/util/downloader"
require "vagrant/util/platform"
require "vagrant/util/safe_chdir"
require "vagrant/util/subprocess"
@ -23,6 +26,11 @@ module Vagrant
# @return [Symbol]
attr_reader :provider
# The version of this box.
#
# @return [String]
attr_reader :version
# This is the directory on disk where this box exists.
#
# @return [Pathname]
@ -34,16 +42,24 @@ module Vagrant
# @return [Hash]
attr_reader :metadata
# This is the URL to the version info and other metadata for this
# box.
#
# @return [String]
attr_reader :metadata_url
# 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)
def initialize(name, provider, version, directory, **opts)
@name = name
@version = version
@provider = provider
@directory = directory
@metadata_url = opts[:metadata_url]
metadata_file = directory.join("metadata.json")
raise Errors::BoxMetadataFileNotFound, :name => @name if !metadata_file.file?
@ -69,6 +85,51 @@ module Vagrant
return true
end
# Loads the metadata URL and returns the latest metadata associated
# with this box.
#
# @return [BoxMetadata]
def load_metadata
tf = Tempfile.new("vagrant")
tf.close
url = @metadata_url
if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i
url = File.expand_path(url)
url = Util::Platform.cygwin_windows_path(url)
url = "file:#{url}"
end
Util::Downloader.new(url, tf.path).download!
BoxMetadata.new(File.open(tf.path, "r"))
end
# Checks if the box has an update and returns the metadata, version,
# and provider. If the box doesn't have an update that satisfies the
# constraints, it will return nil.
#
# This will potentially make a network call if it has to load the
# metadata from the network.
#
# @param [String] version Version constraints the update must
# satisfy. If nil, the version constrain defaults to being a
# larger version than this box.
# @return [Array]
def has_update?(version=nil)
if !@metadata_url
raise Errors::BoxUpdateNoMetadata, name: @name
end
version += ", " if version
version ||= ""
version += "> #{@version}"
md = self.load_metadata
newer = md.version(version, provider: @provider)
return nil if !newer
[md, newer, newer.provider(@provider)]
end
# This repackages this box and outputs it to the given path.
#
# @param [Pathname] path The full path (filename included) of where
@ -96,7 +157,8 @@ module Vagrant
return super if !other.is_a?(self.class)
# Comparison is done by composing the name and provider
"#{@name}-#{@provider}" <=> "#{other.name}-#{other.provider}"
"#{@name}-#{@version}-#{@provider}" <=>
"#{other.name}-#{other.version}-#{other.provider}"
end
end
end

View File

@ -1,5 +1,5 @@
require "digest/sha1"
require "thread"
require "monitor"
require "tmpdir"
require "log4r"
@ -44,7 +44,7 @@ module Vagrant
options ||= {}
@directory = directory
@lock = Mutex.new
@lock = Monitor.new
@temp_root = options[:temp_dir_root]
@logger = Log4r::Logger.new("vagrant::box_collection")
end
@ -56,53 +56,54 @@ module Vagrant
# * 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
# @param [String] version The version of this box.
# @param [Array<String>] providers The providers that this box can
# be a part of. This will be verified with the `metadata.json` and is
# meant as a basic check. If this isn't given, then whatever provider
# the box represents will be added.
# @param [Boolean] force If true, any existing box with the same name
# and provider will be replaced.
def add(path, name, formats=nil, force=false)
formats = [formats] if formats && !formats.is_a?(Array)
def add(path, name, version, **opts)
providers = opts[:providers]
providers = Array(providers) if providers
provider = nil
with_collection_lock do
# A helper to check if a box exists. We store this in a variable
# since we call it multiple times.
check_box_exists = lambda do |box_formats|
box = find(name, box_formats)
next if !box
# A helper to check if a box exists. We store this in a variable
# since we call it multiple times.
check_box_exists = lambda do |box_formats|
box = find(name, box_formats, version)
next if !box
if !force
@logger.error("Box already exists, can't add: #{name} #{box_formats.join(", ")}")
raise Errors::BoxAlreadyExists, :name => name, :formats => box_formats.join(", ")
end
# We're forcing, so just delete the old box
@logger.info(
"Box already exists, but forcing so removing: #{name} #{box_formats.join(", ")}")
box.destroy!
if !opts[:force]
@logger.error(
"Box already exists, can't add: #{name} v#{version} #{box_formats.join(", ")}")
raise Errors::BoxAlreadyExists,
name: name,
provider: box_formats.join(", "),
version: version
end
log_provider = formats ? formats.join(", ") : "any provider"
# We're forcing, so just delete the old box
@logger.info(
"Box already exists, but forcing so removing: " +
"#{name} v#{version} #{box_formats.join(", ")}")
box.destroy!
end
with_collection_lock do
log_provider = providers ? providers.join(", ") : "any provider"
@logger.debug("Adding box: #{name} (#{log_provider}) from #{path}")
# Verify the box doesn't exist early if we're given a provider. This
# can potentially speed things up considerably since we don't need
# to unpack any files.
check_box_exists.call(formats) if formats
# Verify that a V1 box doesn't exist. If it does, then we signal
# to the user that we need an upgrade.
raise Errors::BoxUpgradeRequired, :name => name if v1_box?(@directory.join(name))
check_box_exists.call(providers) if providers
# Create a temporary directory since we're not sure at this point if
# the box we're unpackaging already exists (if no provider was given)
@ -111,7 +112,10 @@ module Vagrant
@logger.debug("Unpacking box into temporary directory: #{temp_dir}")
result = Util::Subprocess.execute(
"bsdtar", "-v", "-x", "-m", "-C", temp_dir.to_s, "-f", path.to_s)
raise Errors::BoxUnpackageFailure, :output => result.stderr.to_s if result.exit_code != 0
if result.exit_code != 0
raise Errors::BoxUnpackageFailure,
output: result.stderr.to_s
end
# If we get a V1 box, we want to update it in place
if v1_box?(temp_dir)
@ -125,22 +129,14 @@ module Vagrant
with_temp_dir(temp_dir) do |final_temp_dir|
# Get an instance of the box we just added before it is finalized
# in the system so we can inspect and use its metadata.
box = Box.new(name, nil, final_temp_dir)
box = Box.new(name, nil, version, final_temp_dir)
# Get the provider, since we'll need that to at the least add it
# to the system or check that it matches what is given to us.
box_provider = box.metadata["provider"]
if formats
found = false
formats.each do |format|
# Verify that the given provider matches what the box has.
if box_provider.to_sym == format.to_sym
found = true
break
end
end
if providers
found = providers.find { |p| p.to_sym == box_provider.to_sym }
if !found
@logger.error("Added box provider doesnt match expected: #{log_provider}")
raise Errors::BoxProviderDoesntMatch,
@ -155,7 +151,8 @@ module Vagrant
provider = box_provider.to_sym
# Create the directory for this box, not including the provider
box_dir = @directory.join(name)
root_box_dir = @directory.join(dir_name(name))
box_dir = root_box_dir.join(version)
box_dir.mkpath
@logger.debug("Box directory: #{box_dir}")
@ -177,20 +174,25 @@ module Vagrant
@logger.debug("Moving: #{f} => #{destination}")
FileUtils.mv(f, destination)
end
if opts[:metadata_url]
root_box_dir.join("metadata_url").open("w") do |f|
f.write(opts[:metadata_url])
end
end
end
end
end
# Return the box
find(name, provider)
find(name, provider, version)
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.
# @return [Array] Array of `[name, version, provider]` of the boxes
# installed on this system.
def all
results = []
@ -201,27 +203,25 @@ module Vagrant
# us in our folder structure.
next if !child.directory?
box_name = child.basename.to_s
box_name = undir_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
# Otherwise, traverse the subdirectories and see what versions
# 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}")
child.children(true).each do |versiondir|
next if !versiondir.directory?
version = versiondir.basename.to_s
versiondir.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, version, provider_name]
else
@logger.debug("Invalid box, ignoring: #{provider}")
end
end
end
end
@ -234,78 +234,108 @@ module Vagrant
#
# @param [String] name Name of the box (logical name).
# @param [Array] providers Providers that the box implements.
# @param [String] version Version constraints to adhere to. Example:
# "~> 1.0" or "= 1.0, ~> 1.1"
# @return [Box] The box found, or `nil` if not found.
def find(name, providers)
providers = [providers].flatten
def find(name, providers, version)
providers = Array(providers)
# Build up the requirements we have
requirements = version.split(",").map do |v|
Gem::Requirement.new(v.strip)
end
with_collection_lock do
providers.each do |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.info("Box found: #{name} (#{provider})")
return Box.new(name, provider, box_directory.dirname)
box_directory = @directory.join(dir_name(name))
if !box_directory.directory?
@logger.info("Box not found: #{name} (#{providers.join(", ")})")
return nil
end
versions = box_directory.children(true).map do |versiondir|
next if !versiondir.directory?
version = versiondir.basename.to_s
Gem::Version.new(version)
end.compact
# Traverse through versions with the latest version first
versions.sort.reverse.each do |v|
if !requirements.all? { |r| r.satisfied_by?(v) }
# Unsatisfied version requirements
next
end
# If we're looking for a VirtualBox box, then we check if there is
# a V1 box.
if provider.to_sym == :virtualbox
# 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?(@directory.join(name))
@logger.warn("V1 box found: #{name}")
raise Errors::BoxUpgradeRequired, :name => name
end
versiondir = box_directory.join(v.to_s)
providers.each do |provider|
provider_dir = versiondir.join(provider.to_s)
next if !provider_dir.directory?
@logger.info("Box found: #{name} (#{provider})")
metadata_url = nil
metadata_url_file = box_directory.join("metadata_url")
metadata_url = metadata_url_file.read if metadata_url_file.file?
return Box.new(
name, provider, v.to_s, provider_dir,
metadata_url: metadata_url,
)
end
end
end
# Didn't find it, return nil
@logger.info("Box not found: #{name} (#{providers.join(", ")})")
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.
#
# @param [String] name Name of the box (logical name).
# @return [Boolean] `true` otherwise an exception is raised.
def upgrade(name)
# This upgrades a v1.1 - v1.4 box directory structure up to a v1.5
# directory structure. This will raise exceptions if it fails in any
# way.
def upgrade_v1_1_v1_5
with_collection_lock do
@logger.debug("Upgrade request for box: #{name}")
box_dir = @directory.join(name)
temp_dir = Pathname.new(Dir.mktmpdir(TEMP_PREFIX, @temp_root))
# If the box doesn't exist at all, raise an exception
raise Errors::BoxNotFound, :name => name, :provider => "virtualbox" if !box_dir.directory?
@directory.children(true).each do |boxdir|
# Ignore all non-directories because they can't be boxes
next if !boxdir.directory?
if v1_box?(box_dir)
@logger.debug("V1 box #{name} found. Upgrading!")
box_name = boxdir.basename.to_s
# First we actually perform the upgrade
temp_dir = v1_upgrade(box_dir)
# If it is a v1 box, then we need to upgrade it first
if v1_box?(boxdir)
upgrade_dir = v1_upgrade(boxdir)
FileUtils.mv(upgrade_dir, boxdir.join("virtualbox"))
end
# Rename the temporary directory to the provider.
FileUtils.mv(temp_dir.to_s, box_dir.join("virtualbox").to_s)
@logger.info("Box '#{name}' upgraded from V1 to V2.")
# Create the directory for this box
new_box_dir = temp_dir.join(dir_name(box_name), "0")
new_box_dir.mkpath
# Go through each provider and move it
boxdir.children(true).each do |providerdir|
FileUtils.cp_r(providerdir, new_box_dir.join(providerdir.basename))
end
end
end
# We did it! Or the v1 box didn't exist so it doesn't matter.
return true
# Move the folder into place
@directory.rmtree
FileUtils.mv(temp_dir.to_s, @directory.to_s)
end
end
protected
# Returns the directory name for the box of the given name.
#
# @param [String] name
# @return [String]
def dir_name(name)
name.gsub("/", "-VAGRANTSLASH-")
end
# Returns the directory name for the box cleaned up
def undir_name(name)
name.gsub("-VAGRANTSLASH-", "/")
end
# This checks if the given directory represents a V1 box on the
# system.
#
@ -361,17 +391,7 @@ module Vagrant
# This locks the region given by the block with a lock on this
# collection.
def with_collection_lock
lock = @lock
begin
lock.synchronize {}
rescue ThreadError
# If we already hold the lock, just create a new lock so
# we definitely don't block and don't get an error.
lock = Mutex.new
end
lock.synchronize do
@lock.synchronize do
return yield
end
end

129
lib/vagrant/box_metadata.rb Normal file
View File

@ -0,0 +1,129 @@
require "json"
module Vagrant
# BoxMetadata represents metadata about a box, including the name
# it should have, a description of it, the versions it has, and
# more.
class BoxMetadata
# The name that the box should be if it is added.
#
# @return [String]
attr_accessor :name
# The long-form human-readable description of a box.
#
# @return [String]
attr_accessor :description
# Loads the metadata associated with the box from the given
# IO.
#
# @param [IO] io An IO object to read the metadata from.
def initialize(io)
begin
@raw = JSON.load(io)
rescue JSON::ParserError => e
raise Errors::BoxMetadataMalformed,
error: e.to_s
end
@raw ||= {}
@name = @raw["name"]
@description = @raw["description"]
@version_map = (@raw["versions"] || []).map do |v|
[Gem::Version.new(v["version"]), v]
end
@version_map = Hash[@version_map]
# TODO: check for corruption:
# - malformed version
end
# Returns data about a single version that is included in this
# metadata.
#
# @param [String] version The version to return, this can also
# be a constraint.
# @return [Version] The matching version or nil if a matching
# version was not found.
def version(version, **opts)
requirements = version.split(",").map do |v|
Gem::Requirement.new(v.strip)
end
providers = nil
providers = Array(opts[:provider]).map(&:to_sym) if opts[:provider]
@version_map.keys.sort.reverse.each do |v|
next if !requirements.all? { |r| r.satisfied_by?(v) }
version = Version.new(@version_map[v])
next if (providers & version.providers).empty? if providers
return version
end
nil
end
# Returns all the versions supported by this metadata. These
# versions are sorted so the last element of the list is the
# latest version.
#
# @return[Array<String>]
def versions
@version_map.keys.sort.map(&:to_s)
end
# Represents a single version within the metadata.
class Version
# The version that this Version object represents.
#
# @return [String]
attr_accessor :version
def initialize(raw=nil)
return if !raw
@version = raw["version"]
@provider_map = (raw["providers"] || []).map do |p|
[p["name"].to_sym, p]
end
@provider_map = Hash[@provider_map]
end
# Returns a [Provider] for the given name, or nil if it isn't
# supported by this version.
def provider(name)
p = @provider_map[name.to_sym]
return nil if !p
Provider.new(p)
end
# Returns the providers that are available for this version
# of the box.
#
# @return [Array<Symbol>]
def providers
@provider_map.keys.map(&:to_sym)
end
end
# Provider represents a single provider-specific box available
# for a version for a box.
class Provider
# The name of the provider.
#
# @return [String]
attr_accessor :name
# The URL of the box.
#
# @return [String]
attr_accessor :url
def initialize(raw)
@name = raw["name"]
@url = raw["url"]
end
end
end
end

View File

@ -14,6 +14,12 @@ module Vagrant
# defined as basically a folder with a "Vagrantfile." This class allows
# access to the VMs, CLI, etc. all in the scope of this environment.
class Environment
# This is the current version that this version of Vagrant is
# compatible with in the home directory.
#
# @return [String]
CURRENT_SETUP_VERSION = "1.5"
DEFAULT_LOCAL_DATA = ".vagrant"
# The `cwd` that this environment represents
@ -342,14 +348,8 @@ module Vagrant
load_box_and_overrides = lambda do
box = nil
if config.vm.box
begin
box = boxes.find(config.vm.box, box_formats)
rescue Errors::BoxUpgradeRequired
# Upgrade the box if we must
@logger.info("Upgrading box during config load: #{config.vm.box}")
boxes.upgrade(config.vm.box)
retry
end
box = boxes.find(
config.vm.box, box_formats, config.vm.box_version)
end
# If a box was found, then we attempt to load the Vagrantfile for
@ -630,13 +630,37 @@ module Vagrant
raise Errors::HomeDirectoryNotAccessible, home_path: @home_path.to_s
end
# Create the version file to mark the version of the home directory
# we're using.
# Create the version file that we use to track the structure of
# the home directory. If we have an old version, we need to explicitly
# upgrade it. Otherwise, we just mark that its the current version.
version_file = @home_path.join("setup_version")
if version_file.file?
version = version_file.read
if version > CURRENT_SETUP_VERSION
raise Errors::HomeDirectoryLaterVersion
end
case version
when CURRENT_SETUP_VERSION
# We're already good, at the latest version.
when "1.1"
# We need to update our directory structure
upgrade_home_path_v1_1
# Delete the version file so we put our latest version in
version_file.delete
else
raise Errors::HomeDirectoryUnknownVersion,
path: @home_path.to_s,
version: version
end
end
if !version_file.file?
@logger.debug("Setting up the version file.")
@logger.debug(
"Creating home directory version file: #{CURRENT_SETUP_VERSION}")
version_file.open("w") do |f|
f.write("1.1")
f.write(CURRENT_SETUP_VERSION)
end
end
@ -723,6 +747,14 @@ module Vagrant
nil
end
# This upgrades a home directory that was in the v1.1 format to the
# v1.5 format. It will raise exceptions if anything fails.
def upgrade_home_path_v1_1
collection = BoxCollection.new(
@home_path.join("boxes"), temp_dir_root: tmp_path)
collection.upgrade_v1_1_v1_5
end
# This upgrades a Vagrant 1.0.x "dotfile" to the new V2 format.
#
# This is a destructive process. Once the upgrade is complete, the

View File

@ -120,8 +120,32 @@ module Vagrant
error_key(:batch_multi_error)
end
class BoxAddMetadataMultiURL < VagrantError
error_key(:box_add_metadata_multi_url)
end
class BoxAddNameMismatch < VagrantError
error_key(:box_add_name_mismatch)
end
class BoxAddNameRequired < VagrantError
error_key(:box_add_name_required)
end
class BoxAddNoMatchingProvider < VagrantError
error_key(:box_add_no_matching_provider)
end
class BoxAddNoMatchingVersion < VagrantError
error_key(:box_add_no_matching_version)
end
class BoxAddShortNotFound < VagrantError
error_key(:box_add_short_not_found)
end
class BoxAlreadyExists < VagrantError
error_key(:already_exists, "vagrant.actions.box.unpackage")
error_key(:box_add_exists)
end
class BoxChecksumInvalidType < VagrantError
@ -144,20 +168,56 @@ module Vagrant
error_key(:box_metadata_file_not_found)
end
class BoxMetadataMalformed < VagrantError
error_key(:box_metadata_malformed)
end
class BoxNotFound < VagrantError
error_key(:box_not_found)
end
class BoxNotFoundWithProvider < VagrantError
error_key(:box_not_found_with_provider)
end
class BoxOutdatedNoBox < VagrantError
error_key(:box_outdated_no_box)
end
class BoxProviderDoesntMatch < VagrantError
error_key(:box_provider_doesnt_match)
end
class BoxRemoveNotFound < VagrantError
error_key(:box_remove_not_found)
end
class BoxRemoveProviderNotFound < VagrantError
error_key(:box_remove_provider_not_found)
end
class BoxRemoveMultiProvider < VagrantError
error_key(:box_remove_multi_provider)
end
class BoxRemoveMultiVersion < VagrantError
error_key(:box_remove_multi_version)
end
class BoxServerNotSet < VagrantError
error_key(:box_server_not_set)
end
class BoxUnpackageFailure < VagrantError
error_key(:untar_failure, "vagrant.actions.box.unpackage")
end
class BoxUpgradeRequired < VagrantError
error_key(:box_upgrade_required)
class BoxUpdateMultiProvider < VagrantError
error_key(:box_update_multi_provider)
end
class BoxUpdateNoMetadata < VagrantError
error_key(:box_update_no_metadata)
end
class BoxVerificationFailed < VagrantError
@ -260,10 +320,18 @@ module Vagrant
error_key(:environment_locked)
end
class HomeDirectoryLaterVersion < VagrantError
error_key(:home_dir_later_version)
end
class HomeDirectoryNotAccessible < VagrantError
error_key(:home_dir_not_accessible)
end
class HomeDirectoryUnknownVersion < VagrantError
error_key(:home_dir_unknown_version)
end
class ForwardPortAdapterNotFound < VagrantError
error_key(:forward_port_adapter_not_found)
end

View File

@ -1,6 +1,13 @@
require "pathname"
module Vagrant
# This is the default endpoint of the Vagrant Cloud in
# use. API calls will be made to this for various functions
# of Vagrant that may require remote access.
#
# @return [String]
DEFAULT_SERVER_URL = "http://www.vagrantcloud.com"
# This returns whether or not 3rd party plugins should be loaded.
#
# @return [Boolean]
@ -8,6 +15,13 @@ module Vagrant
!ENV["VAGRANT_NO_PLUGINS"]
end
# Returns the URL prefix to the server.
#
# @return [String]
def self.server_url
ENV["VAGRANT_SERVER_URL"] || DEFAULT_SERVER_URL
end
# The source root is the path to the root directory of the Vagrant source.
#
# @return [Pathname]

View File

@ -1,4 +1,5 @@
require "delegate"
require "io/console"
require "thread"
require "log4r"
@ -123,17 +124,28 @@ module Vagrant
# Setup the options so that the new line is suppressed
opts ||= {}
opts[:echo] = true if !opts.has_key?(:echo)
opts[:new_line] = false if !opts.has_key?(:new_line)
opts[:prefix] = false if !opts.has_key?(:prefix)
# Output the data
say(:info, message, opts)
input = nil
if opts[:echo]
input = $stdin.gets
else
input = $stdin.noecho(&:gets)
# Output a newline because without echo, the newline isn't
# echoed either.
say(:info, "\n", opts)
end
# Get the results and chomp off the newline. We do a logical OR
# here because `gets` can return a nil, for example in the case
# that ctrl-D is pressed on the input.
input = $stdin.gets || ""
input.chomp
(input || "").chomp
end
# This is used to output progress reports to the UI.
@ -209,7 +221,10 @@ module Vagrant
class_eval <<-CODE
def #{method}(message, *args, **opts)
super(message)
opts[:bold] = #{method.inspect} != :detail if !opts.has_key?(:bold)
if !opts.has_key?(:bold)
opts[:bold] = #{method.inspect} != :detail && \
#{method.inspect} != :ask
end
@ui.#{method}(format_message(#{method.inspect}, message, **opts), *args, **opts)
end
CODE
@ -241,7 +256,8 @@ module Vagrant
prefix = ""
if !opts.has_key?(:prefix) || opts[:prefix]
prefix = OUTPUT_PREFIX
prefix = " " * OUTPUT_PREFIX.length if type == :detail
prefix = " " * OUTPUT_PREFIX.length if \
type == :detail || type == :ask
end
# Fast-path if there is no prefix
@ -281,16 +297,17 @@ module Vagrant
opts[:color] = :green if type == :success
opts[:color] = :yellow if type == :warn
# If there is no color specified, exit early
return message if !opts.has_key?(:color)
# If it is a detail, it is not bold. Every other message type
# is bolded.
bold = !!opts[:bold]
color = COLORS[opts[:color]]
colorseq = "#{bold ? 1 : 0 }"
if opts[:color]
color = COLORS[opts[:color]]
colorseq += ";#{color}"
end
# Color the message and make sure to reset the color at the end
"\033[#{bold ? 1 : 0};#{color}m#{message}\033[0m"
"\033[#{colorseq}m#{message}\033[0m"
end
end
end

View File

@ -1,6 +1,7 @@
require "log4r"
require "vagrant/util/busy"
require "vagrant/util/platform"
require "vagrant/util/subprocess"
module Vagrant
@ -13,6 +14,9 @@ module Vagrant
# are properly tracked.
USER_AGENT = "Vagrant/#{VERSION}"
attr_reader :source
attr_reader :destination
def initialize(source, destination, options=nil)
@logger = Log4r::Logger.new("vagrant::util::downloader")
@source = source.to_s
@ -34,31 +38,10 @@ module Vagrant
# If this method returns without an exception, the download
# succeeded. An exception will be raised if the download failed.
def download!
# Build the list of parameters to execute with cURL
options = [
"--fail",
"--location",
"--max-redirs", "10",
"--user-agent", USER_AGENT,
"--output", @destination,
]
options += ["--cacert", @ca_cert] if @ca_cert
options += ["--continue-at", "-"] if @continue
options << "--insecure" if @insecure
options << "--cert" << @client_cert if @client_cert
options, subprocess_options = self.options
options += ["--output", @destination]
options << @source
# Specify some options for the subprocess
subprocess_options = {}
# If we're in Vagrant, then we use the packaged CA bundle
if Vagrant.in_installer?
subprocess_options[:env] ||= {}
subprocess_options[:env]["CURL_CA_BUNDLE"] =
File.expand_path("cacert.pem", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"])
end
# This variable can contain the proc that'll be sent to
# the subprocess execute.
data_proc = nil
@ -109,12 +92,48 @@ module Vagrant
output = "Progress: #{columns[0]}% (Rate: #{columns[11]}/s, Estimated time remaining: #{columns[10]})"
@ui.clear_line
@ui.info(output, :new_line => false)
@ui.detail(output, :new_line => false)
end
end
end
# Add the subprocess options onto the options we'll execute with
@logger.info("Downloader starting download: ")
@logger.info(" -- Source: #{@source}")
@logger.info(" -- Destination: #{@destination}")
begin
execute_curl(options, subprocess_options, &data_proc)
ensure
# If we're outputting to the UI, clear the output to
# avoid lingering progress meters.
if @ui
@ui.clear_line
# Windows doesn't clear properly for some reason, so we just
# output one more newline.
@ui.detail("") if Platform.windows?
end
end
# Everything succeeded
true
end
# Does a HEAD request of the URL and returns the output.
def head
options, subprocess_options = self.options
options.unshift("-I")
options << @source
@logger.info("HEAD: #{@source}")
result = execute_curl(options, subprocess_options)
result.stdout
end
protected
def execute_curl(options, subprocess_options, &data_proc)
options = options.dup
options << subprocess_options
# Create the callback that is called if we are interrupted
@ -124,10 +143,6 @@ module Vagrant
interrupted = true
end
@logger.info("Downloader starting download: ")
@logger.info(" -- Source: #{@source}")
@logger.info(" -- Destination: #{@destination}")
# Execute!
result = Busy.busy(int_callback) do
Subprocess.execute("curl", *options, &data_proc)
@ -136,10 +151,6 @@ module Vagrant
# If the download was interrupted, then raise a specific error
raise Errors::DownloaderInterrupted if interrupted
# If we're outputting to the UI, clear the output to
# avoid lingering progress meters.
@ui.clear_line if @ui
# If it didn't exit successfully, we need to parse the data and
# show an error message.
if result.exit_code != 0
@ -149,8 +160,37 @@ module Vagrant
raise Errors::DownloaderError, :message => parts[1].chomp
end
# Everything succeeded
true
result
end
# Returns the varoius cURL and subprocess options.
#
# @return [Array<Array, Hash>]
def options
# Build the list of parameters to execute with cURL
options = [
"--fail",
"--location",
"--max-redirs", "10",
"--user-agent", USER_AGENT,
]
options += ["--cacert", @ca_cert] if @ca_cert
options += ["--continue-at", "-"] if @continue
options << "--insecure" if @insecure
options << "--cert" << @client_cert if @client_cert
# Specify some options for the subprocess
subprocess_options = {}
# If we're in Vagrant, then we use the packaged CA bundle
if Vagrant.in_installer?
subprocess_options[:env] ||= {}
subprocess_options[:env]["CURL_CA_BUNDLE"] =
File.expand_path("cacert.pem", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"])
end
return [options, subprocess_options]
end
end
end

View File

@ -8,18 +8,10 @@ module VagrantPlugins
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant box add <name> <url> [--provider provider] [-h]"
o.banner = "Usage: vagrant box add <url> [-h]"
o.separator ""
o.on("--checksum VALUE", String, "Checksum") do |c|
options[:checksum] = c
end
o.on("--checksum-type VALUE", String, "Checksum type") do |c|
options[:checksum_type] = c.to_sym
end
o.on("-c", "--clean", "Remove old temporary download if it exists.") do |c|
o.on("-c", "--clean", "Clean any temporary download files") do |c|
options[:clean] = c
end
@ -27,45 +19,72 @@ module VagrantPlugins
options[:force] = f
end
o.on("--insecure", "If set, SSL certs will not be validated.") do |i|
o.on("--insecure", "Do not validate SSL certificates") do |i|
options[:insecure] = i
end
o.on("--cacert certfile", String, "CA certificate") do |c|
o.on("--cacert certfile", String, "CA certificate for SSL download") do |c|
options[:ca_cert] = c
end
o.on("--cert certfile", String,
"The client SSL cert") do |c|
"A client SSL cert, if needed") do |c|
options[:client_cert] = c
end
o.on("--provider provider", String,
"The provider that backs the box.") do |p|
o.on("--provider VALUE", String, "Provider the box should satisfy") do |p|
options[:provider] = p
end
o.on("--box-version VALUE", String, "Constrain version of the added box") do |v|
options[:version] = v
end
o.separator ""
o.separator "The options below only apply if you're adding a box file directly,"
o.separator "and not using a Vagrant server or a box structured like 'user/box':"
o.separator ""
o.on("--checksum VALUE", String, "Checksum for the box") do |c|
options[:checksum] = c
end
o.on("--checksum-type VALUE", String, "Checksum type (md5, sha1, sha256)") do |c|
options[:checksum_type] = c.to_sym
end
o.on("--name VALUE", String, "Name of the box") do |n|
options[:name] = n
end
end
# Parse the options
argv = parse_options(opts)
return if !argv
raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length < 2
if argv.empty? || argv.length > 2
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
# Get the provider if one was set
provider = nil
provider = options[:provider].to_sym if options[:provider]
url = argv[0]
if argv.length == 2
options[:name] = argv[0]
url = argv[1]
end
@env.action_runner.run(Vagrant::Action.action_box_add, {
:box_name => argv[0],
:box_provider => provider,
:box_url => argv[1],
:box_checksum_type => options[:checksum_type],
:box_checksum => options[:checksum],
:box_clean => options[:clean],
:box_force => options[:force],
:box_download_ca_cert => options[:ca_cert],
:box_download_client_cert => options[:client_cert],
:box_download_insecure => options[:insecure],
box_url: url,
box_name: options[:name],
box_provider: options[:provider],
box_version: options[:version],
box_checksum_type: options[:checksum_type],
box_checksum: options[:checksum],
box_clean: options[:clean],
box_force: options[:force],
box_download_ca_cert: options[:ca_cert],
box_download_client_cert: options[:client_cert],
box_download_insecure: options[:insecure],
ui: Vagrant::UI::Prefixed.new(@env.ui, "box"),
})
# Success, exit status 0

View File

@ -42,13 +42,15 @@ module VagrantPlugins
# ignore the "v1" param for now since I'm not yet sure if its
# important for the user to know what boxes need to be upgraded
# and which don't, since we plan on doing that transparently.
boxes.each do |name, provider, _v1|
@env.ui.info("#{name.ljust(longest_box_length)} (#{provider})", :prefix => false)
boxes.each do |name, version, provider|
@env.ui.info("#{name.ljust(longest_box_length)} (#{provider})")
@env.ui.machine("box-name", name)
@env.ui.machine("box-provider", provider)
@env.ui.machine("box-version", version)
info_file = @env.boxes.find(name, provider).directory.join("info.json")
info_file = @env.boxes.find(name, provider, version).
directory.join("info.json")
if info_file.file?
info = JSON.parse(info_file.read)
info.each do |k, v|

View File

@ -0,0 +1,89 @@
require 'optparse'
module VagrantPlugins
module CommandBox
module Command
class Outdated < Vagrant.plugin("2", :command)
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant box outdated [options]"
o.separator ""
o.separator "Checks if there is a new version available for the box"
o.separator "that are you are using. If you pass in the --global flag,"
o.separator "all boxes will be checked for updates."
o.separator ""
o.separator "Options:"
o.separator ""
o.on("--global", "Check all boxes installed.") do |g|
options[:global] = g
end
end
argv = parse_options(opts)
return if !argv
# If we're checking the boxes globally, then do that.
if options[:global]
outdated_global
return 0
end
with_target_vms(argv) do |machine|
@env.action_runner.run(Vagrant::Action.action_box_outdated, {
box_outdated_force: true,
box_outdated_refresh: true,
box_outdated_success_ui: true,
machine: machine,
})
end
end
def outdated_global
boxes = {}
@env.boxes.all.reverse.each do |name, version, provider|
next if boxes[name]
boxes[name] = @env.boxes.find(name, provider, version)
end
boxes.values.each do |box|
if !box.metadata_url
@env.ui.output(I18n.t(
"vagrant.box_outdated_no_metadata",
name: box.name))
next
end
md = nil
begin
md = box.load_metadata
rescue Vagrant::Errors::DownloaderError => e
@env.ui.error(I18n.t(
"vagrant.box_outdated_metadata_error",
name: box.name,
message: e.extra_data[:message]))
next
end
current = Gem::Version.new(box.version)
latest = Gem::Version.new(md.versions.last)
if latest <= current
@env.ui.success(I18n.t(
"vagrant.box_up_to_date",
name: box.name,
version: box.version))
else
@env.ui.warn(I18n.t(
"vagrant.box_outdated",
name: box.name,
current: box.version,
latest: latest.to_s,))
end
end
end
end
end
end
end

View File

@ -5,38 +5,42 @@ module VagrantPlugins
module Command
class Remove < Vagrant.plugin("2", :command)
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant box remove <name> <provider>"
o.banner = "Usage: vagrant box remove <name>"
o.separator ""
o.on("--provider VALUE", String,
"The specific provider type for the box to remove.") do |p|
options[:provider] = p
end
o.on("--box-version VALUE", String,
"The specific version of the box to remove.") do |v|
options[:version] = v
end
end
# Parse the options
argv = parse_options(opts)
return if !argv
raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length < 1
if argv.empty? || argv.length > 2
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
if !argv[1]
# Try to automatically determine the provider.
providers = []
@env.boxes.all.each do |name, provider|
if name == argv[0]
providers << provider
end
end
if providers.length > 1
@env.ui.error(
I18n.t("vagrant.commands.box.remove_must_specify_provider",
name: argv[0],
providers: providers.join(", ")))
return 1
end
argv[1] = providers[0] || ""
if argv.length == 2
# @deprecated
@env.ui.warn("WARNING: The second argument to `vagrant box remove`")
@env.ui.warn("is deprecated. Please use the --provider flag. This")
@env.ui.warn("feature will stop working in the next version.")
options[:provider] = argv[1]
end
@env.action_runner.run(Vagrant::Action.action_box_remove, {
:box_name => argv[0],
:box_provider => argv[1]
:box_provider => options[:provider],
:box_version => options[:version],
})
# Success, exit status 0

View File

@ -14,7 +14,7 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length < 2
raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length != 2
box_name = argv[0]
box_provider = argv[1].to_sym

View File

@ -24,6 +24,11 @@ module VagrantPlugins
List
end
@subcommands.register(:outdated) do
require_relative "outdated"
Outdated
end
@subcommands.register(:remove) do
require File.expand_path("../remove", __FILE__)
Remove
@ -33,6 +38,11 @@ module VagrantPlugins
require File.expand_path("../repackage", __FILE__)
Repackage
end
@subcommands.register(:update) do
require_relative "update"
Update
end
end
def execute

View File

@ -0,0 +1,125 @@
require 'optparse'
module VagrantPlugins
module CommandBox
module Command
class Update < Vagrant.plugin("2", :command)
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant box update [options]"
o.separator ""
o.separator "Updates the box that is in use in the current Vagrant environment,"
o.separator "if there any updates available. This does not destroy/recreate the"
o.separator "machine, so you'll have to do that to see changes."
o.separator ""
o.separator "To update a specific box (not tied to a Vagrant environment), use the"
o.separator "--box flag."
o.separator ""
o.separator "Options:"
o.separator ""
o.on("--box VALUE", String, "Update a specific box") do |b|
options[:box] = b
end
o.on("--provider VALUE", String, "Update box with specific provider.") do |p|
options[:provider] = p.to_sym
end
end
argv = parse_options(opts)
return if !argv
if options[:box]
update_specific(options[:box], options[:provider])
else
update_vms(argv)
end
0
end
def update_specific(name, provider)
boxes = {}
@env.boxes.all.each do |n, v, p|
boxes[n] ||= {}
boxes[n][p] ||= []
boxes[n][p] << v
end
if !boxes[name]
raise Vagrant::Errors::BoxNotFound, name: name.to_s
end
if !provider
if boxes[name].length > 1
raise Vagrant::Errors::BoxUpdateMultiProvider,
name: name.to_s,
providers: boxes[name].keys.map(&:to_s).sort.join(", ")
end
provider = boxes[name].keys.first
elsif !boxes[name][provider]
raise Vagrant::Errors::BoxNotFoundWithProvider,
name: name.to_s,
provider: provider.to_s,
providers: boxes[name].keys.map(&:to_s).sort.join(", ")
end
to_update = [
[name, provider, boxes[name][provider].last],
]
to_update.each do |n, p, v|
box = @env.boxes.find(n, p, v)
box_update(box, "> #{v}", @env.ui)
end
end
def update_vms(argv)
with_target_vms(argv) do |machine|
if !machine.box
machine.ui.output(I18n.t(
"vagrant.errors.box_update_no_box",
name: machine.config.vm.box))
next
end
box = machine.box
version = machine.config.vm.box_version
box_update(box, version, machine.ui)
end
end
def box_update(box, version, ui)
ui.output(I18n.t("vagrant.box_update_checking", name: box.name))
ui.detail("Version constraints: #{version}")
ui.detail("Provider: #{box.provider}")
update = box.has_update?(version)
if !update
ui.success(I18n.t(
"vagrant.box_up_to_date_single",
name: box.name, version: box.version))
return
end
ui.output(I18n.t(
"vagrant.box_updating",
name: update[0].name,
provider: update[2].name,
old: box.version,
new: update[1].version))
@env.action_runner.run(Vagrant::Action.action_box_add, {
box_url: box.metadata_url,
box_provider: update[2].name,
box_version: update[1].version,
ui: ui,
})
end
end
end
end
end

View File

@ -17,7 +17,9 @@ module VagrantPlugins
attr_accessor :base_mac
attr_accessor :boot_timeout
attr_accessor :box
attr_accessor :box_check_update
attr_accessor :box_url
attr_accessor :box_version
attr_accessor :box_download_ca_cert
attr_accessor :box_download_checksum
attr_accessor :box_download_checksum_type
@ -32,12 +34,14 @@ module VagrantPlugins
def initialize
@base_mac = UNSET_VALUE
@boot_timeout = UNSET_VALUE
@box_check_update = UNSET_VALUE
@box_download_ca_cert = UNSET_VALUE
@box_download_checksum = UNSET_VALUE
@box_download_checksum_type = UNSET_VALUE
@box_download_client_cert = UNSET_VALUE
@box_download_insecure = UNSET_VALUE
@box_url = UNSET_VALUE
@box_version = UNSET_VALUE
@graceful_halt_timeout = UNSET_VALUE
@guest = UNSET_VALUE
@hostname = UNSET_VALUE
@ -303,12 +307,14 @@ module VagrantPlugins
# Defaults
@base_mac = nil if @base_mac == UNSET_VALUE
@boot_timeout = 300 if @boot_timeout == UNSET_VALUE
@box_check_update = true if @box_check_update == UNSET_VALUE
@box_download_ca_cert = nil if @box_download_ca_cert == UNSET_VALUE
@box_download_checksum = nil if @box_download_checksum == UNSET_VALUE
@box_download_checksum_type = nil if @box_download_checksum_type == UNSET_VALUE
@box_download_client_cert = nil if @box_download_client_cert == UNSET_VALUE
@box_download_insecure = false if @box_download_insecure == UNSET_VALUE
@box_url = nil if @box_url == UNSET_VALUE
@box_version = ">= 0" if @box_version == UNSET_VALUE
@graceful_halt_timeout = 60 if @graceful_halt_timeout == UNSET_VALUE
@guest = nil if @guest == UNSET_VALUE
@hostname = nil if @hostname == UNSET_VALUE
@ -326,9 +332,7 @@ module VagrantPlugins
end
# Make sure the box URL is an array if it is set
if @box_url && !@box_url.is_a?(Array)
@box_url = [@box_url]
end
@box_url = Array(@box_url) if @box_url
# Set the guest properly
@guest = @guest.to_sym if @guest
@ -449,11 +453,20 @@ module VagrantPlugins
def validate(machine)
errors = _detected_errors
errors << I18n.t("vagrant.config.vm.box_missing") if !box
errors << I18n.t("vagrant.config.vm.box_not_found", :name => box) if \
box && !box_url && !machine.box
errors << I18n.t("vagrant.config.vm.hostname_invalid_characters") if \
@hostname && @hostname !~ /^[a-z0-9][-.a-z0-9]+$/i
if @box_version
@box_version.split(",").each do |v|
begin
Gem::Requirement.new(v.strip)
rescue Gem::Requirement::BadRequirementError
errors << I18n.t(
"vagrant.config.vm.bad_version", version: v)
end
end
end
if box_download_ca_cert
path = Pathname.new(box_download_ca_cert).
expand_path(machine.env.root_path)

View File

@ -246,6 +246,7 @@ module VagrantPlugins
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use ConfigValidate
b.use BoxCheckOutdated
b.use Call, IsRunning do |env, b2|
# If the VM is running, then our work here is done, exit
if env[:result]
@ -302,7 +303,7 @@ module VagrantPlugins
# works fine.
b.use Call, Created do |env, b2|
if !env[:result]
b2.use HandleBoxUrl
b2.use HandleBox
end
end

View File

@ -4,6 +4,59 @@ en:
Machine booted and ready!
boot_waiting: |-
Waiting for machine to boot. This may take a few minutes...
box_auto_adding: |-
Box '%{name}' could not be found. Attempting to find and install...
box_add_choose_provider: |-
This box can work with multiple providers! The providers that it
can work with are listed below. Please review the list and choose
the provider you will be working with.
%{options}
Enter your choice:
box_add_choose_provider_again: |-
Invalid choice. Try again:
box_add_with_version: |-
Adding box '%{name}' (v%{version}) for provider: %{providers}
box_added: |-
Successfully added box '%{name}' (v%{version}) for '%{provider}'!
box_downloading: |-
Downloading: %{url}
box_download_error: |-
Error downloading: %{message}
box_expanding_url: |-
URL: %{url}
box_loading_metadata: |-
Loading metadata for box '%{name}'
box_outdated: |-
* '%{name}' is outdated! Current: %{current}. Latest: %{latest}
box_outdated_checking_with_refresh: |-
Checking if box '%{name}' is up to date...
box_outdated_local: |-
A newer version of the box '%{name}' is available and already
installed, but your Vagrant machine is running against
version '%{old}'. To update to version '%{new}',
destroy and recreate your machine.
box_outdated_metadata_error_single: |-
Error loading box metadata while attempting to check for
updates: %{message}
box_outdated_single: |-
A newer version of the box '%{name}' is available! You currently
have version '%{current}'. The latest is version '%{latest}'. Run
`vagrant box update` to update.
box_outdated_metadata_error: |-
* '%{name}': Error loading metadata: %{message}
box_outdated_no_metadata: |-
* '%{name}' wasn't added from a catalog, no version information
box_updating: |-
Updating '%{name}' with provider '%{provider}' from version
'%{old}' to '%{new}'...
box_update_checking: |-
Checking for updates to '%{name}'
box_up_to_date: |-
* '%{name}' (v%{version}) is up to date
box_up_to_date_single: |-
Box '%{name}' (v%{version}) is running the latest version.
cfengine_bootstrapping: |-
Bootstrapping CFEngine with policy server: %{policy_server}...
cfengine_bootstrapping_policy_hub: |-
@ -211,6 +264,32 @@ en:
Any errors that occurred are shown below.
%{message}
box_add_no_matching_provider: |-
The box you're attempting to add doesn't support the provider
you requested. Please find an alternate box or use an alternate
provider. Double-check your requested provider to verify you didn't
simply misspell it.
Name: %{name}
Address: %{url}
Requested provider: %{requested}
box_add_no_matching_version: |-
The box you're attempting to add has no available version that
matches the constraints you requested. Please double-check your
settings.
Box: %{name}
Address: %{url}
Constraints: %{constraints}
Available versions: %{versions}
box_add_short_not_found: |-
The box '%{name}' could not be found or
could not be accessed in the remote catalog. Please
double-check the name. The expanded URL and error message
are shown below.
URL: %{url}
Error: %{error}
boot_bad_state: |-
The guest machine entered an invalid state while waiting for it
to boot. Valid states are '%{valid}'. The machine is in the
@ -236,6 +315,33 @@ en:
If the box appears to be booting properly, you may want to increase
the timeout ("config.vm.boot_timeout") value.
box_add_exists: |-
The box you're attempting to add already exists. Remove it before
adding it again or add it with the `--force` flag.
Name: %{name}
Provider: %{provider}
Version: %{version}
box_add_metadata_multi_url: |-
Multiple URLs for a box can't be specified when adding
versioned boxes. Please specify a single URL to the box
metadata (JSON) information. The full list of URLs you
specified is shown below:
%{urls}
box_add_name_mismatch: |-
The box you're adding has a name different from the name you
requested. For boxes with metadata, you cannot override the name.
If you're adding a box using `vagrant box add`, don't specify
the `--name` parameter. If the box is being added via a Vagrantfile,
change the `config.vm.box` value to match the name below.
Requested name: %{requested_name}
Actual name: %{actual_name}
box_add_name_required: |-
A name is required when adding a box file directly. Please pass
the `--name` parameter to `vagrant box add`. See
`vagrant box add -h` for more help.
box_checksum_invalid_type: |-
The specified checksum type is not supported by Vagrant: %{type}.
Vagrant supports the following checksum types:
@ -266,16 +372,75 @@ en:
box file format can be found at the URL below:
http://docs.vagrantup.com/v2/boxes/format.html
box_not_found: Box '%{name}' with '%{provider}' provider could not be found.
box_metadata_malformed: |-
The metadata for the box was malformed. The exact error
is shown below. Please contact the maintainer of the box so
that this issue can be fixed.
%{error}
box_not_found: |-
The box '%{name}' does not exist. Please double check and
try again. You can see the boxes that are installed with
`vagrant box list`.
box_not_found_with_provider: |-
The box '%{name}' isn't installed for the provider '%{provider}'.
Please double-check and try again. The installed providers for
the box are shown below:
%{providers}
box_outdated_no_box: |-
The box '%{name}' isn't downloaded or added yet, so we can't
check if it is outdated. Run a `vagrant up` or add the box
with `vagrant box add` to download an appropriate version.
box_provider_doesnt_match: |-
The box you attempted to add doesn't match the provider you specified.
Provider expected: %{expected}
Provider of box: %{actual}
box_upgrade_required: |-
The box '%{name}' is still stored on disk in the Vagrant 1.0.x
format. This box must be upgraded in order to work properly with
this version of Vagrant.
box_remove_multi_provider: |-
You requested to remove the box '%{name}'. This box has
multiple providers. You must explicitly select a single
provider to remove with `--provider`.
Available providers: %{providers}
box_remove_multi_version: |-
You requested to remove the box '%{name}' with provider
'%{provider}'. This box has multiple versions. You must
explicitly specify which version you want to remove with
the `--box-version` flag.
Versions: %{versions}
box_remove_not_found: |-
The box you requested to be removed could not be found. No
boxes named '%{name}' could be found.
box_remove_provider_not_found: |-
You requested to remove the box '%{name}' with provider
'%{provider}'. The box '%{name}' exists but not with
the provider specified. Please double-check and try again.
The providers for this are: %{providers}
box_server_not_set: |-
A URL to a Vagrant Cloud server is not set, so boxes cannot
be added with a shorthand ("mitchellh/precise64") format.
You may also be seeing this error if you meant to type in
a path to a box file which doesn't exist locally on your
system.
To set a URL to a Vagrant Cloud server, set the
`VAGRANT_SERVER_URL` environmental variable. Or, if you
meant to use a file path, make sure the path to the file
is valid.
box_update_multi_provider: |-
You requested to update the box '%{name}'. This box has
multiple providers. You must explicitly select a single
provider to remove with `--provider`.
Available providers: %{providers}
box_update_no_metadata: |-
The box '%{name}' is not a versioned box. The box was added
directly instead of from a box catalog. Vagrant can only
check the versions of boxes that were added from a catalog
such as from the public Vagrant Server.
bundler_disabled: |-
Vagrant's built-in bundler management mechanism is disabled because
Vagrant is running in an external bundler environment. In these
@ -432,11 +597,20 @@ en:
as mounting shared folders and configuring networks. Please add
the ability to detect this guest operating system to Vagrant
by creating a plugin or reporting a bug.
home_dir_later_version: |-
It appears that a newer version of Vagrant was run on this machine
at some point. The current version of Vagrant is unable to read
the configuration structure of this newer version. Please upgrade to
the latest version of Vagrant.
home_dir_not_accessible: |-
The home directory you specified is not accessible. The home
directory that Vagrant uses must be both readable and writable.
You specified: %{home_path}
home_dir_unknown_version: |-
The Vagrant app data directory (%{path}) is in a
structure Vagrant doesn't understand. This is a rare exception.
Please report an issue or ask the mailing list for help.
host_explicit_not_detected: |-
The host implementation explicitly specified in your Vagrantfile
("%{value}") could not be found. Please verify that the plugin is
@ -859,6 +1033,8 @@ en:
ssh:
private_key_missing: "`private_key_path` file must exist: %{path}"
vm:
bad_version: |-
Invalid box version constraints: %{version}
box_download_ca_cert_not_found: |-
"box_download_ca_cert" file not found: %{path}
box_download_checksum_blank: |-
@ -866,7 +1042,6 @@ en:
box_download_checksum_notblank: |-
Checksum specified but must also specify "box_download_checksum_type"
box_missing: "A box must be specified."
box_not_found: "The box '%{name}' could not be found."
hostname_invalid_characters: |-
The hostname set for the VM should only contain letters, numbers,
hyphens or dots. It cannot start with a hyphen or dot.
@ -916,17 +1091,6 @@ en:
vm_not_created: "VM not created. Moving on..."
vm_not_running: "VM is not currently running. Please, first bring it up with `vagrant up` then run this command."
box:
remove_must_specify_provider: |-
Multiple providers were found for the box '%{name}'. Please specify
the specific provider for the box you want to remove. The list of
providers backing this box is:
'%{providers}'
To remove the box for a specific provider, run the following command,
filling in PROVIDER with one of the providers above:
vagrant box remove '%{name}' PROVIDER
no_installed_boxes: "There are no installed boxes! Use `vagrant box add` to add some."
removing: |-
Removing box '%{name}' with provider '%{provider}'...
@ -1217,23 +1381,15 @@ en:
output from attempting to unpackage (if any):
%{output}
already_exists: |-
The box you're attempting to add already exists:
Name: %{name}
Provider: %{formats}
add:
adding: |-
Extracting box...
added: |-
Successfully added box '%{name}' with provider '%{provider}'!
checksumming: |-
Calculating and comparing box checksum...
destroy:
destroying: "Deleting box '%{name}'..."
download:
cleaning: "Cleaning up downloaded box..."
downloading: "Downloading box from URL: %{url}"
download_failed: |-
Download failed. Will try another box URL if there is one.
interrupted: "Box download was interrupted. Exiting."

View File

@ -0,0 +1,67 @@
require File.expand_path("../../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/box/command/add")
describe VagrantPlugins::CommandBox::Command::Add do
include_context "unit"
let(:argv) { [] }
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
env = isolated_environment
env.vagrantfile("")
env.create_vagrant_env
end
subject { described_class.new(argv, iso_env) }
let(:action_runner) { double("action_runner") }
before do
iso_env.stub(action_runner: action_runner)
end
context "with no arguments" do
it "shows help" do
expect { subject.execute }.
to raise_error(Vagrant::Errors::CLIInvalidUsage)
end
end
context "with one argument" do
let(:argv) { ["foo"] }
it "executes the runner with the proper actions" do
action_runner.should_receive(:run).with do |action, **opts|
expect(opts[:box_name]).to be_nil
expect(opts[:box_url]).to eq("foo")
true
end
subject.execute
end
end
context "with two arguments" do
let(:argv) { ["foo", "bar"] }
it "executes the runner with the proper actions" do
action_runner.should_receive(:run).with do |action, **opts|
expect(opts[:box_name]).to eq("foo")
expect(opts[:box_url]).to eq("bar")
true
end
subject.execute
end
end
context "with more than two arguments" do
let(:argv) { ["one", "two", "three"] }
it "shows help" do
expect { subject.execute }.
to raise_error(Vagrant::Errors::CLIInvalidUsage)
end
end
end

View File

@ -0,0 +1,66 @@
require File.expand_path("../../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/box/command/remove")
describe VagrantPlugins::CommandBox::Command::Remove do
include_context "unit"
let(:argv) { [] }
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
env = isolated_environment
env.vagrantfile("")
env.create_vagrant_env
end
subject { described_class.new(argv, iso_env) }
let(:action_runner) { double("action_runner") }
before do
iso_env.stub(action_runner: action_runner)
end
context "with no arguments" do
it "shows help" do
expect { subject.execute }.
to raise_error(Vagrant::Errors::CLIInvalidUsage)
end
end
context "with one argument" do
let(:argv) { ["foo"] }
it "invokes the action runner" do
action_runner.should_receive(:run).with do |action, opts|
expect(opts[:box_name]).to eq("foo")
true
end
subject.execute
end
end
context "with two arguments" do
let(:argv) { ["foo", "bar"] }
it "uses the 2nd arg as a provider" do
action_runner.should_receive(:run).with do |action, opts|
expect(opts[:box_name]).to eq("foo")
expect(opts[:box_provider]).to eq("bar")
true
end
subject.execute
end
end
context "with more than two arguments" do
let(:argv) { ["one", "two", "three"] }
it "shows help" do
expect { subject.execute }.
to raise_error(Vagrant::Errors::CLIInvalidUsage)
end
end
end

View File

@ -0,0 +1,54 @@
require File.expand_path("../../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/box/command/repackage")
describe VagrantPlugins::CommandBox::Command::Repackage do
include_context "unit"
let(:argv) { [] }
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
env = isolated_environment
env.vagrantfile("")
env.create_vagrant_env
end
subject { described_class.new(argv, iso_env) }
let(:action_runner) { double("action_runner") }
before do
iso_env.stub(action_runner: action_runner)
end
context "with no arguments" do
it "shows help" do
expect { subject.execute }.
to raise_error(Vagrant::Errors::CLIInvalidUsage)
end
end
context "with one argument" do
let(:argv) { ["one"] }
it "shows help" do
expect { subject.execute }.
to raise_error(Vagrant::Errors::CLIInvalidUsage)
end
end
context "with two arguments" do
it "repackages the box with the given provider" do
pending
end
end
context "with more than two arguments" do
let(:argv) { ["one", "two", "three"] }
it "shows help" do
expect { subject.execute }.
to raise_error(Vagrant::Errors::CLIInvalidUsage)
end
end
end

View File

@ -0,0 +1,217 @@
require "pathname"
require "tmpdir"
require File.expand_path("../../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/box/command/update")
describe VagrantPlugins::CommandBox::Command::Update do
include_context "unit"
let(:argv) { [] }
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
test_iso_env.vagrantfile("")
test_iso_env.create_vagrant_env
end
let(:test_iso_env) { isolated_environment }
let(:action_runner) { double("action_runner") }
let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
subject { described_class.new(argv, iso_env) }
before do
iso_env.stub(action_runner: action_runner)
end
describe "execute" do
context "updating specific box" do
let(:argv) { ["--box", "foo"] }
let(:metadata_url) { Pathname.new(Dir.mktmpdir).join("metadata.json") }
before do
metadata_url.open("w") do |f|
f.write("")
end
test_iso_env.box3(
"foo", "1.0", :virtualbox, metadata_url: metadata_url.to_s)
end
it "doesn't update if they're up to date" do
action_runner.should_receive(:run).never
subject.execute
end
it "does update if there is an update" do
metadata_url.open("w") do |f|
f.write(<<-RAW)
{
"name": "foo",
"versions": [
{
"version": "1.0"
},
{
"version": "1.1",
"providers": [
{
"name": "virtualbox",
"url": "bar"
}
]
}
]
}
RAW
end
action_runner.should_receive(:run).with do |action, opts|
expect(opts[:box_url]).to eq(metadata_url.to_s)
expect(opts[:box_provider]).to eq("virtualbox")
expect(opts[:box_version]).to eq("1.1")
true
end
subject.execute
end
it "raises an error if there are multiple providers" do
test_iso_env.box3("foo", "1.0", :vmware)
action_runner.should_receive(:run).never
expect { subject.execute }.
to raise_error(Vagrant::Errors::BoxUpdateMultiProvider)
end
context "with multiple providers and specifying the provider" do
let(:argv) { ["--box", "foo", "--provider", "vmware"] }
it "updates the proper box" do
metadata_url.open("w") do |f|
f.write(<<-RAW)
{
"name": "foo",
"versions": [
{
"version": "1.0"
},
{
"version": "1.1",
"providers": [
{
"name": "vmware",
"url": "bar"
}
]
}
]
}
RAW
end
test_iso_env.box3("foo", "1.0", :vmware)
action_runner.should_receive(:run).with do |action, opts|
expect(opts[:box_url]).to eq(metadata_url.to_s)
expect(opts[:box_provider]).to eq("vmware")
expect(opts[:box_version]).to eq("1.1")
true
end
subject.execute
end
it "raises an error if that provider doesn't exist" do
action_runner.should_receive(:run).never
expect { subject.execute }.
to raise_error(Vagrant::Errors::BoxNotFoundWithProvider)
end
end
context "with a box that doesn't exist" do
let(:argv) { ["--box", "nope"] }
it "raises an exception" do
action_runner.should_receive(:run).never
expect { subject.execute }.
to raise_error(Vagrant::Errors::BoxNotFound)
end
end
end
context "updating environment machines" do
before do
subject.stub(:with_target_vms) { |&block| block.call machine }
end
let(:box) do
box_dir = test_iso_env.box3("foo", "1.0", :virtualbox)
box = Vagrant::Box.new(
"foo", :virtualbox, "1.0", box_dir, metadata_url: "foo")
box.stub(has_update?: nil)
box
end
it "ignores machines without boxes" do
action_runner.should_receive(:run).never
subject.execute
end
it "doesn't update boxes if they're up-to-date" do
machine.stub(box: box)
box.should_receive(:has_update?).
with(machine.config.vm.box_version).
and_return(nil)
action_runner.should_receive(:run).never
subject.execute
end
it "updates boxes if they have an update" do
md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))
{
"name": "foo",
"versions": [
{
"version": "1.0"
},
{
"version": "1.1",
"providers": [
{
"name": "virtualbox",
"url": "bar"
}
]
}
]
}
RAW
machine.stub(box: box)
box.should_receive(:has_update?).
with(machine.config.vm.box_version).
and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")])
action_runner.should_receive(:run).with do |action, opts|
expect(opts[:box_url]).to eq(box.metadata_url)
expect(opts[:box_provider]).to eq("virtualbox")
expect(opts[:box_version]).to eq("1.1")
expect(opts[:ui]).to equal(machine.ui)
true
end
subject.execute
end
end
end
end

View File

@ -5,6 +5,29 @@ require Vagrant.source_root.join("plugins/kernel_v2/config/vm")
describe VagrantPlugins::Kernel_V2::VMConfig do
subject { described_class.new }
let(:machine) { double("machine") }
def assert_valid
errors = subject.validate(machine)
if !errors.values.all? { |v| v.empty? }
raise "Errors: #{errors.inspect}"
end
end
before do
env = double("env")
env.stub(root_path: nil)
machine.stub(env: env)
machine.stub(provider_config: nil)
subject.box = "foo"
end
it "is valid with test defaults" do
subject.finalize!
assert_valid
end
describe "#base_mac" do
it "defaults properly" do
subject.finalize!
@ -12,11 +35,58 @@ describe VagrantPlugins::Kernel_V2::VMConfig do
end
end
describe "#box_url" do
it "defaults properly" do
context "#box_check_update" do
it "defaults to true" do
subject.finalize!
expect(subject.box_check_update).to be_true
end
end
describe "#box_url" do
it "defaults to nil" do
subject.finalize!
expect(subject.box_url).to be_nil
end
it "turns into an array" do
subject.box_url = "foo"
subject.finalize!
expect(subject.box_url).to eq(
["foo"])
end
it "keeps in array" do
subject.box_url = ["foo", "bar"]
subject.finalize!
expect(subject.box_url).to eq(
["foo", "bar"])
end
end
context "#box_version" do
it "defaults to >= 0" do
subject.finalize!
expect(subject.box_version).to eq(">= 0")
end
it "errors if invalid version" do
subject.box_version = "nope"
subject.finalize!
expect { assert_valid }.to raise_error(RuntimeError)
end
it "can have complex constraints" do
subject.box_version = ">= 0, ~> 1.0"
subject.finalize!
assert_valid
end
end
describe "#network(s)" do

View File

@ -89,6 +89,44 @@ module Unit
box_dir
end
# Creates a fake box to exist in this environment according
# to the "gen-3" box format.
#
# @param [String] name
# @param [String] version
# @param [String] provider
# @return [Pathname]
def box3(name, version, provider, **opts)
# Create the directory for the box
box_dir = boxes_dir.join(name, version, provider.to_s)
box_dir.mkpath
# Create the metadata.json for it
box_metadata_file = box_dir.join("metadata.json")
box_metadata_file.open("w") do |f|
f.write(JSON.generate({
:provider => provider.to_s
}))
end
# Create a Vagrantfile
if opts[:vagrantfile]
box_vagrantfile = box_dir.join("Vagrantfile")
box_vagrantfile.open("w") do |f|
f.write(opts[:vagrantfile])
end
end
# Create the metadata URL
if opts[:metadata_url]
boxes_dir.join(name, "metadata_url").open("w") do |f|
f.write(opts[:metadata_url])
end
end
box_dir
end
# This creates a "box" file that is a valid V1 box.
#
# @return [Pathname] Path to the newly created box.

View File

@ -0,0 +1,826 @@
require "digest/sha1"
require "pathname"
require "tempfile"
require "tmpdir"
require "webrick"
require File.expand_path("../../../../base", __FILE__)
require "vagrant/util/file_checksum"
describe Vagrant::Action::Builtin::BoxAdd do
include_context "unit"
let(:app) { lambda { |env| } }
let(:env) { {
box_collection: box_collection,
tmp_path: Pathname.new(Dir.mktmpdir),
ui: Vagrant::UI::Silent.new,
} }
subject { described_class.new(app, env) }
let(:box_collection) { double("box_collection") }
let(:iso_env) { isolated_environment }
let(:box) do
box_dir = iso_env.box3("foo", "1.0", :virtualbox)
Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir)
end
# Helper to quickly SHA1 checksum a path
def checksum(path)
FileChecksum.new(path, Digest::SHA1).checksum
end
def with_web_server(path)
tf = Tempfile.new("vagrant")
tf.close
mime_types = WEBrick::HTTPUtils::DefaultMimeTypes
mime_types.store "json", "application/json"
port = 3838
server = WEBrick::HTTPServer.new(
AccessLog: [],
Logger: WEBrick::Log.new(tf.path, 7),
Port: port,
DocumentRoot: path.dirname.to_s,
MimeTypes: mime_types)
thr = Thread.new { server.start }
yield port
ensure
server.shutdown rescue nil
thr.join rescue nil
end
before do
box_collection.stub(find: nil)
end
context "with box file directly" do
it "adds it" do
box_path = iso_env.box2_file(:virtualbox)
env[:box_name] = "foo"
env[:box_url] = box_path.to_s
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo")
expect(version).to eq("0")
expect(opts[:metadata_url]).to be_nil
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
end
it "adds from multiple URLs" do
box_path = iso_env.box2_file(:virtualbox)
env[:box_name] = "foo"
env[:box_url] = [
"/foo/bar/baz",
box_path.to_s,
]
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo")
expect(version).to eq("0")
expect(opts[:metadata_url]).to be_nil
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
end
it "adds from HTTP URL" do
box_path = iso_env.box2_file(:virtualbox)
with_web_server(box_path) do |port|
env[:box_name] = "foo"
env[:box_url] = "http://127.0.0.1:#{port}/#{box_path.basename}"
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo")
expect(version).to eq("0")
expect(opts[:metadata_url]).to be_nil
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
end
end
it "raises an error if no name is given" do
box_path = iso_env.box2_file(:virtualbox)
env[:box_url] = box_path.to_s
box_collection.should_receive(:add).never
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxAddNameRequired)
end
it "raises an error if the box already exists" do
box_path = iso_env.box2_file(:virtualbox)
env[:box_name] = "foo"
env[:box_url] = box_path.to_s
env[:box_provider] = "virtualbox"
box_collection.should_receive(:find).with(
"foo", ["virtualbox"], "0").and_return(box)
box_collection.should_receive(:add).never
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxAlreadyExists)
end
it "force adds if exists and specified" do
box_path = iso_env.box2_file(:virtualbox)
env[:box_force] = true
env[:box_name] = "foo"
env[:box_url] = box_path.to_s
env[:box_provider] = "virtualbox"
box_collection.stub(find: box)
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo")
expect(version).to eq("0")
expect(opts[:metadata_url]).to be_nil
true
end.and_return(box)
app.should_receive(:call).with(env).once
subject.call(env)
end
end
context "with box metadata" do
it "adds from HTTP URL" do
box_path = iso_env.box2_file(:virtualbox)
tf = Tempfile.new(["vagrant", ".json"]).tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{box_path}"
}
]
}
]
}
RAW
f.close
end
md_path = Pathname.new(tf.path)
with_web_server(md_path) do |port|
env[:box_url] = "http://127.0.0.1:#{port}/#{md_path.basename}"
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(name).to eq("foo/bar")
expect(version).to eq("0.7")
expect(checksum(path)).to eq(checksum(box_path))
expect(opts[:metadata_url]).to eq(env[:box_url])
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
end
end
it "adds from shorthand path" do
box_path = iso_env.box2_file(:virtualbox)
td = Pathname.new(Dir.mktmpdir)
tf = td.join("mitchellh", "precise64.json")
tf.dirname.mkpath
tf.open("w") do |f|
f.write(<<-RAW)
{
"name": "mitchellh/precise64",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{box_path}"
}
]
}
]
}
RAW
end
with_web_server(tf.dirname) do |port|
url = "http://127.0.0.1:#{port}"
env[:box_url] = "mitchellh/precise64.json"
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(name).to eq("mitchellh/precise64")
expect(version).to eq("0.7")
expect(checksum(path)).to eq(checksum(box_path))
expect(opts[:metadata_url]).to eq(
"#{url}/#{env[:box_url]}")
true
end.and_return(box)
app.should_receive(:call).with(env)
with_temp_env("VAGRANT_SERVER_URL" => url) do
subject.call(env)
end
end
end
it "raises an error if no Vagrant server is set" do
tf = Tempfile.new("foo")
tf.close
env[:box_url] = "mitchellh/precise64.json"
box_collection.should_receive(:add).never
app.should_receive(:call).never
Vagrant.stub(server_url: nil)
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxServerNotSet)
end
it "raises an error if shorthand is invalid" do
tf = Tempfile.new("foo")
tf.close
with_web_server(Pathname.new(tf.path)) do |port|
env[:box_url] = "mitchellh/precise64.json"
box_collection.should_receive(:add).never
app.should_receive(:call).never
url = "http://127.0.0.1:#{port}"
with_temp_env("VAGRANT_SERVER_URL" => url) do
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxAddShortNotFound)
end
end
end
it "raises an error if multiple metadata URLs are given" do
box_path = iso_env.box2_file(:virtualbox)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{box_path}"
}
]
}
]
}
RAW
f.close
end
env[:box_url] = [
"/foo/bar/baz",
tf.path,
]
box_collection.should_receive(:add).never
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxAddMetadataMultiURL)
end
it "adds the latest version of a box with only one provider" do
box_path = iso_env.box2_file(:virtualbox)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{box_path}"
}
]
}
]
}
RAW
f.close
end
env[:box_url] = tf.path
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo/bar")
expect(version).to eq("0.7")
expect(opts[:metadata_url]).to eq("file://#{tf.path}")
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
end
it "adds the latest version of a box with the specified provider" do
box_path = iso_env.box2_file(:vmware)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{iso_env.box2_file(:virtualbox)}"
},
{
"name": "vmware",
"url": "#{box_path}"
}
]
}
]
}
RAW
f.close
end
env[:box_url] = tf.path
env[:box_provider] = "vmware"
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo/bar")
expect(version).to eq("0.7")
expect(opts[:metadata_url]).to eq("file://#{tf.path}")
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
expect(env[:box_added]).to equal(box)
end
it "adds the latest version of a box with the specified provider, even if not latest" do
box_path = iso_env.box2_file(:vmware)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{iso_env.box2_file(:virtualbox)}"
},
{
"name": "vmware",
"url": "#{box_path}"
}
]
},
{
"version": "1.5"
}
]
}
RAW
f.close
end
env[:box_url] = tf.path
env[:box_provider] = "vmware"
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo/bar")
expect(version).to eq("0.7")
expect(opts[:metadata_url]).to eq("file://#{tf.path}")
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
expect(env[:box_added]).to equal(box)
end
it "adds the constrained version of a box with the only provider" do
box_path = iso_env.box2_file(:vmware)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5",
"providers": [
{
"name": "vmware",
"url": "#{box_path}"
}
]
},
{ "version": "1.1" }
]
}
RAW
f.close
end
env[:box_url] = tf.path
env[:box_version] = "~> 0.1"
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo/bar")
expect(version).to eq("0.5")
expect(opts[:metadata_url]).to eq("file://#{tf.path}")
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
expect(env[:box_added]).to equal(box)
end
it "adds the constrained version of a box with the specified provider" do
box_path = iso_env.box2_file(:vmware)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5",
"providers": [
{
"name": "vmware",
"url": "#{box_path}"
},
{
"name": "virtualbox",
"url": "#{iso_env.box2_file(:virtualbox)}"
}
]
},
{ "version": "1.1" }
]
}
RAW
f.close
end
env[:box_url] = tf.path
env[:box_provider] = "vmware"
env[:box_version] = "~> 0.1"
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo/bar")
expect(version).to eq("0.5")
expect(opts[:metadata_url]).to eq("file://#{tf.path}")
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
expect(env[:box_added]).to equal(box)
end
it "adds the latest version of a box with any specified provider" do
box_path = iso_env.box2_file(:vmware)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5",
"providers": [
{
"name": "virtualbox",
"url": "#{iso_env.box2_file(:virtualbox)}"
}
]
},
{
"version": "0.7",
"providers": [
{
"name": "vmware",
"url": "#{box_path}"
}
]
}
]
}
RAW
f.close
end
env[:box_url] = tf.path
env[:box_provider] = ["virtualbox", "vmware"]
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo/bar")
expect(version).to eq("0.7")
expect(opts[:metadata_url]).to eq("file://#{tf.path}")
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
expect(env[:box_added]).to equal(box)
end
it "asks the user what provider if multiple options" do
box_path = iso_env.box2_file(:virtualbox)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{box_path}"
},
{
"name": "vmware",
"url": "#{iso_env.box2_file(:vmware)}"
}
]
}
]
}
RAW
f.close
end
env[:box_url] = tf.path
env[:ui].should_receive(:ask).and_return("1")
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo/bar")
expect(version).to eq("0.7")
expect(opts[:metadata_url]).to eq("file://#{tf.path}")
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
end
it "raises an exception if the name doesn't match a requested name" do
box_path = iso_env.box2_file(:virtualbox)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{box_path}"
}
]
}
]
}
RAW
f.close
end
env[:box_name] = "foo"
env[:box_url] = tf.path
box_collection.should_receive(:add).never
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxAddNameMismatch)
end
it "raises an exception if no matching version" do
box_path = iso_env.box2_file(:vmware)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5",
"providers": [
{
"name": "vmware",
"url": "#{box_path}"
}
]
},
{ "version": "1.1" }
]
}
RAW
f.close
end
env[:box_url] = tf.path
env[:box_version] = "~> 2.0"
box_collection.should_receive(:add).never
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxAddNoMatchingVersion)
end
it "raises an error if there is no matching provider" do
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{iso_env.box2_file(:virtualbox)}"
}
]
}
]
}
RAW
f.close
end
env[:box_url] = tf.path
env[:box_provider] = "vmware"
box_collection.should_receive(:add).never
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxAddNoMatchingProvider)
end
it "raises an error if a box already exists" do
box_path = iso_env.box2_file(:virtualbox)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{box_path}"
}
]
}
]
}
RAW
f.close
end
env[:box_url] = tf.path
box_collection.should_receive(:find).
with("foo/bar", "virtualbox", "0.7").and_return(box)
box_collection.should_receive(:add).never
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxAlreadyExists)
end
it "force adds a box if specified" do
box_path = iso_env.box2_file(:virtualbox)
tf = Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo/bar",
"versions": [
{
"version": "0.5"
},
{
"version": "0.7",
"providers": [
{
"name": "virtualbox",
"url": "#{box_path}"
}
]
}
]
}
RAW
f.close
end
env[:box_force] = true
env[:box_url] = tf.path
box_collection.stub(find: box)
box_collection.should_receive(:add).with do |path, name, version, **opts|
expect(checksum(path)).to eq(checksum(box_path))
expect(name).to eq("foo/bar")
expect(version).to eq("0.7")
expect(opts[:force]).to be_true
expect(opts[:metadata_url]).to eq("file://#{tf.path}")
true
end.and_return(box)
app.should_receive(:call).with(env)
subject.call(env)
expect(env[:box_added]).to equal(box)
end
end
end

View File

@ -0,0 +1,146 @@
require File.expand_path("../../../../base", __FILE__)
describe Vagrant::Action::Builtin::BoxCheckOutdated do
include_context "unit"
let(:app) { lambda { |env| } }
let(:env) { {
box_collection: iso_vagrant_env.boxes,
machine: machine,
ui: Vagrant::UI::Silent.new,
} }
subject { described_class.new(app, env) }
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
isolated_environment.tap do |env|
env.vagrantfile("")
end
end
let(:iso_vagrant_env) { iso_env.create_vagrant_env }
let(:box) do
box_dir = iso_env.box3("foo", "1.0", :virtualbox)
Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir).tap do |b|
b.stub(has_update?: nil)
end
end
let(:machine) do
m = iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy)
m.config.vm.box_check_update = true
m
end
before do
machine.stub(box: box)
end
context "disabling outdated checking" do
it "doesn't check" do
machine.config.vm.box_check_update = false
app.should_receive(:call).with(env).once
subject.call(env)
expect(env).to_not have_key(:box_outdated)
end
it "checks if forced" do
machine.config.vm.box_check_update = false
env[:box_outdated_force] = true
app.should_receive(:call).with(env).once
subject.call(env)
expect(env).to have_key(:box_outdated)
end
end
context "no box" do
it "raises an exception if the machine doesn't have a box yet" do
machine.stub(box: nil)
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxOutdatedNoBox)
end
end
context "with a non-versioned box" do
it "does nothing" do
box.stub(metadata_url: nil)
box.stub(version: "0")
app.should_receive(:call).once
box.should_receive(:has_update?).never
subject.call(env)
end
end
context "with a box" do
it "sets env if no update" do
box.should_receive(:has_update?).and_return(nil)
app.should_receive(:call).with(env).once
subject.call(env)
expect(env[:box_outdated]).to be_false
end
it "sets env if there is an update" do
md = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))
{
"name": "foo",
"versions": [
{
"version": "1.0"
},
{
"version": "1.1",
"providers": [
{
"name": "virtualbox",
"url": "bar"
}
]
}
]
}
RAW
box.should_receive(:has_update?).with(machine.config.vm.box_version).
and_return([md, md.version("1.1"), md.version("1.1").provider("virtualbox")])
app.should_receive(:call).with(env).once
subject.call(env)
expect(env[:box_outdated]).to be_true
end
it "raises error if has_update? errors" do
box.should_receive(:has_update?).and_raise(Vagrant::Errors::VagrantError)
app.should_receive(:call).never
expect { subject.call(env) }.to raise_error(Vagrant::Errors::VagrantError)
end
it "doesn't raise an error if ignore errors is on" do
env[:box_outdated_ignore_errors] = true
box.should_receive(:has_update?).and_raise(Vagrant::Errors::VagrantError)
app.should_receive(:call).with(env).once
expect { subject.call(env) }.to_not raise_error
end
end
end

View File

@ -0,0 +1,108 @@
require File.expand_path("../../../../base", __FILE__)
describe Vagrant::Action::Builtin::BoxRemove do
include_context "unit"
let(:app) { lambda { |env| } }
let(:env) { {
box_collection: box_collection,
ui: Vagrant::UI::Silent.new,
} }
subject { described_class.new(app, env) }
let(:box_collection) { double("box_collection") }
let(:iso_env) { isolated_environment }
let(:box) do
box_dir = iso_env.box3("foo", "1.0", :virtualbox)
Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir)
end
it "deletes the box if it is the only option" do
box_collection.stub(all: [["foo", "1.0", :virtualbox]])
env[:box_name] = "foo"
box_collection.should_receive(:find).with(
"foo", :virtualbox, "1.0").and_return(box)
box.should_receive(:destroy!).once
app.should_receive(:call).with(env).once
subject.call(env)
expect(env[:box_removed]).to equal(box)
end
it "deletes the box with the specified provider if given" do
box_collection.stub(
all: [
["foo", "1.0", :virtualbox],
["foo", "1.0", :vmware],
])
env[:box_name] = "foo"
env[:box_provider] = "virtualbox"
box_collection.should_receive(:find).with(
"foo", :virtualbox, "1.0").and_return(box)
box.should_receive(:destroy!).once
app.should_receive(:call).with(env).once
subject.call(env)
expect(env[:box_removed]).to equal(box)
end
it "errors if the box doesn't exist" do
box_collection.stub(all: [])
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxRemoveNotFound)
end
it "errors if the specified provider doesn't exist" do
env[:box_name] = "foo"
env[:box_provider] = "bar"
box_collection.stub(all: [["foo", "1.0", :virtualbox]])
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxRemoveProviderNotFound)
end
it "errors if there are multiple providers" do
env[:box_name] = "foo"
box_collection.stub(
all: [
["foo", "1.0", :virtualbox],
["foo", "1.0", :vmware],
])
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxRemoveMultiProvider)
end
it "errors if the specified provider has multiple versions" do
env[:box_name] = "foo"
env[:box_provider] = "virtualbox"
box_collection.stub(
all: [
["foo", "1.0", :virtualbox],
["foo", "1.1", :virtualbox],
])
app.should_receive(:call).never
expect { subject.call(env) }.
to raise_error(Vagrant::Errors::BoxRemoveMultiVersion)
end
end

View File

@ -0,0 +1,109 @@
require File.expand_path("../../../../base", __FILE__)
describe Vagrant::Action::Builtin::HandleBox do
include_context "unit"
let(:app) { lambda { |env| } }
let(:env) { {
action_runner: action_runner,
machine: machine,
ui: Vagrant::UI::Silent.new,
} }
subject { described_class.new(app, env) }
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
isolated_environment.tap do |env|
env.vagrantfile("")
end
end
let(:iso_vagrant_env) { iso_env.create_vagrant_env }
let(:action_runner) { double("action_runner") }
let(:box) do
box_dir = iso_env.box3("foo", "1.0", :virtualbox)
Vagrant::Box.new("foo", :virtualbox, "1.0", box_dir)
end
let(:machine) { iso_vagrant_env.machine(iso_vagrant_env.machine_names[0], :dummy) }
it "works if there is no box set" do
machine.config.vm.box = nil
machine.config.vm.box_url = nil
app.should_receive(:call).with(env)
subject.call(env)
end
it "doesn't do anything if a box exists" do
machine.stub(box: box)
action_runner.should_receive(:run).never
app.should_receive(:call).with(env)
subject.call(env)
end
context "with a box set and no box_url" do
before do
machine.stub(box: nil)
machine.config.vm.box = "foo"
end
it "adds a box that doesn't exist" do
action_runner.should_receive(:run).with do |action, opts|
expect(opts[:box_name]).to eq(machine.config.vm.box)
expect(opts[:box_url]).to eq(machine.config.vm.box)
expect(opts[:box_provider]).to eq(:dummy)
expect(opts[:box_version]).to eq(machine.config.vm.box_version)
true
end
app.should_receive(:call).with(env)
subject.call(env)
end
it "adds a box using any format the provider allows" do
machine.provider_options[:box_format] = [:foo, :bar]
action_runner.should_receive(:run).with do |action, opts|
expect(opts[:box_name]).to eq(machine.config.vm.box)
expect(opts[:box_url]).to eq(machine.config.vm.box)
expect(opts[:box_provider]).to eq([:foo, :bar])
expect(opts[:box_version]).to eq(machine.config.vm.box_version)
true
end
app.should_receive(:call).with(env)
subject.call(env)
end
end
context "with a box and box_url set" do
before do
machine.stub(box: nil)
machine.config.vm.box = "foo"
machine.config.vm.box_url = "bar"
end
it "adds a box that doesn't exist" do
action_runner.should_receive(:run).with do |action, opts|
expect(opts[:box_name]).to eq(machine.config.vm.box)
expect(opts[:box_url]).to eq(machine.config.vm.box_url)
expect(opts[:box_provider]).to eq(:dummy)
expect(opts[:box_version]).to eq(machine.config.vm.box_version)
true
end
app.should_receive(:call).with(env)
subject.call(env)
end
end
end

View File

@ -8,35 +8,141 @@ describe Vagrant::BoxCollection do
let(:box_class) { Vagrant::Box }
let(:environment) { isolated_environment }
let(:instance) { described_class.new(environment.boxes_dir) }
subject { described_class.new(environment.boxes_dir) }
it "should tell us the directory it is using" do
instance.directory.should == environment.boxes_dir
subject.directory.should == environment.boxes_dir
end
describe "adding" do
describe "#all" do
it "should return an empty array when no boxes are there" do
subject.all.should == []
end
it "should return the boxes and their providers" do
# Create some boxes
environment.box3("foo", "1.0", :virtualbox)
environment.box3("foo", "1.0", :vmware)
environment.box3("bar", "0", :ec2)
environment.box3("foo-VAGRANTSLASH-bar", "1.0", :virtualbox)
# Verify some output
results = subject.all
results.length.should == 4
results.include?(["foo", "1.0", :virtualbox]).should be
results.include?(["foo", "1.0", :vmware]).should be
results.include?(["bar", "0", :ec2]).should be
results.include?(["foo/bar", "1.0", :virtualbox]).should be
end
it 'does not raise an exception when a file appears in the boxes dir' do
Tempfile.new('a_file', environment.boxes_dir)
expect { subject.all }.to_not raise_error
end
end
describe "#find" do
it "returns nil if the box does not exist" do
expect(subject.find("foo", :i_dont_exist, ">= 0")).to be_nil
end
it "returns a box if the box does exist" do
# Create the "box"
environment.box3("foo", "0", :virtualbox)
# Actual test
result = subject.find("foo", :virtualbox, ">= 0")
expect(result).to_not be_nil
expect(result).to be_kind_of(box_class)
expect(result.name).to eq("foo")
end
it "returns latest version matching constraint" do
# Create the "box"
environment.box3("foo", "1.0", :virtualbox)
environment.box3("foo", "1.5", :virtualbox)
# Actual test
result = subject.find("foo", :virtualbox, ">= 0")
expect(result).to_not be_nil
expect(result).to be_kind_of(box_class)
expect(result.name).to eq("foo")
expect(result.version).to eq("1.5")
end
it "can satisfy complex constraints" do
# Create the "box"
environment.box3("foo", "0.1", :virtualbox)
environment.box3("foo", "1.0", :virtualbox)
environment.box3("foo", "2.1", :virtualbox)
# Actual test
result = subject.find("foo", :virtualbox, ">= 0.9, < 1.5")
expect(result).to_not be_nil
expect(result).to be_kind_of(box_class)
expect(result.name).to eq("foo")
expect(result.version).to eq("1.0")
end
it "returns nil if a box's constraints can't be satisfied" do
# Create the "box"
environment.box3("foo", "0.1", :virtualbox)
environment.box3("foo", "1.0", :virtualbox)
environment.box3("foo", "2.1", :virtualbox)
# Actual test
result = subject.find("foo", :virtualbox, "> 1.0, < 1.5")
expect(result).to be_nil
end
end
describe "#add" 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
box = subject.add(box_path, "foo", "1.0", providers: :virtualbox)
expect(box).to be_kind_of(box_class)
expect(box.name).to eq("foo")
expect(box.provider).to eq(:virtualbox)
# Verify we can find it as well
box = instance.find("foo", :virtualbox)
box.should_not be_nil
expect(subject.find("foo", :virtualbox, "1.0")).to_not be_nil
end
it "should add a box with a name with '/' in it" do
box_path = environment.box2_file(:virtualbox)
# Add the box
box = subject.add(box_path, "foo/bar", "1.0")
expect(box).to be_kind_of(box_class)
expect(box.name).to eq("foo/bar")
expect(box.provider).to eq(:virtualbox)
# Verify we can find it as well
expect(subject.find("foo/bar", :virtualbox, "1.0")).to_not be_nil
end
it "should add a box without specifying a provider" do
box_path = environment.box2_file(:vmware)
# Add the box
box = instance.add(box_path, "foo")
box.should be_kind_of(box_class)
box.name.should == "foo"
box.provider.should == :vmware
box = subject.add(box_path, "foo", "1.0")
expect(box).to be_kind_of(box_class)
expect(box.name).to eq("foo")
expect(box.provider).to eq(:vmware)
end
it "should store a metadata URL" do
box_path = environment.box2_file(:virtualbox)
subject.add(
box_path, "foo", "1.0",
metadata_url: "bar")
box = subject.find("foo", :virtualbox, "1.0")
expect(box.metadata_url).to eq("bar")
end
it "should add a V1 box" do
@ -44,35 +150,39 @@ describe Vagrant::BoxCollection do
box_path = environment.box1_file
# Add the box
box = instance.add(box_path, "foo")
box.should be_kind_of(box_class)
box.name.should == "foo"
box.provider.should == :virtualbox
box = subject.add(box_path, "foo", "1.0")
expect(box).to be_kind_of(box_class)
expect(box.name).to eq("foo")
expect(box.provider).to eq(:virtualbox)
end
it "should raise an exception if the box already exists" do
prev_box_name = "foo"
prev_box_provider = :virtualbox
prev_box_version = "1.0"
# Create the box we're adding
environment.box2(prev_box_name, prev_box_provider)
environment.box3(prev_box_name, "1.0", 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)
expect {
subject.add(box_path, prev_box_name,
prev_box_version, providers: prev_box_provider)
}.to raise_error(Vagrant::Errors::BoxAlreadyExists)
end
it "should replace the box if force is specified" do
prev_box_name = "foo"
prev_box_provider = :vmware
prev_box_version = "1.0"
# Setup the environment with the box pre-added
environment.box2(prev_box_name, prev_box_provider)
environment.box3(prev_box_name, prev_box_version, prev_box_provider)
# Attempt to add the box with the same name
box_path = environment.box2_file(prev_box_provider, metadata: { "replaced" => "yes" })
box = instance.add(box_path, prev_box_name, nil, true)
box = subject.add(box_path, prev_box_name, prev_box_version, force: true)
box.metadata["replaced"].should == "yes"
end
@ -82,25 +192,13 @@ describe Vagrant::BoxCollection do
box_path = environment.box2_file(:vmware)
# Add it once, successfully
expect { instance.add(box_path, box_name) }.to_not raise_error
expect { subject.add(box_path, box_name, "1.0") }.to_not raise_error
# Add it again, and fail!
expect { instance.add(box_path, box_name) }.
expect { subject.add(box_path, box_name, "1.0") }.
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) }.
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
@ -111,11 +209,11 @@ describe Vagrant::BoxCollection do
# Add the box but with an invalid provider, verify we get the proper
# error.
expect { instance.add(box_path, box_name, bad_provider) }.
expect { subject.add(box_path, box_name, "1.0", providers: bad_provider) }.
to raise_error(Vagrant::Errors::BoxProviderDoesntMatch)
# Verify the box doesn't exist
instance.find(box_name, bad_provider).should be_nil
expect(subject.find(box_name, bad_provider, "1.0")).to be_nil
end
it "should raise an exception if you add an invalid box file" do
@ -130,7 +228,7 @@ describe Vagrant::BoxCollection do
f.write("\0"*CHECKSUM_LENGTH)
f.close
expect { instance.add(f.path, "foo", :virtualbox) }.
expect { subject.add(f.path, "foo", "1.0") }.
to raise_error(Vagrant::Errors::BoxUnpackageFailure)
ensure
f.close
@ -139,103 +237,36 @@ describe Vagrant::BoxCollection do
end
end
describe "listing all" do
it "should return an empty array when no boxes are there" do
instance.all.should == []
describe "#upgrade_v1_1_v1_5" do
let(:boxes_dir) { environment.boxes_dir }
before do
# Create all the various box directories
@foo_path = environment.box2("foo", "virtualbox")
@vbox_path = environment.box2("precise64", "virtualbox")
@vmware_path = environment.box2("precise64", "vmware")
@v1_path = environment.box("v1box")
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)
it "upgrades the boxes" do
subject.upgrade_v1_1_v1_5
# Verify some output
results = instance.all
results.length.should == 3
results.include?(["foo", :virtualbox]).should be
results.include?(["foo", :vmware]).should be
results.include?(["bar", :ec2]).should be
end
# The old paths should not exist anymore
expect(@foo_path).to_not exist
expect(@vbox_path).to_not exist
expect(@vmware_path).to_not exist
expect(@v1_path.join("box.ovf")).to_not exist
it "should return V1 boxes as well" do
# Create some boxes, including a V1 box
environment.box1("bar")
environment.box2("foo", :vmware)
# New paths should exist
foo_path = boxes_dir.join("foo", "0", "virtualbox")
vbox_path = boxes_dir.join("precise64", "0", "virtualbox")
vmware_path = boxes_dir.join("precise64", "0", "vmware")
v1_path = boxes_dir.join("v1box", "0", "virtualbox")
# Verify some output
results = instance.all.sort
results.should == [["bar", :virtualbox, :v1], ["foo", :vmware]]
end
it 'does not raise an exception when a file appears in the boxes dir' do
Tempfile.new('a_file', environment.boxes_dir)
expect { instance.all }.to_not raise_error
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
it "should return nil if there is a V1 box but we're looking for another provider" do
# Create a V1 box
environment.box1("foo")
# Test
instance.find("foo", :another_provider).should be_nil
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
expect(foo_path).to exist
expect(vbox_path).to exist
expect(vmware_path).to exist
expect(v1_path).to exist
end
end
end

View File

@ -0,0 +1,157 @@
require File.expand_path("../../base", __FILE__)
require "vagrant/box_metadata"
describe Vagrant::BoxMetadata do
include_context "unit"
let(:raw) do
<<-RAW
{
"name": "foo",
"description": "bar",
"versions": [
{
"version": "1.0.0",
"providers": [
{ "name": "virtualbox" },
{ "name": "vmware" }
]
},
{
"version": "1.1.5",
"providers": [
{ "name": "virtualbox" }
]
},
{
"version": "1.1.0",
"providers": [
{ "name": "virtualbox" },
{ "name": "vmware" }
]
}
]
}
RAW
end
subject { described_class.new(raw) }
its(:name) { should eq("foo") }
its(:description) { should eq("bar") }
context "with poorly formatted JSON" do
let(:raw) {
<<-RAW
{ "name": "foo", }
RAW
}
it "raises an exception" do
expect { subject }.
to raise_error(Vagrant::Errors::BoxMetadataMalformed)
end
end
describe "#version" do
it "matches an exact version" do
result = subject.version("1.0.0")
expect(result).to_not be_nil
expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)
expect(result.version).to eq("1.0.0")
end
it "matches a constraint with latest matching version" do
result = subject.version(">= 1.0")
expect(result).to_not be_nil
expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)
expect(result.version).to eq("1.1.5")
end
it "matches complex constraints" do
result = subject.version(">= 0.9, ~> 1.0.0")
expect(result).to_not be_nil
expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)
expect(result.version).to eq("1.0.0")
end
it "matches the constraint that has the given provider" do
result = subject.version(">= 0", provider: :vmware)
expect(result).to_not be_nil
expect(result).to be_kind_of(Vagrant::BoxMetadata::Version)
expect(result.version).to eq("1.1.0")
end
end
describe "#versions" do
it "returns the versions it contained" do
expect(subject.versions).to eq(
["1.0.0", "1.1.0", "1.1.5"])
end
end
end
describe Vagrant::BoxMetadata::Version do
let(:raw) { {} }
subject { described_class.new(raw) }
before do
raw["providers"] = [
{
"name" => "virtualbox",
},
{
"name" => "vmware",
}
]
end
describe "#version" do
it "is the version in the raw data" do
v = "1.0"
raw["version"] = v
expect(subject.version).to eq(v)
end
end
describe "#provider" do
it "returns nil if a provider isn't supported" do
expect(subject.provider("foo")).to be_nil
end
it "returns the provider specified" do
result = subject.provider("virtualbox")
expect(result).to_not be_nil
expect(result).to be_kind_of(Vagrant::BoxMetadata::Provider)
end
end
describe "#providers" do
it "returns the providers available" do
expect(subject.providers.sort).to eq(
[:virtualbox, :vmware])
end
end
end
describe Vagrant::BoxMetadata::Provider do
let(:raw) { {} }
subject { described_class.new(raw) }
describe "#name" do
it "is the name specified" do
raw["name"] = "foo"
expect(subject.name).to eq("foo")
end
end
describe "#url" do
it "is the URL specified" do
raw["url"] = "bar"
expect(subject.url).to eq("bar")
end
end
end

View File

@ -1,6 +1,10 @@
require File.expand_path("../../base", __FILE__)
require "pathname"
require "stringio"
require "tempfile"
require "vagrant/box_metadata"
describe Vagrant::Box do
include_context "unit"
@ -11,21 +15,22 @@ describe Vagrant::Box do
let(:name) { "foo" }
let(:provider) { :virtualbox }
let(:directory) { environment.box2("foo", :virtualbox) }
let(:instance) { described_class.new(name, provider, directory) }
let(:version) { "1.0" }
let(:directory) { environment.box3("foo", "1.0", :virtualbox) }
subject { described_class.new(name, provider, version, directory) }
subject { described_class.new(name, provider, directory) }
its(:metadata_url) { should be_nil }
it "provides the name" do
instance.name.should == name
subject.name.should == name
end
it "provides the provider" do
instance.provider.should == provider
subject.provider.should == provider
end
it "provides the directory" do
instance.directory.should == directory
subject.directory.should == directory
end
it "provides the metadata associated with a box" do
@ -37,7 +42,17 @@ describe Vagrant::Box do
end
# Verify the metadata
instance.metadata.should == data
subject.metadata.should == data
end
context "with a metadata URL" do
subject do
described_class.new(
name, provider, version, directory,
metadata_url: "foo")
end
its(:metadata_url) { should eq("foo") }
end
context "with a corrupt metadata file" do
@ -64,21 +79,157 @@ describe Vagrant::Box do
end
end
context "#has_update?" do
subject do
described_class.new(
name, provider, version, directory,
metadata_url: "foo")
end
it "raises an exception if no metadata_url is set" do
subject = described_class.new(
name, provider, version, directory)
expect { subject.has_update?("> 0") }.
to raise_error(Vagrant::Errors::BoxUpdateNoMetadata)
end
it "returns nil if there is no update" do
metadata = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))
{
"name": "foo",
"versions": [
{ "version": "1.0" }
]
}
RAW
subject.stub(load_metadata: metadata)
expect(subject.has_update?).to be_nil
end
it "returns the updated box info if there is an update available" do
metadata = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))
{
"name": "foo",
"versions": [
{
"version": "1.0"
},
{
"version": "1.1",
"providers": [
{
"name": "virtualbox",
"url": "bar"
}
]
}
]
}
RAW
subject.stub(load_metadata: metadata)
result = subject.has_update?
expect(result).to_not be_nil
expect(result[0]).to be_kind_of(Vagrant::BoxMetadata)
expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version)
expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider)
expect(result[0].name).to eq("foo")
expect(result[1].version).to eq("1.1")
expect(result[2].url).to eq("bar")
end
it "returns the updated box info within constraints" do
metadata = Vagrant::BoxMetadata.new(StringIO.new(<<-RAW))
{
"name": "foo",
"versions": [
{
"version": "1.0"
},
{
"version": "1.1",
"providers": [
{
"name": "virtualbox",
"url": "bar"
}
]
},
{
"version": "1.4",
"providers": [
{
"name": "virtualbox",
"url": "bar"
}
]
}
]
}
RAW
subject.stub(load_metadata: metadata)
result = subject.has_update?(">= 1.1, < 1.4")
expect(result).to_not be_nil
expect(result[0]).to be_kind_of(Vagrant::BoxMetadata)
expect(result[1]).to be_kind_of(Vagrant::BoxMetadata::Version)
expect(result[2]).to be_kind_of(Vagrant::BoxMetadata::Provider)
expect(result[0].name).to eq("foo")
expect(result[1].version).to eq("1.1")
expect(result[2].url).to eq("bar")
end
end
context "#load_metadata" do
let(:metadata_url) do
Tempfile.new("vagrant").tap do |f|
f.write(<<-RAW)
{
"name": "foo",
"description": "bar"
}
RAW
f.close
end
end
subject do
described_class.new(
name, provider, version, directory,
metadata_url: metadata_url.path)
end
it "loads the url and returns the data" do
result = subject.load_metadata
expect(result.name).to eq("foo")
expect(result.description).to eq("bar")
end
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
subject.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
# Get the subject so that it is instantiated
box = subject
# Delete the directory
directory.rmtree
@ -100,36 +251,51 @@ describe Vagrant::Box do
# Repackage our box to some temporary directory
box_output_path = temporary_dir.join("package.box")
instance.repackage(box_output_path).should be
expect(subject.repackage(box_output_path)).to be_true
# Let's now add this box again under a different name, and then
# verify that we get the proper result back.
new_box = box_collection.add(box_output_path, "foo2")
new_box = box_collection.add(box_output_path, "foo2", "1.0")
new_box.directory.join("test_file").read.should == test_file_contents
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)
it "should be equal if the name, provider, version match" do
a = described_class.new("a", :foo, "1.0", directory)
b = described_class.new("a", :foo, "1.0", 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)
it "should not be equal if name doesn't match" do
a = described_class.new("a", :foo, "1.0", directory)
b = described_class.new("b", :foo, "1.0", directory)
a.should_not == b
expect(a).to_not eq(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)
it "should not be equal if provider doesn't match" do
a = described_class.new("a", :foo, "1.0", directory)
b = described_class.new("a", :bar, "1.0", directory)
[c, a, b].sort.should == [a, b, c]
expect(a).to_not eq(b)
end
it "should not be equal if version doesn't match" do
a = described_class.new("a", :foo, "1.0", directory)
b = described_class.new("a", :foo, "1.1", directory)
expect(a).to_not eq(b)
end
it "should sort them in order of name, version, provider" do
a = described_class.new("a", :foo, "1.0", directory)
b = described_class.new("a", :foo2, "1.0", directory)
c = described_class.new("a", :foo2, "1.1", directory)
d = described_class.new("b", :foo2, "1.0", directory)
[d, c, a, b].sort.should == [a, b, c, d]
end
end
end

View File

@ -13,7 +13,7 @@ describe Vagrant::Environment do
let(:env) do
isolated_environment.tap do |e|
e.box2("base", :virtualbox)
e.box3("base", "1.0", :virtualbox)
e.vagrantfile <<-VF
Vagrant.configure("2") do |config|
config.vm.box = "base"
@ -25,6 +25,100 @@ describe Vagrant::Environment do
let(:instance) { env.create_vagrant_env }
subject { instance }
describe "#home_path" do
it "is set to the home path given" do
Dir.mktmpdir do |dir|
instance = described_class.new(:home_path => dir)
instance.home_path.should == Pathname.new(dir)
end
end
it "is set to the environmental variable VAGRANT_HOME" do
Dir.mktmpdir do |dir|
instance = with_temp_env("VAGRANT_HOME" => dir) do
described_class.new
end
instance.home_path.should == Pathname.new(dir)
end
end
it "throws an exception if inaccessible" do
expect {
described_class.new(:home_path => "/")
}.to raise_error(Vagrant::Errors::HomeDirectoryNotAccessible)
end
context "with setup version file" do
it "creates a setup version flie" do
path = subject.home_path.join("setup_version")
expect(path).to be_file
expect(path.read).to eq(Vagrant::Environment::CURRENT_SETUP_VERSION)
end
it "is okay if it has the current version" do
Dir.mktmpdir do |dir|
Pathname.new(dir).join("setup_version").open("w") do |f|
f.write(Vagrant::Environment::CURRENT_SETUP_VERSION)
end
instance = described_class.new(home_path: dir)
path = instance.home_path.join("setup_version")
expect(path).to be_file
expect(path.read).to eq(Vagrant::Environment::CURRENT_SETUP_VERSION)
end
end
it "raises an exception if the version is newer than ours" do
Dir.mktmpdir do |dir|
Pathname.new(dir).join("setup_version").open("w") do |f|
f.write("100.5")
end
expect { described_class.new(home_path: dir) }.
to raise_error(Vagrant::Errors::HomeDirectoryLaterVersion)
end
end
it "raises an exception if there is an unknown home directory version" do
Dir.mktmpdir do |dir|
Pathname.new(dir).join("setup_version").open("w") do |f|
f.write("0.7")
end
expect { described_class.new(home_path: dir) }.
to raise_error(Vagrant::Errors::HomeDirectoryUnknownVersion)
end
end
end
context "upgrading a v1.1 directory structure" do
let(:env) { isolated_environment }
before do
env.homedir.join("setup_version").open("w") do |f|
f.write("1.1")
end
end
it "replaces the setup version with the new version" do
expect(subject.home_path.join("setup_version").read).
to eq(Vagrant::Environment::CURRENT_SETUP_VERSION)
end
it "moves the boxes into the new directory structure" do
# Kind of hacky but avoids two instantiations of BoxCollection
Vagrant::Environment.any_instance.stub(boxes: double("boxes"))
collection = double("collection")
Vagrant::BoxCollection.should_receive(:new).with(
env.homedir.join("boxes"), anything).and_return(collection)
collection.should_receive(:upgrade_v1_1_v1_5).once
subject
end
end
end
describe "#host" do
let(:plugin_hosts) { {} }
let(:plugin_host_caps) { {} }
@ -100,6 +194,334 @@ describe Vagrant::Environment do
end
end
describe "#machine" do
# A helper to register a provider for use in tests.
def register_provider(name, config_class=nil, options=nil)
provider_cls = Class.new(Vagrant.plugin("2", :provider))
register_plugin("2") do |p|
p.provider(name, options) { provider_cls }
if config_class
p.config(name, :provider) { config_class }
end
end
provider_cls
end
it "should return a machine object with the correct provider" do
# Create a provider
foo_provider = register_provider("foo")
# Create the configuration
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
config.vm.define "foo"
end
VF
e.box3("base", "1.0", :foo)
end
# Verify that we can get the machine
env = isolated_env.create_vagrant_env
machine = env.machine(:foo, :foo)
machine.should be_kind_of(Vagrant::Machine)
machine.name.should == :foo
machine.provider.should be_kind_of(foo_provider)
machine.provider_config.should be_nil
end
it "should return a machine object with the machine configuration" do
# Create a provider
foo_config = Class.new(Vagrant.plugin("2", :config)) do
attr_accessor :value
end
foo_provider = register_provider("foo", foo_config)
# Create the configuration
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
config.vm.define "foo"
config.vm.provider :foo do |fooconfig|
fooconfig.value = 100
end
end
VF
e.box3("base", "1.0", :foo)
end
# Verify that we can get the machine
env = isolated_env.create_vagrant_env
machine = env.machine(:foo, :foo)
machine.should be_kind_of(Vagrant::Machine)
machine.name.should == :foo
machine.provider.should be_kind_of(foo_provider)
machine.provider_config.value.should == 100
end
it "should cache the machine objects by name and provider" do
# Create a provider
foo_provider = register_provider("foo")
bar_provider = register_provider("bar")
# Create the configuration
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
config.vm.define "vm1"
config.vm.define "vm2"
end
VF
e.box3("base", "1.0", :foo)
e.box3("base", "1.0", :bar)
end
env = isolated_env.create_vagrant_env
vm1_foo = env.machine(:vm1, :foo)
vm1_bar = env.machine(:vm1, :bar)
vm2_foo = env.machine(:vm2, :foo)
vm1_foo.should eql(env.machine(:vm1, :foo))
vm1_bar.should eql(env.machine(:vm1, :bar))
vm1_foo.should_not eql(vm1_bar)
vm2_foo.should eql(env.machine(:vm2, :foo))
end
it "should load a machine without a box" do
register_provider("foo")
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "i-dont-exist"
end
VF
end
env = environment.create_vagrant_env
machine = env.machine(:default, :foo)
machine.box.should be_nil
end
it "should load the machine configuration" do
register_provider("foo")
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 1
config.vm.box = "base"
config.vm.define "vm1" do |inner|
inner.ssh.port = 100
end
end
VF
env.box3("base", "1.0", :foo)
end
env = environment.create_vagrant_env
machine = env.machine(:vm1, :foo)
machine.config.ssh.port.should == 100
machine.config.vm.box.should == "base"
end
it "should load the box configuration for a box" do
register_provider("foo")
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
env.box3("base", "1.0", :foo, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 100
end
VF
end
env = environment.create_vagrant_env
machine = env.machine(:default, :foo)
machine.config.ssh.port.should == 100
end
it "should load the box configuration for a box and custom Vagrantfile name" do
register_provider("foo")
environment = isolated_environment do |env|
env.file("some_other_name", <<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
env.box3("base", "1.0", :foo, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 100
end
VF
end
env = with_temp_env("VAGRANT_VAGRANTFILE" => "some_other_name") do
environment.create_vagrant_env
end
machine = env.machine(:default, :foo)
machine.config.ssh.port.should == 100
end
it "should load the box configuration for other formats for a box" do
register_provider("foo", nil, box_format: "bar")
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
env.box3("base", "1.0", :bar, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 100
end
VF
end
env = environment.create_vagrant_env
machine = env.machine(:default, :foo)
machine.config.ssh.port.should == 100
end
it "prefer sooner formats when multiple box formats are available" do
register_provider("foo", nil, box_format: ["fA", "fB"])
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
env.box3("base", "1.0", :fA, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 100
end
VF
env.box3("base", "1.0", :fB, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 200
end
VF
end
env = environment.create_vagrant_env
machine = env.machine(:default, :foo)
machine.config.ssh.port.should == 100
end
it "should load the proper version of a box" do
register_provider("foo")
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
config.vm.box_version = "~> 1.2"
end
VF
env.box3("base", "1.0", :foo, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 100
end
VF
env.box3("base", "1.5", :foo, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 200
end
VF
end
env = environment.create_vagrant_env
machine = env.machine(:default, :foo)
machine.config.ssh.port.should == 200
end
it "should load the provider override if set" do
register_provider("bar")
register_provider("foo")
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "foo"
config.vm.provider :foo do |_, c|
c.vm.box = "bar"
end
end
VF
end
env = isolated_env.create_vagrant_env
foo_vm = env.machine(:default, :foo)
bar_vm = env.machine(:default, :bar)
foo_vm.config.vm.box.should == "bar"
bar_vm.config.vm.box.should == "foo"
end
it "should reload the cache if refresh is set" do
# Create a provider
foo_provider = register_provider("foo")
# Create the configuration
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
e.box3("base", "1.0", :foo)
end
env = isolated_env.create_vagrant_env
vm1 = env.machine(:default, :foo)
vm2 = env.machine(:default, :foo, true)
vm3 = env.machine(:default, :foo)
vm1.should_not eql(vm2)
vm2.should eql(vm3)
end
it "should raise an error if the VM is not found" do
expect { instance.machine("i-definitely-dont-exist", :virtualbox) }.
to raise_error(Vagrant::Errors::MachineNotFound)
end
it "should raise an error if the provider is not found" do
expect { instance.machine(:default, :lol_no) }.
to raise_error(Vagrant::Errors::ProviderNotFound)
end
end
describe "active machines" do
it "should be empty if the machines folder doesn't exist" do
folder = instance.local_data_path.join("machines")
@ -211,60 +633,6 @@ describe Vagrant::Environment do
end
end
describe "home path" do
it "is set to the home path given" do
Dir.mktmpdir do |dir|
instance = described_class.new(:home_path => dir)
instance.home_path.should == Pathname.new(dir)
end
end
it "is set to the environmental variable VAGRANT_HOME" do
Dir.mktmpdir do |dir|
instance = with_temp_env("VAGRANT_HOME" => dir) do
described_class.new
end
instance.home_path.should == Pathname.new(dir)
end
end
context "default home path" do
it "is set to '~/.vagrant.d' by default" do
expected = Vagrant::Util::Platform.fs_real_path("~/.vagrant.d")
described_class.new.home_path.should == expected
end
it "is set to '~/.vagrant.d' if on Windows but no USERPROFILE" do
Vagrant::Util::Platform.stub(:windows? => true)
expected = Vagrant::Util::Platform.fs_real_path("~/.vagrant.d")
with_temp_env("USERPROFILE" => nil) do
described_class.new.home_path.should == expected
end
end
it "is set to '%USERPROFILE%/.vagrant.d' if on Windows and USERPROFILE is set" do
Vagrant::Util::Platform.stub(:windows? => true)
Dir.mktmpdir do |dir|
expected = Vagrant::Util::Platform.fs_real_path("#{dir}/.vagrant.d")
with_temp_env("USERPROFILE" => dir) do
described_class.new.home_path.should == expected
end
end
end
end
it "throws an exception if inaccessible" do
expect {
described_class.new(:home_path => "/")
}.to raise_error(Vagrant::Errors::HomeDirectoryNotAccessible)
end
end
describe "local data path" do
it "is set to the proper default" do
default = instance.root_path.join(described_class::DEFAULT_LOCAL_DATA)
@ -429,7 +797,7 @@ Vagrant.configure("2") do |config|
end
VF
env.box2("base", :virtualbox)
env.box3("base", "1.0", :virtualbox)
end
env = environment.create_vagrant_env
@ -446,7 +814,7 @@ Vagrant.configure("2") do |config|
end
VF
env.box2("base", :virtualbox)
env.box3("base", "1.0", :virtualbox)
end
env = environment.create_vagrant_env
@ -519,305 +887,6 @@ VF
end
end
describe "getting a machine" do
# A helper to register a provider for use in tests.
def register_provider(name, config_class=nil, options=nil)
provider_cls = Class.new(Vagrant.plugin("2", :provider))
register_plugin("2") do |p|
p.provider(name, options) { provider_cls }
if config_class
p.config(name, :provider) { config_class }
end
end
provider_cls
end
it "should return a machine object with the correct provider" do
# Create a provider
foo_provider = register_provider("foo")
# Create the configuration
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
config.vm.define "foo"
end
VF
e.box2("base", :foo)
end
# Verify that we can get the machine
env = isolated_env.create_vagrant_env
machine = env.machine(:foo, :foo)
machine.should be_kind_of(Vagrant::Machine)
machine.name.should == :foo
machine.provider.should be_kind_of(foo_provider)
machine.provider_config.should be_nil
end
it "should return a machine object with the machine configuration" do
# Create a provider
foo_config = Class.new(Vagrant.plugin("2", :config)) do
attr_accessor :value
end
foo_provider = register_provider("foo", foo_config)
# Create the configuration
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
config.vm.define "foo"
config.vm.provider :foo do |fooconfig|
fooconfig.value = 100
end
end
VF
e.box2("base", :foo)
end
# Verify that we can get the machine
env = isolated_env.create_vagrant_env
machine = env.machine(:foo, :foo)
machine.should be_kind_of(Vagrant::Machine)
machine.name.should == :foo
machine.provider.should be_kind_of(foo_provider)
machine.provider_config.value.should == 100
end
it "should cache the machine objects by name and provider" do
# Create a provider
foo_provider = register_provider("foo")
bar_provider = register_provider("bar")
# Create the configuration
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
config.vm.define "vm1"
config.vm.define "vm2"
end
VF
e.box2("base", :foo)
e.box2("base", :bar)
end
env = isolated_env.create_vagrant_env
vm1_foo = env.machine(:vm1, :foo)
vm1_bar = env.machine(:vm1, :bar)
vm2_foo = env.machine(:vm2, :foo)
vm1_foo.should eql(env.machine(:vm1, :foo))
vm1_bar.should eql(env.machine(:vm1, :bar))
vm1_foo.should_not eql(vm1_bar)
vm2_foo.should eql(env.machine(:vm2, :foo))
end
it "should load a machine without a box" do
register_provider("foo")
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "i-dont-exist"
end
VF
end
env = environment.create_vagrant_env
machine = env.machine(:default, :foo)
machine.box.should be_nil
end
it "should load the machine configuration" do
register_provider("foo")
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 1
config.vm.box = "base"
config.vm.define "vm1" do |inner|
inner.ssh.port = 100
end
end
VF
env.box2("base", :foo)
end
env = environment.create_vagrant_env
machine = env.machine(:vm1, :foo)
machine.config.ssh.port.should == 100
machine.config.vm.box.should == "base"
end
it "should load the box configuration for a V2 box" do
register_provider("foo")
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
env.box2("base", :foo, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 100
end
VF
end
env = environment.create_vagrant_env
machine = env.machine(:default, :foo)
machine.config.ssh.port.should == 100
end
it "should load the box configuration for a V2 box and custom Vagrantfile name" do
register_provider("foo")
environment = isolated_environment do |env|
env.file("some_other_name", <<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
env.box2("base", :foo, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 100
end
VF
end
env = with_temp_env("VAGRANT_VAGRANTFILE" => "some_other_name") do
environment.create_vagrant_env
end
machine = env.machine(:default, :foo)
machine.config.ssh.port.should == 100
end
it "should load the box configuration for other formats for a V2 box" do
register_provider("foo", nil, box_format: "bar")
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
env.box2("base", :bar, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 100
end
VF
end
env = environment.create_vagrant_env
machine = env.machine(:default, :foo)
machine.config.ssh.port.should == 100
end
it "prefer sooner formats when multiple box formats are available" do
register_provider("foo", nil, box_format: ["fA", "fB"])
environment = isolated_environment do |env|
env.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
env.box2("base", :fA, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 100
end
VF
env.box2("base", :fB, :vagrantfile => <<-VF)
Vagrant.configure("2") do |config|
config.ssh.port = 200
end
VF
end
env = environment.create_vagrant_env
machine = env.machine(:default, :foo)
machine.config.ssh.port.should == 100
end
it "should load the provider override if set" do
register_provider("bar")
register_provider("foo")
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "foo"
config.vm.provider :foo do |_, c|
c.vm.box = "bar"
end
end
VF
end
env = isolated_env.create_vagrant_env
foo_vm = env.machine(:default, :foo)
bar_vm = env.machine(:default, :bar)
foo_vm.config.vm.box.should == "bar"
bar_vm.config.vm.box.should == "foo"
end
it "should reload the cache if refresh is set" do
# Create a provider
foo_provider = register_provider("foo")
# Create the configuration
isolated_env = isolated_environment do |e|
e.vagrantfile(<<-VF)
Vagrant.configure("2") do |config|
config.vm.box = "base"
end
VF
e.box2("base", :foo)
end
env = isolated_env.create_vagrant_env
vm1 = env.machine(:default, :foo)
vm2 = env.machine(:default, :foo, true)
vm3 = env.machine(:default, :foo)
vm1.should_not eql(vm2)
vm2.should eql(vm3)
end
it "should raise an error if the VM is not found" do
expect { instance.machine("i-definitely-dont-exist", :virtualbox) }.
to raise_error(Vagrant::Errors::MachineNotFound)
end
it "should raise an error if the provider is not found" do
expect { instance.machine(:default, :lol_no) }.
to raise_error(Vagrant::Errors::ProviderNotFound)
end
end
describe "getting machine names" do
it "should return the default machine if no multi-VM is used" do
# Create the config

View File

@ -22,6 +22,21 @@ describe Vagrant do
end
end
describe "#server_url" do
it "defaults to the default value" do
with_temp_env("VAGRANT_SERVER_URL" => nil) do
expect(subject.server_url).to eq(
Vagrant::DEFAULT_SERVER_URL)
end
end
it "is the VAGRANT_SERVER_URL value" do
with_temp_env("VAGRANT_SERVER_URL" => "foo") do
expect(subject.server_url).to eq("foo")
end
end
end
describe "#user_data_path" do
around do |example|
env = {

View File

@ -65,7 +65,7 @@ describe Vagrant::UI::Colored do
describe "#detail" do
it "colors output nothing by default" do
subject.should_receive(:safe_puts).with("foo", anything)
subject.should_receive(:safe_puts).with("\033[0mfoo\033[0m", anything)
subject.detail("foo")
end
@ -91,11 +91,16 @@ describe Vagrant::UI::Colored do
end
describe "#output" do
it "colors output nothing by default" do
subject.should_receive(:safe_puts).with("foo", anything)
it "colors output nothing by default, no bold" do
subject.should_receive(:safe_puts).with("\033[0mfoo\033[0m", anything)
subject.output("foo")
end
it "bolds output without color if specified" do
subject.should_receive(:safe_puts).with("\033[1mfoo\033[0m", anything)
subject.output("foo", bold: true)
end
it "colors output to color specified in global opts" do
subject.opts[:color] = :red
@ -232,6 +237,13 @@ describe Vagrant::UI::Prefixed do
subject { described_class.new(ui, prefix) }
describe "#ask" do
it "does not request bolding" do
ui.should_receive(:ask).with(" #{prefix}: foo", bold: false)
subject.ask("foo")
end
end
describe "#detail" do
it "prefixes with spaces and the message" do
ui.should_receive(:safe_puts).with(" #{prefix}: foo", anything)

View File

@ -54,4 +54,22 @@ describe Vagrant::Util::Downloader do
pending "tests for a UI"
end
end
describe "#head" do
let(:curl_options) {
["--fail", "--location", "--max-redirs", "10", "--user-agent", described_class::USER_AGENT, source, {}]
}
it "returns the output" do
subprocess_result.stub(stdout: "foo")
options = curl_options.dup
options.unshift("-I")
Vagrant::Util::Subprocess.should_receive(:execute).
with("curl", *options).and_return(subprocess_result)
expect(subject.head).to eq("foo")
end
end
end

23
website/docs/Vagrantfile vendored Normal file
View File

@ -0,0 +1,23 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
$script = <<SCRIPT
sudo apt-get -y update
sudo apt-get -y install curl
curl -sSL https://get.rvm.io | bash -s stable
. ~/.bashrc
. ~/.bash_profile
rvm install 2.0.0
rvm --default use 2.0.0
cd /vagrant
bundle
SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "precise64"
config.vm.network "private_network", ip: "33.33.33.10"
config.vm.provision "shell", inline: $script, privileged: false
end

View File

@ -139,6 +139,7 @@
<li<%= sidebar_current("boxes") %>><a href="/v2/boxes.html">Boxes</a></li>
<% if sidebar_section == "boxes" %>
<ul class="sub unstyled">
<li<%= sidebar_current("boxes-versioning") %>><a href="/v2/boxes/versioning.html">Box Versioning</a></li>
<li<%= sidebar_current("boxes-base") %>><a href="/v2/boxes/base.html">Creating a Base Box</a></li>
<li<%= sidebar_current("boxes-format") %>><a href="/v2/boxes/format.html">Box File Format</a></li>
</ul>

View File

@ -87,7 +87,7 @@ h6 {
}
p, td {
letter-spacing: 0.05em;
letter-spacing: normal;
line-height: 32px;
a {

View File

@ -5,74 +5,44 @@ sidebar_current: "boxes"
# Boxes
Boxes are the skeleton from which Vagrant machines are constructed. They are
portable files which can be used by others on any platform that runs Vagrant
to bring up a working environment.
Boxes are the package format for Vagrant environments. A box can be used by
anyone on any platform that Vagrant supports to bring up an identical
working environment.
The `vagrant box` utility provides all the functionality for managing
boxes. Boxes must currently be created manually.
boxes. You can read the documentation on the [vagrant box](/v2/cli/box.html)
command for more information.
Boxes are [provider-specific](/v2/providers/index.html), so you must obtain
the proper box depending on what provider you're using.
The easiest way to use a box is to add a box from the
[publicly available catalog of Vagrant boxes](http://www.vagrantcloud.com).
You can also add and share your own customized boxes on this website.
If you're interested in more details on the exact file format of
boxes, there is a separate [page dedicated to that](/v2/boxes/format.html), since
it is an advanced topic that general users don't need to know.
Boxes also support versioning so that members of your team using Vagrant
can update the underlying box easily, and the people who create boxes
can push fixes and communicate these fixes efficiently.
## Adding Boxes
You can learn all about boxes by reading this page as well as the
sub-pages in the navigation to the left.
Adding boxes is straightforward:
## Discovering Boxes
The easiest way to find boxes is to look on the
[public Vagrant box catalog](http://www.vagrantcloud.com)
for a box matching your use case. The catalog contains most major operating
systems as bases, as well as specialized boxes to get you up and running
quickly with LAMP stacks, Ruby, Python, etc.
The boxes on the public catalog work with many different
[providers](/v2/providers/index.html). Whether you're using Vagrant with
VirtualBox, VMware, AWS, etc. you should be able to find a box you need.
Adding a box from the catalog is very easy. Each box shows you instructions
with how to add it, but they all follow the same format:
```
$ vagrant box add name url
$ vagrant box add USER/BOX
...
```
`name` is a logical name by which the box is referenced from the
Vagrantfile. You can put anything you want here, but know that Vagrant
matches the `config.vm.box` directive with this name in order to look up
the box to use.
`url` is the location of the box. This can be a path to your local filesystem
or an HTTP URL to the box remotely.
Vagrant will automatically determine the provider the box was built
for by reading the "metadata.json" file within the box archive. You
may also tell Vagrant what provider the box is for by specifying the
`--provider` flag.
This is recommended as it adds an extra level of verification
to the box you're downloading. Additionally, Vagrant can exit early with
an error if a box with that name and provider already exists, whereas
it must download the entire box before showing such an error in the other
case.
Multiple boxes with the same name can exist as long as they are all
for different providers. The example of listing boxes below shows this,
where there are multiple precise64 boxes, backed by different providers.
This lets a single `config.vm.box` configuration within a Vagrantfile
properly reference boxes across providers.
## Listing Boxes
To view what boxes Vagrant has locally installed, use `vagrant box list`:
```
$ vagrant box list
precise64 (virtualbox)
precise64 (vmware_fusion)
```
Vagrant lists all boxes along with the providers the box is for in parentheses.
## Removing Boxes
Boxes can be removed just as easily as they are added:
```
$ vagrant box remove precise64 virtualbox
```
The two arguments are the logical name of the box and the provider of the
box. The second argument (the provider) is optional. If you have only a single
provider backing that box, it doesn't need to be specified. If you have multiple
providers backing a box and it isn't specified, Vagrant will show an error.
For example: `vagrant box add hashicorp/precise64`. You can also quickly
initialize a Vagrant environment with `vagrant init hashicorp/precise64`.

View File

@ -180,6 +180,15 @@ Packaging the box into a `box` file is provider-specific. Please refer to
the provider-specific documentation for creating a base box. Some
provider-specific guides are linked to towards the top of this page.
## Distributing the Box
You can distribute the box file however you'd like. However, if you want
to support versioning, putting multiple providers at a single URL, pushing
updates, analytics, and more, we recommend you add the box to
[Vagrant Cloud](http://www.vagrantcloud.com).
You can upload both public and private boxes to this service.
## Testing the Box
To test the box, pretend you're a new user of Vagrant and give it a shot:

View File

@ -6,21 +6,82 @@ sidebar_current: "boxes-format"
# Box File Format
In the past, boxes were just [tar files](http://en.wikipedia.org/wiki/Tar_\(computing\))
of VirtualBox exports. With Vagrant supporting multiple providers, box files
are now tar files where the contents differ for each provider. They are
still tar files, but they may now optionally be [gzipped](http://en.wikipedia.org/wiki/Gzip)
as well.
of VirtualBox exports. With Vagrant supporting multiple
[providers](/v2/providers/index.html) and [versioning](/v2/boxes/versioning.html)
now, box files are slightly more complicated.
Box files made for Vagrant 1.0.x and VirtualBox continue to work with
Vagrant 1.1+ and the VirtualBox provider.
Box files made for Vagrant 1.0.x (the VirtualBox export tar files) continue
to work with Vagrant today. When Vagrant encounters one of these old boxes,
it automatically updates it internally to the new format.
The only file required within a box is a "metadata.json" file. This is
a [JSON](http://www.json.org/) file that has a top-level object that
can contain any metadata about the box. Vagrant requires that a "provider"
key exists in this metadata with the name of the provider the box is made
for.
Today, box files are split into two different components:
The "metadata.json" file for a VirtualBox box:
* Box Metadata - This is a JSON document that specifies the name of the box,
a description, available versions, available providers, and URLs to the
actual box files (next component) for each provider and version. If this
metadata doesn't exist, a box file can still be added directly, but it
will not support versioning and updating.
* Box File - This is a compressed (tar, tar.gz, zip) file that is specific
to a single provider and can contain anything. Vagrant core doesn't ever
use the contents of this file. Instead, they are passed to the provider.
Therefore, a VirtualBox box file has different contents from a VMware
box file and so on.
Each component is covered in more detail below.
## Box Metadata
The metadata is an optional component for a box (but highly recommended)
that enables [versioning](/v2/boxes/versioning.html), updating, multiple
providers from a single file, and more.
<div class="alert alert-block alert-info">
<strong>You don't need to manually make the metadata.</strong> If you
have an account with <a href="#">Vagrant Cloud</a>, you
can create boxes there, and Vagrant Cloud automatically creates
the metadata for you. The format is still documented here.
</div>
It is a JSON document, structured in the following way:
```json
{
"name": "hashicorp/precise64",
"description": "This box contains Ubuntu 12.04 LTS 64-bit.",
"versions": [{
"version": "0.1.0",
"providers": [{
"name": "virtualbox",
"url": "http://somewhere.s3.com/precise64_010_virtualbox.box"
}]
}]
}
```
As you can see, the JSON document can describe multiple versions of a box,
multiple providers, and can add/remove providers in different versions.
This JSON file can be passed directly to `vagrant box add` and Vagrant
will install the proper version of the box. If multiple providers are
available, Vagrant will ask what provider you want to use.
## Box File
The actual box file is the required portion for Vagrant. It is recommended
you always use a metadata file alongside a box file, but direct box files
are supported for legacy reasons in Vagrant.
Box files are compressed using tar, tar.gz, or zip. The contents of the
archive can be anything, and is specific to each
[provider](/v2/providers/index.html). Vagrant core itself only unpacks
the boxes for use later.
Within the archive, Vagrant does expect a single file: "metadata.json".
This is a JSON file that is completely unrelated to the above "box metadata"
component. This file must contain at least the "provider" key with the
provider the box is for. For example, if your box was for VirtualBox,
the metadata.json would look like this:
```json
{

View File

@ -0,0 +1,95 @@
---
page_title: "Box Versioning"
sidebar_current: "boxes-versioning"
---
# Box Versioning
Since Vagrant 1.5, boxes support versioning. This allows the people who
make boxes to push updates to the box, and the people who use the box
have a simple workflow for checking for updates, updating their boxes,
and seeing what has changed.
If you're just getting started with Vagrant, box versioning isn't too
important, and we recommend learning about some other topics first. But
if you're using Vagrant on a team or plan on creating your own boxes,
versioning is very important. Luckily, having versioning built right in
to Vagrant makes it easy to use and fit nicely into the Vagrant workflow.
This page will cover how to use versioned boxes. It does _not_ cover how
to update your own custom boxes with versions. That is covered in
[creating a base box](/v2/boxes/base.html).
## Viewing Versions and Updating
You can view the versions a box has by calling `vagrant box info`. This
command will refresh the metadata associated with a box and list all
available versions for this box. `vagrant box list` only shows _installed_
versions of boxes.
You can check if the box you're using is outdated with `vagrant box outdated`.
This can check if the box in your current Vagrant environment is outdated
as well as any other box installed on the system.
Finally, you can update boxes with `vagrant box update`. This will download
and install the new box. This _will not_ magically update running Vagrant
environments. If a Vagrant environment is already running, you'll have to
destroy and recreate it to acquire the new updates in the box. The update
command just downloads these updates locally.
## Version Constraints
You can constrain a Vagrant environment to a specific version or versions
of a box using the [Vagrantfile](/v2/vagrantfile/index.html) by specifying
the `config.vm.box_version` option.
If this option is not specified, the latest version is always used. This is
equivalent to specifying a constraint of ">= 0".
The box version configuration can be a specific version or a constraint of
versions. Constraints can be any combination of the following:
`= X`, `> X`, `< X`, `>= X`, `<= X`, `~> X`. You can combine multiple
constraints by separating them with commas. All the constraints should be
self explanatory except perhaps for `~>`, known as the "pessimistic constraint".
Examples explain it best: `~> 1.0` is equivalent to `>= 1.0, < 2.0`. And
`~> 1.1.5` is equivalent to `>= 1.1.5, < 1.2.0`.
You can choose to handle versions however you see fit. However, many boxes
in the public catalog follow [semantic versioning](http://semver.org/).
Basically, only the first number (the "major version") breaks backwards
compatibility. In terms of Vagrant boxes, this means that any software that
runs in version "1.1.5" of a box should work in "1.2" and "1.4.5" and so on,
but "2.0" might introduce big changes that break your software. By following
this convention, the best constraint is `~> 1.0` because you know it is safe
no matter what version is in that range.
Of course, you're free to use versions however you'd like!
## Automatic Update Checking
Using the [Vagrantfile](/v2/vagrantfile/index.html), you can also configure
Vagrant to automatically check for updates during any `vagrant up`. This is
disabled by default, but can easily be enabled with
`config.vm.box_check_update = true` in your Vagrantfile.
When this is enabled, Vagrant will check for updates on every `vagrant up`,
not just when the machine is being created from scratch, but also when it
is resuming, starting after being halted, etc.
If an update is found, Vagrant will output a warning to the user letting
them know an update is available. That user can choose to ignore the warning
for now, or can update the box by running `vagrant box update`.
Vagrant can not and does not automatically download the updated box and
update the machine because boxes can be relatively large and updating the
machine requires destroying it and recreating it, which can cause important
data to be lost. Therefore, this process is manual to the extent that the
user has to manually enter a command to do it.
## Pruning Old Versions
Vagrant does not automatically prune old versions because it doesn't know
if they might be in use by other Vagrant environments. Because boxes can
be large, you may want to actively prune them once in awhile using
`vagrant box remove`. You can see all the boxes that are installed
using `vagrant box list`.

View File

@ -13,23 +13,28 @@ The main functionality of this command is exposed via even more subcommands:
* `add`
* `list`
* `outdated`
* `remove`
* `repackage`
* `update`
# Box Add
**Command: `vagrant box add NAME URL`**
**Command: `vagrant box add ADDRESS`**
This adds a box at the given URL to Vagrant and stores it under the
logical name `NAME`.
This adds a box with the given address to Vagrant. The address can be
one of three things:
The URL may be a file path or an HTTP URL. For HTTP, basic authentication
is supported and `http_proxy` environmental variables are respected. HTTPS
is also supported.
* A shorthand name from the
[public catalog of available Vagrant images](http://www.vagrantcloud.com),
such as "hashicorp/precise64".
The name argument of this command is a _logical name_, meaning you can
effectively choose whatever you want. This is the name that Vagrant searches
for to match with the `config.vm.box` setting in Vagrantfiles.
* File path or HTTP URL to a box in a [catalog](http://www.vagrantcloud.com).
For HTTP, basic authentication is supported and `http_proxy` environmental
variables are respected. HTTPS is also supported.
* URL directly a box file. In this case, you must specify a `--name` flag
(see below) and versioning/updates won't work.
If an error occurs during the download or the download is interrupted with
a Ctrl-C, then Vagrant will attempt to resume the download the next time it
@ -38,21 +43,17 @@ after the initial download.
## Options
* `--box-version VALUE` - The version of the box you want to add. By default,
the latest version will be added. The value of this can be an exact version
number such as "1.2.3" or it can be a set of version constraints. A version
constraint looks like ">= 1.0, < 2.0".
* `--cacert CERTFILE` - The certificate for the CA used to verify the peer.
This should be used if the remote end doesn't use a standard root CA.
* `--cert CERTFILE` - A client certificate to use when downloading the box, if
necessary.
* `--checksum VALUE` - A checksum for the box that is downloaded. If specified,
Vagrant will compare this checksum to what is actually downloaded and will
error if the checksums do not match. This is highly recommended since
box files are so large. If this is specified, `--checksum-type` must
also be specified.
* `--checksum-type TYPE` - The type of checksum that `--checksum` is if it
is specified. Supported values are currently "md5", "sha1", and "sha256".
* `--clean` - If given, Vagrant will remove any old temporary files from
prior downloads of the same URL. This is useful if you don't want Vagrant
to resume a download from a previous point, perhaps because the contents
@ -68,23 +69,66 @@ after the initial download.
adding is for the given provider. By default, Vagrant automatically
detects the proper provider to use.
## Options for direct box files
The options below only apply if you're adding a box file directly (when
you're not using a catalog).
* `--checksum VALUE` - A checksum for the box that is downloaded. If specified,
Vagrant will compare this checksum to what is actually downloaded and will
error if the checksums do not match. This is highly recommended since
box files are so large. If this is specified, `--checksum-type` must
also be specified. If you're downloading from a catalog, the checksum is
included within the catalog entry.
* `--checksum-type TYPE` - The type of checksum that `--checksum` is if it
is specified. Supported values are currently "md5", "sha1", and "sha256".
* `--name VALUE` - Logical name for the box. This is the value that you
would put into `config.vm.box` in your Vagrantfile. When adding a box from
a catalog, the name is included in the catalog entry and doesn't have
to be specified.
# Box List
**Command: `vagrant box list`**
This command lists all the boxes that are installed into Vagrant.
# Box Outdated
**Command: `vagrant box outdated`**
This command tells you whether or not the box you're using in
your current Vagrant environment is outdated. If the `--global` flag
is present, every installed box will be checked for updates.
Checking for updates involves refreshing the metadata associated with
a box. This generally requires an internet connection.
## Options
* `--box-info` - If given, Vagrant will display the URL from where the box
has been downloaded and the date it was added
* `--global` - Check for updates for all installed boxes, not just the
boxes for the current Vagrant environment.
# Box Remove
**Command: `vagrant box remove NAME PROVIDER`**
**Command: `vagrant box remove NAME`**
This command removes a box from Vagrant that matches the given name and
provider.
This command removes a box from Vagrant that matches the given name.
If a box has multiple providers, the exact provider must be specified
with the `--provider` flag. If a box has multiple versions, you can select
what versions to delete with the `--box-version` flag.
## Options
* `--box-version VALUE` - Version of version constraints of the boxes to
remove. See documentation on this flag for `box add` for more details.
* `--provider VALUE` - The provider-specific box to remove with the given
name. This is only required if a box is backed by multiple providers.
If there is only a single provider, Vagrant will default to removing it.
# Box Repackage
@ -96,3 +140,28 @@ directory so you can redistribute it.
When you add a box, Vagrant unpacks it and stores it internally. The
original `*.box` file is not preserved. This command is useful for
reclaiming a `*.box` file from an installed Vagrant box.
# Box Update
**Command: `vagrant box update`**
This command updates the box for the current Vagrant environment if there
are updates available. The command can also update a specific box (outside
of an active Vagrant environment), by specifying the `--box` flag.
Note that updating the box will not update an already-running Vagrant
machine. To reflect the changes in the box, you'll have to destroy and
bring back up the Vagrant machine.
If you just want to check if there are updates available, use the
`vagrant box outdated` command.
## Options
* `--box VALUE` - Name of a specific box to update. If this flag is not
specified, Vagrant will update the boxes for the active Vagrant
environment.
* `--provider VALUE` - When `--box` is present, this controls what
provider-specific box to update. This is not required unless the box has
multiple providers. Without the `--box` flag, this has no effect.

View File

@ -18,8 +18,18 @@ for the machine to boot and be accessible. By default this is 300 seconds.
<hr>
`config.vm.box` - This configures what [box](/v2/boxes.html) the
machine will be brought up against. The value here should match one of
the installed boxes on the system.
machine will be brought up against. The value here should be the name
of an installed box or a shorthand name of a box in
[Vagrant Cloud](http://www.vagrantcloud.com).
<hr>
`config.vm.box_check_update` - If true, Vagrant will check for updates to
the configured box on every `vagrant up`. If an update is found, Vagrant
will tell the user. By default this is true. Updates will only be checked
for boxes that properly support updates (boxes from
[Vagrant Cloud](http://www.vagrantcloud.com)
or some other versioned box).
<hr>
@ -53,8 +63,10 @@ URL, then SSL certs will be verified.
<hr>
`config.vm.box_url` - The URL that the configured box can be found at.
If the box is not installed on the system, it will be retrieved from this
URL when `vagrant up` is run.
If `config.vm.box` is a shorthand to a box in [Vagrant Cloud](http://www.vagrantcloud.com)
then this value doesn't need to be specified. Otherwise, it should
point to the proper place where the box can be found if it isn't
installed.
This can also be an array of multiple URLs. The URLs will be tried in
order. Note that any client certificates, insecure download settings, and
@ -62,6 +74,14 @@ so on will apply to all URLs in this list.
<hr>
`config.vm.box_version` - The version of the box to use. This defaults to
">= 0" (the latest version available). This can contain an arbitrary list
of constraints, separated by commas, such as: `>= 1.0, < 1.5`. When constraints
are given, Vagrant will use the latest available box satisfying these
constraints.
<hr>
`config.vm.graceful_halt_timeout` - The time in seconds that Vagrant will
wait for the machine to gracefully halt when `vagrant halt` is called.
Defaults to 300 seconds.