manifestlist.go 7.3 KB

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