use custom config type for OCI v1.0

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
This commit is contained in:
Milas Bowman 2023-12-05 11:53:08 -05:00 committed by Nicolas De loof
parent df6fe59f72
commit 9c29d2236d
1 changed files with 36 additions and 7 deletions

View File

@ -34,6 +34,28 @@ import (
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
const (
// ComposeProjectArtifactType is the OCI 1.1-compliant artifact type value
// for the generated image manifest.
ComposeProjectArtifactType = "application/vnd.docker.compose.project"
// ComposeYAMLMediaType is the media type for each layer (Compose file)
// in the image manifest.
ComposeYAMLMediaType = "application/vnd.docker.compose.file+yaml"
// ComposeEmptyConfigMediaType is a media type used for the config descriptor
// when doing OCI 1.0-style pushes.
//
// The content is always `{}`, the same as a normal empty descriptor, but
// the specific media type allows clients to fall back to the config media
// type to recognize the manifest as a Compose project since the artifact
// type field is not available in OCI 1.0.
//
// This is based on guidance from the OCI 1.1 spec:
// > Implementers note: artifacts have historically been created without
// > an artifactType field, and tooling to work with artifacts should
// > fallback to the config.mediaType value.
ComposeEmptyConfigMediaType = "application/vnd.docker.compose.config.empty.v1+json"
)
// clientAuthStatusCodes are client (4xx) errors that are authentication // clientAuthStatusCodes are client (4xx) errors that are authentication
// related. // related.
var clientAuthStatusCodes = []int{ var clientAuthStatusCodes = []int{
@ -49,7 +71,7 @@ type Pushable struct {
func DescriptorForComposeFile(path string, content []byte) v1.Descriptor { func DescriptorForComposeFile(path string, content []byte) v1.Descriptor {
return v1.Descriptor{ return v1.Descriptor{
MediaType: "application/vnd.docker.compose.file+yaml", MediaType: ComposeYAMLMediaType,
Digest: digest.FromString(string(content)), Digest: digest.FromString(string(content)),
Size: int64(len(content)), Size: int64(len(content)),
Annotations: map[string]string{ Annotations: map[string]string{
@ -133,12 +155,19 @@ func generateManifest(layers []v1.Descriptor, ociCompat api.OCIVersion) ([]Pusha
var artifactType string var artifactType string
switch ociCompat { switch ociCompat {
case api.OCIVersion1_0: case api.OCIVersion1_0:
configData, err := json.Marshal(v1.ImageConfig{}) // "Content other than OCI container images MAY be packaged using the image manifest.
if err != nil { // When this is done, the config.mediaType value MUST be set to a value specific to
return nil, err // the artifact type or the empty value."
} // Source: https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidelines-for-artifact-usage
//
// The `ComposeEmptyConfigMediaType` is used specifically for this purpose:
// there is no config, and an empty descriptor is used for OCI 1.1 in
// conjunction with the `ArtifactType`, but for OCI 1.0 compatibility,
// tooling falls back to the config media type, so this is used to
// indicate that it's not a container image but custom content.
configData := []byte("{}")
config = v1.Descriptor{ config = v1.Descriptor{
MediaType: v1.MediaTypeImageConfig, MediaType: ComposeEmptyConfigMediaType,
Digest: digest.FromBytes(configData), Digest: digest.FromBytes(configData),
Size: int64(len(configData)), Size: int64(len(configData)),
} }
@ -148,7 +177,7 @@ func generateManifest(layers []v1.Descriptor, ociCompat api.OCIVersion) ([]Pusha
toPush = append(toPush, Pushable{Descriptor: config, Data: configData}) toPush = append(toPush, Pushable{Descriptor: config, Data: configData})
case api.OCIVersion1_1: case api.OCIVersion1_1:
config = v1.DescriptorEmptyJSON config = v1.DescriptorEmptyJSON
artifactType = "application/vnd.docker.compose.project" artifactType = ComposeProjectArtifactType
// N.B. the descriptor has the data embedded in it // N.B. the descriptor has the data embedded in it
toPush = append(toPush, Pushable{Descriptor: config, Data: nil}) toPush = append(toPush, Pushable{Descriptor: config, Data: nil})
default: default: