2015-12-10 00:38:04 +00:00
|
|
|
package schema2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
2020-08-24 11:18:39 +00:00
|
|
|
"github.com/distribution/distribution/v3"
|
|
|
|
"github.com/distribution/distribution/v3/manifest"
|
2016-12-17 00:28:34 +00:00
|
|
|
"github.com/opencontainers/go-digest"
|
2023-04-30 16:16:51 +00:00
|
|
|
"github.com/opencontainers/image-spec/specs-go"
|
2015-12-10 00:38:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// MediaTypeManifest specifies the mediaType for the current version.
|
|
|
|
MediaTypeManifest = "application/vnd.docker.distribution.manifest.v2+json"
|
|
|
|
|
2016-12-15 00:17:20 +00:00
|
|
|
// MediaTypeImageConfig specifies the mediaType for the image configuration.
|
|
|
|
MediaTypeImageConfig = "application/vnd.docker.container.image.v1+json"
|
2015-12-10 00:38:04 +00:00
|
|
|
|
2016-08-11 22:40:46 +00:00
|
|
|
// MediaTypePluginConfig specifies the mediaType for plugin configuration.
|
2016-11-10 19:26:05 +00:00
|
|
|
MediaTypePluginConfig = "application/vnd.docker.plugin.v1+json"
|
2016-08-11 22:40:46 +00:00
|
|
|
|
2015-12-10 00:38:04 +00:00
|
|
|
// MediaTypeLayer is the mediaType used for layers referenced by the
|
|
|
|
// manifest.
|
|
|
|
MediaTypeLayer = "application/vnd.docker.image.rootfs.diff.tar.gzip"
|
2016-05-14 21:49:08 +00:00
|
|
|
|
|
|
|
// MediaTypeForeignLayer is the mediaType used for layers that must be
|
|
|
|
// downloaded from foreign URLs.
|
|
|
|
MediaTypeForeignLayer = "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip"
|
2016-12-16 19:12:11 +00:00
|
|
|
|
|
|
|
// MediaTypeUncompressedLayer is the mediaType used for layers which
|
|
|
|
// are not compressed.
|
|
|
|
MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar"
|
2015-12-10 00:38:04 +00:00
|
|
|
)
|
|
|
|
|
2023-04-30 16:16:51 +00:00
|
|
|
const (
|
|
|
|
defaultSchemaVersion = 2
|
|
|
|
defaultMediaType = MediaTypeManifest
|
|
|
|
)
|
|
|
|
|
2022-11-02 21:05:45 +00:00
|
|
|
// SchemaVersion provides a pre-initialized version structure for this
|
|
|
|
// packages version of the manifest.
|
2023-04-30 16:16:51 +00:00
|
|
|
//
|
|
|
|
// Deprecated: use [specs.Versioned] and set MediaType on the manifest
|
|
|
|
// to [MediaTypeManifest].
|
|
|
|
//
|
|
|
|
//nolint:staticcheck // ignore SA1019: manifest.Versioned is deprecated:
|
2022-11-02 21:05:45 +00:00
|
|
|
var SchemaVersion = manifest.Versioned{
|
2023-04-30 16:16:51 +00:00
|
|
|
SchemaVersion: defaultSchemaVersion,
|
|
|
|
MediaType: defaultMediaType,
|
2022-11-02 21:05:45 +00:00
|
|
|
}
|
2015-12-10 00:38:04 +00:00
|
|
|
|
|
|
|
func init() {
|
2023-04-30 16:16:51 +00:00
|
|
|
if err := distribution.RegisterManifestSchema(defaultMediaType, unmarshalSchema2); err != nil {
|
2015-12-10 00:38:04 +00:00
|
|
|
panic(fmt.Sprintf("Unable to register manifest: %s", err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-16 10:02:28 +00:00
|
|
|
func unmarshalSchema2(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
|
|
|
|
m := &DeserializedManifest{}
|
|
|
|
if err := m.UnmarshalJSON(b); err != nil {
|
|
|
|
return nil, distribution.Descriptor{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return m, distribution.Descriptor{
|
|
|
|
Digest: digest.FromBytes(b),
|
|
|
|
Size: int64(len(b)),
|
2023-04-30 16:16:51 +00:00
|
|
|
MediaType: defaultMediaType,
|
2024-07-16 10:02:28 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2015-12-10 00:38:04 +00:00
|
|
|
// Manifest defines a schema2 manifest.
|
|
|
|
type Manifest struct {
|
2023-04-30 16:16:51 +00:00
|
|
|
specs.Versioned
|
|
|
|
|
|
|
|
// MediaType is the media type of this schema.
|
|
|
|
MediaType string `json:"mediaType,omitempty"`
|
2015-12-10 00:38:04 +00:00
|
|
|
|
|
|
|
// Config references the image configuration as a blob.
|
|
|
|
Config distribution.Descriptor `json:"config"`
|
|
|
|
|
|
|
|
// Layers lists descriptors for the layers referenced by the
|
|
|
|
// configuration.
|
|
|
|
Layers []distribution.Descriptor `json:"layers"`
|
|
|
|
}
|
|
|
|
|
2018-06-27 15:47:40 +00:00
|
|
|
// References returns the descriptors of this manifests references.
|
2015-12-10 00:38:04 +00:00
|
|
|
func (m Manifest) References() []distribution.Descriptor {
|
2016-10-13 00:20:27 +00:00
|
|
|
references := make([]distribution.Descriptor, 0, 1+len(m.Layers))
|
|
|
|
references = append(references, m.Config)
|
|
|
|
references = append(references, m.Layers...)
|
|
|
|
return references
|
2015-12-10 00:38:04 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 23:07:46 +00:00
|
|
|
// Target returns the target of this manifest.
|
2015-12-10 00:38:04 +00:00
|
|
|
func (m Manifest) Target() distribution.Descriptor {
|
|
|
|
return m.Config
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeserializedManifest wraps Manifest with a copy of the original JSON.
|
|
|
|
// It satisfies the distribution.Manifest interface.
|
|
|
|
type DeserializedManifest struct {
|
|
|
|
Manifest
|
|
|
|
|
|
|
|
// canonical is the canonical byte representation of the Manifest.
|
|
|
|
canonical []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromStruct takes a Manifest structure, marshals it to JSON, and returns a
|
|
|
|
// DeserializedManifest which contains the manifest and its JSON representation.
|
|
|
|
func FromStruct(m Manifest) (*DeserializedManifest, error) {
|
|
|
|
var deserialized DeserializedManifest
|
|
|
|
deserialized.Manifest = m
|
|
|
|
|
|
|
|
var err error
|
|
|
|
deserialized.canonical, err = json.MarshalIndent(&m, "", " ")
|
|
|
|
return &deserialized, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON populates a new Manifest struct from JSON data.
|
|
|
|
func (m *DeserializedManifest) UnmarshalJSON(b []byte) error {
|
2019-02-05 00:01:04 +00:00
|
|
|
m.canonical = make([]byte, len(b))
|
2015-12-10 00:38:04 +00:00
|
|
|
// store manifest in canonical
|
|
|
|
copy(m.canonical, b)
|
|
|
|
|
|
|
|
// Unmarshal canonical JSON into Manifest object
|
2022-11-26 12:15:07 +00:00
|
|
|
var mfst Manifest
|
|
|
|
if err := json.Unmarshal(m.canonical, &mfst); err != nil {
|
2015-12-10 00:38:04 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-04-30 16:16:51 +00:00
|
|
|
if mfst.MediaType != defaultMediaType {
|
|
|
|
return fmt.Errorf("mediaType in manifest should be '%s' not '%s'", defaultMediaType, mfst.MediaType)
|
2018-03-14 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
2022-11-26 12:15:07 +00:00
|
|
|
m.Manifest = mfst
|
2015-12-10 00:38:04 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalJSON returns the contents of canonical. If canonical is empty,
|
|
|
|
// marshals the inner contents.
|
|
|
|
func (m *DeserializedManifest) MarshalJSON() ([]byte, error) {
|
|
|
|
if len(m.canonical) > 0 {
|
|
|
|
return m.canonical, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.New("JSON representation not initialized in DeserializedManifest")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Payload returns the raw content of the manifest. The contents can be used to
|
|
|
|
// calculate the content identifier.
|
|
|
|
func (m DeserializedManifest) Payload() (string, []byte, error) {
|
|
|
|
return m.MediaType, m.canonical, nil
|
|
|
|
}
|