247 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
| package manifestlist
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 
 | |
| 	"github.com/distribution/distribution/v3"
 | |
| 	"github.com/distribution/distribution/v3/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
 | |
| 
 | |
| 	// Manifests references a list of manifests
 | |
| 	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
 | |
| 		dependencies[i].Platform = &v1.Platform{
 | |
| 			Architecture: m.Manifests[i].Platform.Architecture,
 | |
| 			OS:           m.Manifests[i].Platform.OS,
 | |
| 			OSVersion:    m.Manifests[i].Platform.OSVersion,
 | |
| 			OSFeatures:   m.Manifests[i].Platform.OSFeatures,
 | |
| 			Variant:      m.Manifests[i].Platform.Variant,
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| }
 |