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 autoload :Remote, "vagrant/box_metadata/remote" # 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| begin [Gem::Version.new(v["version"]), v] rescue ArgumentError raise Errors::BoxMetadataMalformedVersion, version: v["version"].to_s end end @version_map = Hash[@version_map] 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. Optionally filter versions by a matching # provider. # # @return[Array] def versions(**opts) provider = nil provider = opts[:provider].to_sym if opts[:provider] if provider @version_map.select do |version, raw| if raw["providers"] raw["providers"].detect do |p| p["name"].to_sym == provider end end end.keys.sort.map(&:to_s) else @version_map.keys.sort.map(&:to_s) end 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] 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 # The checksum value for this box, if any. # # @return [String] attr_accessor :checksum # The type of checksum (if any) associated with this provider. # # @return [String] attr_accessor :checksum_type def initialize(raw, **_) @name = raw["name"] @url = raw["url"] @checksum = raw["checksum"] @checksum_type = raw["checksum_type"] end end end end