...

Source file src/github.com/docker/distribution/manifest/manifestlist/manifestlist.go

Documentation: github.com/docker/distribution/manifest/manifestlist

     1  package manifestlist
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/docker/distribution"
     9  	"github.com/docker/distribution/manifest"
    10  	"github.com/opencontainers/go-digest"
    11  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
    12  )
    13  
    14  const (
    15  	// MediaTypeManifestList specifies the mediaType for manifest lists.
    16  	MediaTypeManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
    17  )
    18  
    19  // SchemaVersion provides a pre-initialized version structure for this
    20  // packages version of the manifest.
    21  var SchemaVersion = manifest.Versioned{
    22  	SchemaVersion: 2,
    23  	MediaType:     MediaTypeManifestList,
    24  }
    25  
    26  // OCISchemaVersion provides a pre-initialized version structure for this
    27  // packages OCIschema version of the manifest.
    28  var OCISchemaVersion = manifest.Versioned{
    29  	SchemaVersion: 2,
    30  	MediaType:     v1.MediaTypeImageIndex,
    31  }
    32  
    33  func init() {
    34  	manifestListFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
    35  		m := new(DeserializedManifestList)
    36  		err := m.UnmarshalJSON(b)
    37  		if err != nil {
    38  			return nil, distribution.Descriptor{}, err
    39  		}
    40  
    41  		if m.MediaType != MediaTypeManifestList {
    42  			err = fmt.Errorf("mediaType in manifest list should be '%s' not '%s'",
    43  				MediaTypeManifestList, m.MediaType)
    44  
    45  			return nil, distribution.Descriptor{}, err
    46  		}
    47  
    48  		dgst := digest.FromBytes(b)
    49  		return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err
    50  	}
    51  	err := distribution.RegisterManifestSchema(MediaTypeManifestList, manifestListFunc)
    52  	if err != nil {
    53  		panic(fmt.Sprintf("Unable to register manifest: %s", err))
    54  	}
    55  
    56  	imageIndexFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
    57  		if err := validateIndex(b); err != nil {
    58  			return nil, distribution.Descriptor{}, err
    59  		}
    60  		m := new(DeserializedManifestList)
    61  		err := m.UnmarshalJSON(b)
    62  		if err != nil {
    63  			return nil, distribution.Descriptor{}, err
    64  		}
    65  
    66  		if m.MediaType != "" && m.MediaType != v1.MediaTypeImageIndex {
    67  			err = fmt.Errorf("if present, mediaType in image index should be '%s' not '%s'",
    68  				v1.MediaTypeImageIndex, m.MediaType)
    69  
    70  			return nil, distribution.Descriptor{}, err
    71  		}
    72  
    73  		dgst := digest.FromBytes(b)
    74  		return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeImageIndex}, err
    75  	}
    76  	err = distribution.RegisterManifestSchema(v1.MediaTypeImageIndex, imageIndexFunc)
    77  	if err != nil {
    78  		panic(fmt.Sprintf("Unable to register OCI Image Index: %s", err))
    79  	}
    80  }
    81  
    82  // PlatformSpec specifies a platform where a particular image manifest is
    83  // applicable.
    84  type PlatformSpec struct {
    85  	// Architecture field specifies the CPU architecture, for example
    86  	// `amd64` or `ppc64`.
    87  	Architecture string `json:"architecture"`
    88  
    89  	// OS specifies the operating system, for example `linux` or `windows`.
    90  	OS string `json:"os"`
    91  
    92  	// OSVersion is an optional field specifying the operating system
    93  	// version, for example `10.0.10586`.
    94  	OSVersion string `json:"os.version,omitempty"`
    95  
    96  	// OSFeatures is an optional field specifying an array of strings,
    97  	// each listing a required OS feature (for example on Windows `win32k`).
    98  	OSFeatures []string `json:"os.features,omitempty"`
    99  
   100  	// Variant is an optional field specifying a variant of the CPU, for
   101  	// example `ppc64le` to specify a little-endian version of a PowerPC CPU.
   102  	Variant string `json:"variant,omitempty"`
   103  
   104  	// Features is an optional field specifying an array of strings, each
   105  	// listing a required CPU feature (for example `sse4` or `aes`).
   106  	Features []string `json:"features,omitempty"`
   107  }
   108  
   109  // A ManifestDescriptor references a platform-specific manifest.
   110  type ManifestDescriptor struct {
   111  	distribution.Descriptor
   112  
   113  	// Platform specifies which platform the manifest pointed to by the
   114  	// descriptor runs on.
   115  	Platform PlatformSpec `json:"platform"`
   116  }
   117  
   118  // ManifestList references manifests for various platforms.
   119  type ManifestList struct {
   120  	manifest.Versioned
   121  
   122  	// Config references the image configuration as a blob.
   123  	Manifests []ManifestDescriptor `json:"manifests"`
   124  }
   125  
   126  // References returns the distribution descriptors for the referenced image
   127  // manifests.
   128  func (m ManifestList) References() []distribution.Descriptor {
   129  	dependencies := make([]distribution.Descriptor, len(m.Manifests))
   130  	for i := range m.Manifests {
   131  		dependencies[i] = m.Manifests[i].Descriptor
   132  	}
   133  
   134  	return dependencies
   135  }
   136  
   137  // DeserializedManifestList wraps ManifestList with a copy of the original
   138  // JSON.
   139  type DeserializedManifestList struct {
   140  	ManifestList
   141  
   142  	// canonical is the canonical byte representation of the Manifest.
   143  	canonical []byte
   144  }
   145  
   146  // FromDescriptors takes a slice of descriptors, and returns a
   147  // DeserializedManifestList which contains the resulting manifest list
   148  // and its JSON representation.
   149  func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) {
   150  	var mediaType string
   151  	if len(descriptors) > 0 && descriptors[0].Descriptor.MediaType == v1.MediaTypeImageManifest {
   152  		mediaType = v1.MediaTypeImageIndex
   153  	} else {
   154  		mediaType = MediaTypeManifestList
   155  	}
   156  
   157  	return FromDescriptorsWithMediaType(descriptors, mediaType)
   158  }
   159  
   160  // FromDescriptorsWithMediaType is for testing purposes, it's useful to be able to specify the media type explicitly
   161  func FromDescriptorsWithMediaType(descriptors []ManifestDescriptor, mediaType string) (*DeserializedManifestList, error) {
   162  	m := ManifestList{
   163  		Versioned: manifest.Versioned{
   164  			SchemaVersion: 2,
   165  			MediaType:     mediaType,
   166  		},
   167  	}
   168  
   169  	m.Manifests = make([]ManifestDescriptor, len(descriptors))
   170  	copy(m.Manifests, descriptors)
   171  
   172  	deserialized := DeserializedManifestList{
   173  		ManifestList: m,
   174  	}
   175  
   176  	var err error
   177  	deserialized.canonical, err = json.MarshalIndent(&m, "", "   ")
   178  	return &deserialized, err
   179  }
   180  
   181  // UnmarshalJSON populates a new ManifestList struct from JSON data.
   182  func (m *DeserializedManifestList) UnmarshalJSON(b []byte) error {
   183  	m.canonical = make([]byte, len(b))
   184  	// store manifest list in canonical
   185  	copy(m.canonical, b)
   186  
   187  	// Unmarshal canonical JSON into ManifestList object
   188  	var manifestList ManifestList
   189  	if err := json.Unmarshal(m.canonical, &manifestList); err != nil {
   190  		return err
   191  	}
   192  
   193  	m.ManifestList = manifestList
   194  
   195  	return nil
   196  }
   197  
   198  // MarshalJSON returns the contents of canonical. If canonical is empty,
   199  // marshals the inner contents.
   200  func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) {
   201  	if len(m.canonical) > 0 {
   202  		return m.canonical, nil
   203  	}
   204  
   205  	return nil, errors.New("JSON representation not initialized in DeserializedManifestList")
   206  }
   207  
   208  // Payload returns the raw content of the manifest list. The contents can be
   209  // used to calculate the content identifier.
   210  func (m DeserializedManifestList) Payload() (string, []byte, error) {
   211  	var mediaType string
   212  	if m.MediaType == "" {
   213  		mediaType = v1.MediaTypeImageIndex
   214  	} else {
   215  		mediaType = m.MediaType
   216  	}
   217  
   218  	return mediaType, m.canonical, nil
   219  }
   220  
   221  // unknownDocument represents a manifest, manifest list, or index that has not
   222  // yet been validated
   223  type unknownDocument struct {
   224  	Config interface{} `json:"config,omitempty"`
   225  	Layers interface{} `json:"layers,omitempty"`
   226  }
   227  
   228  // validateIndex returns an error if the byte slice is invalid JSON or if it
   229  // contains fields that belong to a manifest
   230  func validateIndex(b []byte) error {
   231  	var doc unknownDocument
   232  	if err := json.Unmarshal(b, &doc); err != nil {
   233  		return err
   234  	}
   235  	if doc.Config != nil || doc.Layers != nil {
   236  		return errors.New("index: expected index but found manifest")
   237  	}
   238  	return nil
   239  }
   240  

View as plain text