manifest/manifestlist/compat/compat.go (74 lines of code) (raw):
// Package compat provides compatibility support for manifest lists containing
// blobs, such as buildx cache manifests using OCI Image Indexes. Since
// manifest lists should not include blob references, this package serves to
// separate the code for backwards compatibility from the code which assumes
// manifest lists that conform to spec.
package compat
import (
"errors"
"fmt"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/ocischema"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
// MediaTypeBuildxCacheConfig is the mediatype associated with buildx
// cache config blobs. This should be unique to buildx.
var MediaTypeBuildxCacheConfig = "application/vnd.buildkit.cacheconfig.v0"
// SplitReferences contains two lists of manifest list references broken down
// into either blobs or manifests. The result of appending these two lists
// together should include all of the descriptors returned by
// ManifestList.References with no duplicates, additions, or omissions.
type SplitReferences struct {
Manifests []distribution.Descriptor
Blobs []distribution.Descriptor
}
// References returns the references of the DeserializedManifestList split into
// manifests and layers based on the mediatype of the standard list of
// descriptors. Only known manifest mediatypes will be sorted into the manifests
// array while everything else will be sorted into blobs. Helm chart manifests
// do not include a mediatype at the time of this commit, but they are unlikely
// to be included within a manifest list.
func References(ml *manifestlist.DeserializedManifestList) SplitReferences {
var (
manifests = make([]distribution.Descriptor, 0)
blobs = make([]distribution.Descriptor, 0)
)
for _, r := range ml.References() {
switch r.MediaType {
case schema2.MediaTypeManifest,
manifestlist.MediaTypeManifestList,
v1.MediaTypeImageManifest,
schema1.MediaTypeSignedManifest,
schema1.MediaTypeManifest:
manifests = append(manifests, r)
default:
blobs = append(blobs, r)
}
}
return SplitReferences{Manifests: manifests, Blobs: blobs}
}
// LikelyBuildxCache returns true if the manifest list is likely a buildx cache
// manifest based on the unique buildx config mediatype.
func LikelyBuildxCache(ml *manifestlist.DeserializedManifestList) bool {
blobs := References(ml).Blobs
for _, desc := range blobs {
if desc.MediaType == MediaTypeBuildxCacheConfig {
return true
}
}
return false
}
// ContainsBlobs returns true if the manifest list contains any blobs.
func ContainsBlobs(ml *manifestlist.DeserializedManifestList) bool {
return len(References(ml).Blobs) > 0
}
// OCIManifestFromBuildkitIndex transforms a Buildkit cache index into an OCI manifest for compatibility purposes.
// Some cache indexes do not contain layers in them, see https://gitlab.com/gitlab-org/container-registry/-/issues/695.
func OCIManifestFromBuildkitIndex(ml *manifestlist.DeserializedManifestList) (*ocischema.DeserializedManifest, error) {
refs := References(ml)
if len(refs.Manifests) > 0 {
return nil, errors.New("buildkit index has unexpected manifest references")
}
// set "config" and "layer" references apart
var cfg *distribution.Descriptor
var layers []distribution.Descriptor
for _, ref := range refs.Blobs {
if ref.MediaType == MediaTypeBuildxCacheConfig {
cfg = &ref
} else {
layers = append(layers, ref)
}
}
// make sure they were found
if cfg == nil {
return nil, errors.New("buildkit index has no config reference")
}
m, err := ocischema.FromStruct(ocischema.Manifest{
Versioned: ocischema.SchemaVersion,
Config: *cfg,
Layers: layers,
})
if err != nil {
return nil, fmt.Errorf("building manifest from buildkit index: %w", err)
}
return m, nil
}