586 lines
16 KiB
Ruby
586 lines
16 KiB
Ruby
# Copyright (c) HashiCorp, Inc.
|
|
# SPDX-License-Identifier: BUSL-1.1
|
|
|
|
require File.expand_path("../../base", __FILE__)
|
|
|
|
require "pathname"
|
|
require "stringio"
|
|
require "tempfile"
|
|
|
|
require "vagrant/box_metadata"
|
|
|
|
describe Vagrant::Box, :skip_windows do
|
|
include_context "unit"
|
|
|
|
let(:environment) { isolated_environment }
|
|
|
|
let(:box_collection) { Vagrant::BoxCollection.new(environment.boxes_dir) }
|
|
|
|
let(:name) { "foo" }
|
|
let(:provider) { :virtualbox }
|
|
let(:version) { "1.0" }
|
|
let(:architecture) { "test-architecture" }
|
|
let(:directory) { environment.box3("foo", "1.0", :virtualbox) }
|
|
subject { described_class.new(name, provider, version, directory) }
|
|
|
|
describe '#metadata_url' do
|
|
subject { super().metadata_url }
|
|
it { should be_nil }
|
|
end
|
|
|
|
it "provides the name" do
|
|
expect(subject.name).to eq(name)
|
|
end
|
|
|
|
it "provides the provider" do
|
|
expect(subject.provider).to eq(provider)
|
|
end
|
|
|
|
it "provides the directory" do
|
|
expect(subject.directory).to eq(directory)
|
|
end
|
|
|
|
it "provides the metadata associated with a box" do
|
|
data = { "foo" => "bar" }
|
|
|
|
# Write the metadata
|
|
directory.join("metadata.json").open("w") do |f|
|
|
f.write(JSON.generate(data))
|
|
end
|
|
|
|
# Verify the metadata
|
|
expect { subject.metadata }.
|
|
to raise_error(Vagrant::Errors::BoxMetadataMissingRequiredFields)
|
|
end
|
|
|
|
it "provides the metadata associated with a box" do
|
|
data = { "provider" => "bar" }
|
|
|
|
# Write the metadata
|
|
directory.join("metadata.json").open("w") do |f|
|
|
f.write(JSON.generate(data))
|
|
end
|
|
|
|
# Verify the metadata
|
|
expect(subject.metadata).to eq(data)
|
|
end
|
|
|
|
context "with a metadata URL" do
|
|
subject do
|
|
described_class.new(
|
|
name, provider, version, directory,
|
|
metadata_url: "foo")
|
|
end
|
|
|
|
describe '#metadata_url' do
|
|
subject { super().metadata_url }
|
|
it { should eq("foo") }
|
|
end
|
|
end
|
|
|
|
context "with a corrupt metadata file" do
|
|
before do
|
|
directory.join("metadata.json").open("w") do |f|
|
|
f.write("")
|
|
end
|
|
end
|
|
|
|
it "should raise an exception" do
|
|
expect { subject }.
|
|
to raise_error(Vagrant::Errors::BoxMetadataCorrupted)
|
|
end
|
|
end
|
|
|
|
context "without a metadata file" do
|
|
before :each do
|
|
directory.join("metadata.json").delete
|
|
end
|
|
|
|
it "should raise an exception" do
|
|
expect { subject }.
|
|
to raise_error(Vagrant::Errors::BoxMetadataFileNotFound)
|
|
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
|
|
|
|
allow(subject).to receive(:load_metadata).and_return(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
|
|
|
|
allow(subject).to receive(:load_metadata).and_return(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
|
|
|
|
allow(subject).to receive(:load_metadata).and_return(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
|
|
|
|
context "with architecture" do
|
|
subject do
|
|
described_class.new(
|
|
name, provider, version, directory,
|
|
architecture: architecture,
|
|
metadata_url: "foo"
|
|
)
|
|
end
|
|
|
|
it "raises an exception if no metadata_url is set" do
|
|
subject = described_class.new(
|
|
name, provider, version, directory,
|
|
architecture: architecture,
|
|
)
|
|
|
|
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(
|
|
{
|
|
name: "foo",
|
|
versions: [ { version: "1.0" } ]
|
|
}.to_json
|
|
)
|
|
allow(subject).to receive(:load_metadata).and_return(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(
|
|
{
|
|
name: "foo",
|
|
versions: [
|
|
{version: "1.0"},
|
|
{
|
|
version: "1.1",
|
|
providers: [
|
|
{
|
|
name: "virtualbox",
|
|
url: "bar",
|
|
architecture: architecture,
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}.to_json
|
|
)
|
|
|
|
allow(subject).to receive(:load_metadata).and_return(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 nil if update does not support architecture" do
|
|
metadata = Vagrant::BoxMetadata.new(
|
|
{
|
|
name: "foo",
|
|
versions: [
|
|
{version: "1.0"},
|
|
{
|
|
version: "1.1",
|
|
providers: [
|
|
{
|
|
name: "virtualbox",
|
|
url: "bar",
|
|
architecture: "other-architecture",
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}.to_json
|
|
)
|
|
|
|
allow(subject).to receive(:load_metadata).and_return(metadata)
|
|
|
|
result = subject.has_update?
|
|
expect(result).to be_nil
|
|
end
|
|
|
|
it "returns the updated box info within constraints" do
|
|
metadata = Vagrant::BoxMetadata.new(
|
|
{
|
|
name: "foo",
|
|
versions: [
|
|
{
|
|
version: "1.0",
|
|
},
|
|
{
|
|
version: "1.1",
|
|
providers: [
|
|
{
|
|
name: "virtualbox",
|
|
url: "bar",
|
|
architecture: architecture
|
|
},
|
|
]
|
|
},
|
|
{
|
|
version: "1.2",
|
|
providers: [
|
|
{
|
|
name: "virtualbox",
|
|
url: "bar",
|
|
architecture: "other-architecture",
|
|
},
|
|
]
|
|
},
|
|
{
|
|
version: "1.4",
|
|
providers: [
|
|
{
|
|
name: "virtualbox",
|
|
url: "bar",
|
|
architecture: architecture
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}.to_json
|
|
)
|
|
|
|
allow(subject).to receive(:load_metadata).and_return(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
|
|
end
|
|
|
|
context "#automatic_update_check_allowed?" do
|
|
it "should return true on intial check" do
|
|
expect(subject.automatic_update_check_allowed?).to be_truthy
|
|
end
|
|
|
|
it "should return false on second check" do
|
|
expect(subject.automatic_update_check_allowed?).to be_truthy
|
|
expect(subject.automatic_update_check_allowed?).to be_falsey
|
|
end
|
|
|
|
it "should use a file to mark last check time" do
|
|
expect(FileUtils).to receive(:touch)
|
|
subject.automatic_update_check_allowed?
|
|
end
|
|
|
|
it "should return true when time since last check is greater than check interval" do
|
|
subject.automatic_update_check_allowed?
|
|
stub_const("Vagrant::Box::BOX_UPDATE_CHECK_INTERVAL", -1)
|
|
expect(subject.automatic_update_check_allowed?).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "#in_use?" do
|
|
let(:index) { [] }
|
|
|
|
def new_entry(name, provider, version, architecture=nil)
|
|
Vagrant::MachineIndex::Entry.new.tap do |entry|
|
|
entry.extra_data["box"] = {
|
|
"name" => name,
|
|
"provider" => provider,
|
|
"version" => version,
|
|
"architecture" => architecture,
|
|
}
|
|
end
|
|
end
|
|
|
|
it "returns nil if the index has no matching entries" do
|
|
index << new_entry("foo", "bar", "1.0")
|
|
index << new_entry("foo", "baz", "1.2")
|
|
|
|
expect(subject).to_not be_in_use(index)
|
|
end
|
|
|
|
it "returns matching entries if they exist" do
|
|
matching = new_entry(name, provider.to_s, version)
|
|
index << new_entry("foo", "bar", "1.0")
|
|
index << matching
|
|
index << new_entry("foo", "baz", "1.2")
|
|
|
|
expect(subject.in_use?(index)).to eq([matching])
|
|
end
|
|
|
|
context "with architecture information" do
|
|
subject { described_class.new(name, provider, version, directory, architecture: architecture) }
|
|
|
|
it "returns nil if the index has no matching entries" do
|
|
index << new_entry("foo", "bar", "1.0", "amd64")
|
|
index << new_entry("foo", "baz", "1.2", "arm64")
|
|
index << new_entry(name, provider.to_s, version, "random-arch")
|
|
|
|
expect(subject).to_not be_in_use(index)
|
|
end
|
|
|
|
it "returns matching entries if they exist" do
|
|
matching = new_entry(name, provider.to_s, version, architecture)
|
|
index << new_entry("foo", "bar", "1.0", "amd64")
|
|
index << matching
|
|
index << new_entry("foo", "baz", "1.2")
|
|
|
|
expect(subject.in_use?(index)).to eq([matching])
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#load_metadata" do
|
|
let(:metadata_url) do
|
|
Tempfile.new("vagrant-test-box-test").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
|
|
|
|
after do
|
|
metadata_url.unlink
|
|
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
|
|
|
|
it "raises an error if the download failed" do
|
|
dl = double("downloader")
|
|
allow(Vagrant::Util::Downloader).to receive(:new).and_return(dl)
|
|
expect(dl).to receive(:download!).and_raise(
|
|
Vagrant::Errors::DownloaderError.new(message: "foo"))
|
|
|
|
expect { subject.load_metadata }.
|
|
to raise_error(Vagrant::Errors::BoxMetadataDownloadError)
|
|
end
|
|
|
|
context "box has a hook for adding authentication" do
|
|
|
|
let(:hook){ double("hook") }
|
|
|
|
subject do
|
|
described_class.new(
|
|
name, provider, version, directory,
|
|
metadata_url: metadata_url.path, hook: hook)
|
|
end
|
|
|
|
it "add authentication headers to the url" do
|
|
expect(hook).to receive(:call).with(:authenticate_box_downloader, any_args)
|
|
result = subject.load_metadata
|
|
expect(result.name).to eq("foo")
|
|
expect(result.description).to eq("bar")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "destroying" do
|
|
it "should destroy an existing box" do
|
|
# Verify that our "box" exists
|
|
expect(directory.exist?).to be
|
|
|
|
# Destroy it
|
|
expect(subject.destroy!).to be
|
|
|
|
# Verify that it is "destroyed"
|
|
expect(directory.exist?).not_to be
|
|
end
|
|
|
|
it "should not error destroying a non-existent box" do
|
|
# Get the subject so that it is instantiated
|
|
box = subject
|
|
|
|
# Delete the directory
|
|
directory.rmtree
|
|
|
|
# Destroy it
|
|
expect(box.destroy!).to be
|
|
end
|
|
end
|
|
|
|
describe "repackaging" do
|
|
let(:scratch) { Dir.mktmpdir("vagrant-test-box-repackaging") }
|
|
|
|
let(:box_output_path) { File.join(scratch, "package.box") }
|
|
|
|
after do
|
|
FileUtils.rm_rf(scratch)
|
|
end
|
|
|
|
it "should repackage the box", :bsdtar do
|
|
test_file_contents = "hello, world!"
|
|
|
|
# Put a file in the box directory to verify it is packaged properly
|
|
# later.
|
|
directory.join("test_file").open("w") do |f|
|
|
f.write(test_file_contents)
|
|
end
|
|
|
|
# Repackage our box to some temporary directory
|
|
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", "1.0")
|
|
expect(new_box.directory.join("test_file").read).to eq(test_file_contents)
|
|
end
|
|
end
|
|
|
|
describe "comparison and ordering" do
|
|
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)
|
|
|
|
expect(a).to eq(b)
|
|
end
|
|
|
|
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)
|
|
|
|
expect(a).to_not eq(b)
|
|
end
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
expect([d, c, a, b].sort).to eq([a, b, c, d])
|
|
end
|
|
end
|
|
end
|