core: BoxMetadata can read the JSON description

This commit is contained in:
Mitchell Hashimoto 2014-01-22 20:17:30 -08:00
parent e8197c4e87
commit 8abcc6e5f2
4 changed files with 273 additions and 0 deletions

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

@ -0,0 +1,124 @@
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
@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)
requirements = version.split(",").map do |v|
Gem::Requirement.new(v.strip)
end
@version_map.keys.sort.reverse.each do |v|
if requirements.all? { |r| r.satisfied_by?(v) }
return Version.new(@version_map[v])
end
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"], 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]
return nil if !p
Provider.new(p)
end
# Returns the providers that are available for this version
# of the box.
#
# @return [Provider]
def providers
@provider_map.keys
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

@ -144,6 +144,10 @@ 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

View File

@ -266,6 +266,12 @@ en:
box file format can be found at the URL below:
http://docs.vagrantup.com/v2/boxes/format.html
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: Box '%{name}' with '%{provider}' provider could not be found.
box_provider_doesnt_match: |-
The box you attempted to add doesn't match the provider you specified.

View File

@ -0,0 +1,139 @@
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"
},
{
"version": "1.1.5"
},
{
"version": "1.1.0"
}
]
}
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
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