123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- package manifestlist
- import (
- "encoding/json"
- "errors"
- "fmt"
- "github.com/docker/distribution"
- "github.com/docker/distribution/manifest"
- "github.com/opencontainers/go-digest"
- v1 "github.com/opencontainers/image-spec/specs-go/v1"
- )
- const (
- // MediaTypeManifestList specifies the mediaType for manifest lists.
- MediaTypeManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
- )
- // SchemaVersion provides a pre-initialized version structure for this
- // packages version of the manifest.
- var SchemaVersion = manifest.Versioned{
- SchemaVersion: 2,
- MediaType: MediaTypeManifestList,
- }
- // OCISchemaVersion provides a pre-initialized version structure for this
- // packages OCIschema version of the manifest.
- var OCISchemaVersion = manifest.Versioned{
- SchemaVersion: 2,
- MediaType: v1.MediaTypeImageIndex,
- }
- func init() {
- manifestListFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
- m := new(DeserializedManifestList)
- err := m.UnmarshalJSON(b)
- if err != nil {
- return nil, distribution.Descriptor{}, err
- }
- if m.MediaType != MediaTypeManifestList {
- err = fmt.Errorf("mediaType in manifest list should be '%s' not '%s'",
- MediaTypeManifestList, m.MediaType)
- return nil, distribution.Descriptor{}, err
- }
- dgst := digest.FromBytes(b)
- return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err
- }
- err := distribution.RegisterManifestSchema(MediaTypeManifestList, manifestListFunc)
- if err != nil {
- panic(fmt.Sprintf("Unable to register manifest: %s", err))
- }
- imageIndexFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
- if err := validateIndex(b); err != nil {
- return nil, distribution.Descriptor{}, err
- }
- m := new(DeserializedManifestList)
- err := m.UnmarshalJSON(b)
- if err != nil {
- return nil, distribution.Descriptor{}, err
- }
- if m.MediaType != "" && m.MediaType != v1.MediaTypeImageIndex {
- err = fmt.Errorf("if present, mediaType in image index should be '%s' not '%s'",
- v1.MediaTypeImageIndex, m.MediaType)
- return nil, distribution.Descriptor{}, err
- }
- dgst := digest.FromBytes(b)
- return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeImageIndex}, err
- }
- err = distribution.RegisterManifestSchema(v1.MediaTypeImageIndex, imageIndexFunc)
- if err != nil {
- panic(fmt.Sprintf("Unable to register OCI Image Index: %s", err))
- }
- }
- // PlatformSpec specifies a platform where a particular image manifest is
- // applicable.
- type PlatformSpec struct {
- // Architecture field specifies the CPU architecture, for example
- // `amd64` or `ppc64`.
- Architecture string `json:"architecture"`
- // OS specifies the operating system, for example `linux` or `windows`.
- OS string `json:"os"`
- // OSVersion is an optional field specifying the operating system
- // version, for example `10.0.10586`.
- OSVersion string `json:"os.version,omitempty"`
- // OSFeatures is an optional field specifying an array of strings,
- // each listing a required OS feature (for example on Windows `win32k`).
- OSFeatures []string `json:"os.features,omitempty"`
- // Variant is an optional field specifying a variant of the CPU, for
- // example `ppc64le` to specify a little-endian version of a PowerPC CPU.
- Variant string `json:"variant,omitempty"`
- // Features is an optional field specifying an array of strings, each
- // listing a required CPU feature (for example `sse4` or `aes`).
- Features []string `json:"features,omitempty"`
- }
- // A ManifestDescriptor references a platform-specific manifest.
- type ManifestDescriptor struct {
- distribution.Descriptor
- // Platform specifies which platform the manifest pointed to by the
- // descriptor runs on.
- Platform PlatformSpec `json:"platform"`
- }
- // ManifestList references manifests for various platforms.
- type ManifestList struct {
- manifest.Versioned
- // Config references the image configuration as a blob.
- Manifests []ManifestDescriptor `json:"manifests"`
- }
- // References returns the distribution descriptors for the referenced image
- // manifests.
- func (m ManifestList) References() []distribution.Descriptor {
- dependencies := make([]distribution.Descriptor, len(m.Manifests))
- for i := range m.Manifests {
- dependencies[i] = m.Manifests[i].Descriptor
- }
- return dependencies
- }
- // DeserializedManifestList wraps ManifestList with a copy of the original
- // JSON.
- type DeserializedManifestList struct {
- ManifestList
- // canonical is the canonical byte representation of the Manifest.
- canonical []byte
- }
- // FromDescriptors takes a slice of descriptors, and returns a
- // DeserializedManifestList which contains the resulting manifest list
- // and its JSON representation.
- func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) {
- var mediaType string
- if len(descriptors) > 0 && descriptors[0].Descriptor.MediaType == v1.MediaTypeImageManifest {
- mediaType = v1.MediaTypeImageIndex
- } else {
- mediaType = MediaTypeManifestList
- }
- return FromDescriptorsWithMediaType(descriptors, mediaType)
- }
- // FromDescriptorsWithMediaType is for testing purposes, it's useful to be able to specify the media type explicitly
- func FromDescriptorsWithMediaType(descriptors []ManifestDescriptor, mediaType string) (*DeserializedManifestList, error) {
- m := ManifestList{
- Versioned: manifest.Versioned{
- SchemaVersion: 2,
- MediaType: mediaType,
- },
- }
- m.Manifests = make([]ManifestDescriptor, len(descriptors))
- copy(m.Manifests, descriptors)
- deserialized := DeserializedManifestList{
- ManifestList: m,
- }
- var err error
- deserialized.canonical, err = json.MarshalIndent(&m, "", " ")
- return &deserialized, err
- }
- // UnmarshalJSON populates a new ManifestList struct from JSON data.
- func (m *DeserializedManifestList) UnmarshalJSON(b []byte) error {
- m.canonical = make([]byte, len(b))
- // store manifest list in canonical
- copy(m.canonical, b)
- // Unmarshal canonical JSON into ManifestList object
- var manifestList ManifestList
- if err := json.Unmarshal(m.canonical, &manifestList); err != nil {
- return err
- }
- m.ManifestList = manifestList
- return nil
- }
- // MarshalJSON returns the contents of canonical. If canonical is empty,
- // marshals the inner contents.
- func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) {
- if len(m.canonical) > 0 {
- return m.canonical, nil
- }
- return nil, errors.New("JSON representation not initialized in DeserializedManifestList")
- }
- // Payload returns the raw content of the manifest list. The contents can be
- // used to calculate the content identifier.
- func (m DeserializedManifestList) Payload() (string, []byte, error) {
- var mediaType string
- if m.MediaType == "" {
- mediaType = v1.MediaTypeImageIndex
- } else {
- mediaType = m.MediaType
- }
- return mediaType, m.canonical, nil
- }
- // unknownDocument represents a manifest, manifest list, or index that has not
- // yet been validated
- type unknownDocument struct {
- Config interface{} `json:"config,omitempty"`
- Layers interface{} `json:"layers,omitempty"`
- }
- // validateIndex returns an error if the byte slice is invalid JSON or if it
- // contains fields that belong to a manifest
- func validateIndex(b []byte) error {
- var doc unknownDocument
- if err := json.Unmarshal(b, &doc); err != nil {
- return err
- }
- if doc.Config != nil || doc.Layers != nil {
- return errors.New("index: expected index but found manifest")
- }
- return nil
- }
|