core: BoxAdd can add from metadata, version constraints, more
This commit is contained in:
parent
15aa91b073
commit
7d44cd61c9
|
@ -1,6 +1,7 @@
|
||||||
require "digest/sha1"
|
require "digest/sha1"
|
||||||
require "log4r"
|
require "log4r"
|
||||||
|
|
||||||
|
require "vagrant/box_metadata"
|
||||||
require "vagrant/util/downloader"
|
require "vagrant/util/downloader"
|
||||||
require "vagrant/util/file_checksum"
|
require "vagrant/util/file_checksum"
|
||||||
require "vagrant/util/platform"
|
require "vagrant/util/platform"
|
||||||
|
@ -19,6 +20,120 @@ module Vagrant
|
||||||
def call(env)
|
def call(env)
|
||||||
@download_interrupted = false
|
@download_interrupted = false
|
||||||
|
|
||||||
|
provider = env[:box_provider]
|
||||||
|
url = env[:box_url]
|
||||||
|
version = env[:box_version]
|
||||||
|
|
||||||
|
metadata = nil
|
||||||
|
if File.file?(url)
|
||||||
|
# TODO: What if file isn't valid JSON
|
||||||
|
# TODO: What if file is old-style box
|
||||||
|
# TODO: What if URL is in the "file:" format
|
||||||
|
File.open(url) do |f|
|
||||||
|
metadata = BoxMetadata.new(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: provider that is in an earlier version
|
||||||
|
# TODO: multiple providers
|
||||||
|
metadata_version = metadata.version(version || ">= 0")
|
||||||
|
if !metadata_version
|
||||||
|
raise Errors::BoxAddNoMatchingVersion,
|
||||||
|
constraints: version || ">= 0",
|
||||||
|
url: url,
|
||||||
|
versions: metadata.versions.join(", ")
|
||||||
|
end
|
||||||
|
|
||||||
|
metadata_provider = nil
|
||||||
|
if provider
|
||||||
|
# If a provider was specified, make sure we get that specific
|
||||||
|
# version.
|
||||||
|
metadata_provider = metadata_version.provider(provider)
|
||||||
|
if !metadata_provider
|
||||||
|
raise Errors::BoxAddNoMatchingProvider,
|
||||||
|
providers: metadata_version.providers.join(", "),
|
||||||
|
requested: provider,
|
||||||
|
url: url
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Now we have a URL, we have to download this URL.
|
||||||
|
box_url = download(metadata_provider.url, env)
|
||||||
|
|
||||||
|
# Add the box!
|
||||||
|
env[:box_collection].add(
|
||||||
|
box_url, metadata.name, metadata_version.version)
|
||||||
|
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
def download(url, env)
|
||||||
|
temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url))
|
||||||
|
@logger.info("Downloading box: #{url} => #{temp_path}")
|
||||||
|
|
||||||
|
if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i
|
||||||
|
@logger.info("URL is a file or protocol not found and assuming file.")
|
||||||
|
file_path = File.expand_path(url)
|
||||||
|
file_path = Util::Platform.cygwin_windows_path(file_path)
|
||||||
|
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?
|
||||||
|
delete = false
|
||||||
|
if env[:box_clean]
|
||||||
|
@logger.info("Cleaning existing temp box file.")
|
||||||
|
delete = true
|
||||||
|
elsif temp_path.mtime.to_i < (Time.now.to_i - 6 * 60 * 60)
|
||||||
|
@logger.info("Existing temp file is too old. Removing.")
|
||||||
|
delete = true
|
||||||
|
end
|
||||||
|
|
||||||
|
temp_path.unlink if delete
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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"))
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
downloader = Util::Downloader.new(url, temp_path, downloader_options)
|
||||||
|
downloader.download!
|
||||||
|
rescue Errors::DownloaderInterrupted
|
||||||
|
# The downloader was interrupted, so just return, because that
|
||||||
|
# means we were interrupted as well.
|
||||||
|
@download_interrupted = true
|
||||||
|
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?
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
|
temp_path
|
||||||
|
end
|
||||||
|
=begin
|
||||||
|
@download_interrupted = false
|
||||||
|
|
||||||
box_name = env[:box_name]
|
box_name = env[:box_name]
|
||||||
box_formats = env[:box_provider]
|
box_formats = env[:box_provider]
|
||||||
if box_formats
|
if box_formats
|
||||||
|
@ -209,6 +324,7 @@ module Vagrant
|
||||||
f.write(JSON.dump(info))
|
f.write(JSON.dump(info))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
=end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -120,6 +120,14 @@ module Vagrant
|
||||||
error_key(:batch_multi_error)
|
error_key(:batch_multi_error)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class BoxAddNoMatchingProvider < VagrantError
|
||||||
|
error_key(:box_add_no_matching_provider)
|
||||||
|
end
|
||||||
|
|
||||||
|
class BoxAddNoMatchingVersion < VagrantError
|
||||||
|
error_key(:box_add_no_matching_version)
|
||||||
|
end
|
||||||
|
|
||||||
class BoxAlreadyExists < VagrantError
|
class BoxAlreadyExists < VagrantError
|
||||||
error_key(:already_exists, "vagrant.actions.box.unpackage")
|
error_key(:already_exists, "vagrant.actions.box.unpackage")
|
||||||
end
|
end
|
||||||
|
|
|
@ -211,6 +211,23 @@ en:
|
||||||
Any errors that occurred are shown below.
|
Any errors that occurred are shown below.
|
||||||
|
|
||||||
%{message}
|
%{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.
|
||||||
|
|
||||||
|
Box: %{url}
|
||||||
|
Requested provider: %{requested}
|
||||||
|
Available providers: %{providers}
|
||||||
|
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: %{url}
|
||||||
|
Constraints: %{constraints}
|
||||||
|
Available versions: %{versions}
|
||||||
boot_bad_state: |-
|
boot_bad_state: |-
|
||||||
The guest machine entered an invalid state while waiting for it
|
The guest machine entered an invalid state while waiting for it
|
||||||
to boot. Valid states are '%{valid}'. The machine is in the
|
to boot. Valid states are '%{valid}'. The machine is in the
|
||||||
|
|
|
@ -1,8 +1,256 @@
|
||||||
|
require "digest/sha1"
|
||||||
|
require "pathname"
|
||||||
|
require "tempfile"
|
||||||
|
require "tmpdir"
|
||||||
|
|
||||||
require File.expand_path("../../../../base", __FILE__)
|
require File.expand_path("../../../../base", __FILE__)
|
||||||
|
|
||||||
|
require "vagrant/util/file_checksum"
|
||||||
|
|
||||||
describe Vagrant::Action::Builtin::BoxAdd do
|
describe Vagrant::Action::Builtin::BoxAdd do
|
||||||
|
include_context "unit"
|
||||||
|
|
||||||
let(:app) { lambda { |env| } }
|
let(:app) { lambda { |env| } }
|
||||||
let(:env) { {} }
|
let(:env) { {
|
||||||
|
box_collection: box_collection,
|
||||||
|
tmp_path: Pathname.new(Dir.mktmpdir),
|
||||||
|
ui: Vagrant::UI::Silent.new,
|
||||||
|
} }
|
||||||
|
|
||||||
subject { described_class.new(app, env) }
|
subject { described_class.new(app, env) }
|
||||||
|
|
||||||
|
let(:box_collection) { double("box_collection") }
|
||||||
|
let(:iso_env) { isolated_environment }
|
||||||
|
|
||||||
|
# Helper to quickly SHA1 checksum a path
|
||||||
|
def checksum(path)
|
||||||
|
FileChecksum.new(path, Digest::SHA1).checksum
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with box metadata" do
|
||||||
|
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|
|
||||||
|
expect(checksum(path)).to eq(checksum(box_path))
|
||||||
|
expect(name).to eq("foo/bar")
|
||||||
|
expect(version).to eq("0.7")
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
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|
|
||||||
|
expect(checksum(path)).to eq(checksum(box_path))
|
||||||
|
expect(name).to eq("foo/bar")
|
||||||
|
expect(version).to eq("0.7")
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
app.should_receive(:call).with(env)
|
||||||
|
|
||||||
|
subject.call(env)
|
||||||
|
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|
|
||||||
|
expect(checksum(path)).to eq(checksum(box_path))
|
||||||
|
expect(name).to eq("foo/bar")
|
||||||
|
expect(version).to eq("0.5")
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
app.should_receive(:call).with(env)
|
||||||
|
|
||||||
|
subject.call(env)
|
||||||
|
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|
|
||||||
|
expect(checksum(path)).to eq(checksum(box_path))
|
||||||
|
expect(name).to eq("foo/bar")
|
||||||
|
expect(version).to eq("0.5")
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
app.should_receive(:call).with(env)
|
||||||
|
|
||||||
|
subject.call(env)
|
||||||
|
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
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue