imagetools: support oci-layout referrers
Handle OCI layout referrers via subject-annotated index entries and add integration coverage for copying signed attestations through oci-layout. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+116
-1
@@ -34,6 +34,7 @@ var imagetoolsTests = []func(t *testing.T, sb integration.Sandbox){
|
||||
testImagetoolsCreatePlatformFilter,
|
||||
testImagetoolsOCILayoutInspect,
|
||||
testImagetoolsOCILayoutCreateSourceAndTarget,
|
||||
testImagetoolsOCILayoutReferrers,
|
||||
testImagetoolsOCILayoutMergeSources,
|
||||
testImagetoolsOCILayoutTargetDigest,
|
||||
testImagetoolsAppend,
|
||||
@@ -450,6 +451,121 @@ func testImagetoolsOCILayoutCreateSourceAndTarget(t *testing.T, sb integration.S
|
||||
require.Equal(t, sourceDigest, digest.FromBytes(dt))
|
||||
}
|
||||
|
||||
// testImagetoolsOCILayoutReferrers verifies standalone referrers are recorded
|
||||
// directly in OCI layout index.json with a subject annotation, while reachable
|
||||
// attestation manifests are not duplicated there.
|
||||
func testImagetoolsOCILayoutReferrers(t *testing.T, sb integration.Sandbox) {
|
||||
if !isDockerContainerWorker(sb) {
|
||||
t.Skip("only testing with docker-container worker, imagetools only runs on docker-container")
|
||||
}
|
||||
|
||||
dir := createDockerfileWithArches(t, "amd64", "arm64")
|
||||
registry1, err := sb.NewRegistry()
|
||||
if errors.Is(err, integration.ErrRequirements) {
|
||||
t.Skip(err.Error())
|
||||
}
|
||||
require.NoError(t, err)
|
||||
registry2, err := sb.NewRegistry()
|
||||
require.NoError(t, err)
|
||||
|
||||
source := registry1 + "/buildx/imtools-oci-layout-referrers-src:latest"
|
||||
out, err := buildCmd(sb, withArgs(
|
||||
"--output", "type=image,name="+source+",push=true,oci-mediatypes=true,oci-artifact=true",
|
||||
"--platform=linux/amd64,linux/arm64",
|
||||
"--provenance=mode=min",
|
||||
dir,
|
||||
))
|
||||
require.NoError(t, err, string(out))
|
||||
|
||||
cmd := buildxCmd(sb, withArgs("imagetools", "inspect", source, "--raw"))
|
||||
dt, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(dt))
|
||||
|
||||
var srcIdx ocispecs.Index
|
||||
err = json.Unmarshal(dt, &srcIdx)
|
||||
require.NoError(t, err)
|
||||
|
||||
var attestations []ocispecs.Descriptor
|
||||
for _, mfst := range srcIdx.Manifests {
|
||||
if mfst.Annotations["vnd.docker.reference.type"] == "attestation-manifest" {
|
||||
attestations = append(attestations, mfst)
|
||||
}
|
||||
}
|
||||
require.Len(t, attestations, 2)
|
||||
|
||||
signatures := make([]ocispecs.Descriptor, 0, len(attestations))
|
||||
for _, attestation := range attestations {
|
||||
signatures = append(signatures, pushFakeSignatureReferrer(t, source, attestation))
|
||||
}
|
||||
|
||||
layoutPath := filepath.Join(dir, "layout-referrers")
|
||||
layoutRef := "oci-layout://" + layoutPath + ":latest"
|
||||
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", layoutRef, source))
|
||||
dt, err = cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(dt))
|
||||
|
||||
idxBytes, err := os.ReadFile(filepath.Join(layoutPath, "index.json"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var layoutIdx ocispecs.Index
|
||||
err = json.Unmarshal(idxBytes, &layoutIdx)
|
||||
require.NoError(t, err)
|
||||
|
||||
directReferrers := map[digest.Digest]ocispecs.Descriptor{}
|
||||
directReferrerCount := 0
|
||||
for _, desc := range layoutIdx.Manifests {
|
||||
if desc.Annotations["io.containerd.manifest.subject"] != "" {
|
||||
directReferrerCount++
|
||||
directReferrers[desc.Digest] = desc
|
||||
}
|
||||
}
|
||||
require.Len(t, directReferrers, directReferrerCount)
|
||||
require.Len(t, directReferrers, len(signatures))
|
||||
for i, sig := range signatures {
|
||||
desc, ok := directReferrers[sig.Digest]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, attestations[i].Digest.String(), desc.Annotations["io.containerd.manifest.subject"])
|
||||
}
|
||||
for _, attestation := range attestations {
|
||||
_, ok := directReferrers[attestation.Digest]
|
||||
require.False(t, ok)
|
||||
}
|
||||
|
||||
target := registry2 + "/buildx/imtools-oci-layout-referrers-dst:latest"
|
||||
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", target, layoutRef))
|
||||
dt, err = cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(dt))
|
||||
|
||||
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target, "--raw"))
|
||||
dt, err = cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(dt))
|
||||
|
||||
var dstIdx ocispecs.Index
|
||||
err = json.Unmarshal(dt, &dstIdx)
|
||||
require.NoError(t, err)
|
||||
|
||||
copiedAttestations := map[digest.Digest]struct{}{}
|
||||
for _, mfst := range dstIdx.Manifests {
|
||||
if mfst.Annotations["vnd.docker.reference.type"] == "attestation-manifest" {
|
||||
copiedAttestations[mfst.Digest] = struct{}{}
|
||||
}
|
||||
}
|
||||
require.Len(t, copiedAttestations, len(attestations))
|
||||
|
||||
for _, sig := range signatures {
|
||||
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target+"@"+sig.Digest.String(), "--raw"))
|
||||
dt, err = cmd.CombinedOutput()
|
||||
require.NoError(t, err, string(dt))
|
||||
|
||||
var sigManifest ocispecs.Manifest
|
||||
err = json.Unmarshal(dt, &sigManifest)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sigManifest.Subject)
|
||||
_, ok := copiedAttestations[sigManifest.Subject.Digest]
|
||||
require.True(t, ok)
|
||||
}
|
||||
}
|
||||
|
||||
// testImagetoolsOCILayoutMergeSources verifies create merges registry and local OCI layout sources.
|
||||
func testImagetoolsOCILayoutMergeSources(t *testing.T, sb integration.Sandbox) {
|
||||
if !isDockerContainerWorker(sb) {
|
||||
@@ -871,7 +987,6 @@ func testImagetoolsCopyAttestationWithSignature(t *testing.T, sb integration.San
|
||||
require.NotNil(t, signatureManifest.Subject)
|
||||
require.Equal(t, attestationDesc.Digest, signatureManifest.Subject.Digest)
|
||||
require.Equal(t, "dsse-envelope", signatureManifest.Annotations["dev.sigstore.bundle.content"])
|
||||
|
||||
}
|
||||
|
||||
// Only attestation signatures should be present after the copy. The
|
||||
|
||||
+33
-47
@@ -273,7 +273,7 @@ func (r *Resolver) Copy(ctx context.Context, src *Source, dest *Location) error
|
||||
return err
|
||||
}
|
||||
|
||||
recorder := &recordingReferrersProvider{base: referrersFunc(func(ctx context.Context, subject ocispecs.Descriptor) ([]ocispecs.Descriptor, error) {
|
||||
referrers := &referrersProvider{base: referrersFunc(func(ctx context.Context, subject ocispecs.Descriptor) ([]ocispecs.Descriptor, error) {
|
||||
descs, err := r.FetchReferrers(ctx, src.Ref, subject.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -287,12 +287,14 @@ func (r *Resolver) Copy(ctx context.Context, src *Source, dest *Location) error
|
||||
return filtered, nil
|
||||
})}
|
||||
|
||||
err = contentutil.CopyChain(ctx, ingester, provider, desc, contentutil.WithReferrers(recorder))
|
||||
err = contentutil.CopyChain(ctx, ingester, provider, desc, contentutil.WithReferrers(referrers))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dest.IsOCILayout() {
|
||||
return r.writeRecordedReferrers(ctx, dest, recorder)
|
||||
for subject, descs := range referrers.refs {
|
||||
r.ociReferrers.record(dest.OCILayout().Path, subject, descs)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -490,23 +492,40 @@ func (r *Resolver) filterPlatforms(ctx context.Context, dt []byte, desc ocispecs
|
||||
return idxBytes, desc, mfstsWithSource, nil
|
||||
}
|
||||
|
||||
type recordingReferrersProvider struct {
|
||||
type referrersProvider struct {
|
||||
base referrersFunc
|
||||
refs map[digest.Digest][]ocispecs.Descriptor
|
||||
}
|
||||
|
||||
func (r *recordingReferrersProvider) Referrers(ctx context.Context, desc ocispecs.Descriptor) ([]ocispecs.Descriptor, error) {
|
||||
func (r *referrersProvider) Referrers(ctx context.Context, desc ocispecs.Descriptor) ([]ocispecs.Descriptor, error) {
|
||||
out, err := r.base(ctx, desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = dedupeDescriptors(out)
|
||||
if r.refs == nil {
|
||||
r.refs = map[digest.Digest][]ocispecs.Descriptor{}
|
||||
}
|
||||
r.refs[desc.Digest] = append(r.refs[desc.Digest], out...)
|
||||
r.refs[desc.Digest] = dedupeDescriptors(append(r.refs[desc.Digest], out...))
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func dedupeDescriptors(descs []ocispecs.Descriptor) []ocispecs.Descriptor {
|
||||
if len(descs) < 2 {
|
||||
return descs
|
||||
}
|
||||
seen := make(map[digest.Digest]struct{}, len(descs))
|
||||
out := descs[:0]
|
||||
for _, desc := range descs {
|
||||
if _, ok := seen[desc.Digest]; ok {
|
||||
continue
|
||||
}
|
||||
seen[desc.Digest] = struct{}{}
|
||||
out = append(out, desc)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Resolver) ingesterForLocation(loc *Location) (content.Ingester, error) {
|
||||
if loc.IsRegistry() {
|
||||
p, err := r.registryResolver().Pusher(context.TODO(), loc.Name())
|
||||
@@ -552,52 +571,19 @@ func (r *Resolver) pushOCILayout(ctx context.Context, ref *Location, desc ocispe
|
||||
idx := ociindex.NewStoreIndex(ref.OCILayout().Path)
|
||||
switch {
|
||||
case ref.Digest() != "":
|
||||
return idx.Put(desc)
|
||||
if err := idx.Put(desc); err != nil {
|
||||
return err
|
||||
}
|
||||
case ref.Tag() != "":
|
||||
return idx.Put(desc, ociindex.Tag(ref.Tag()))
|
||||
if err := idx.Put(desc, ociindex.Tag(ref.Tag())); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return idx.Put(desc, ociindex.Tag("latest"))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Resolver) writeRecordedReferrers(ctx context.Context, loc *Location, refs *recordingReferrersProvider) error {
|
||||
if refs == nil || len(refs.refs) == 0 {
|
||||
return nil
|
||||
}
|
||||
store, err := r.localStore(loc.OCILayout().Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx := ociindex.NewStoreIndex(loc.OCILayout().Path)
|
||||
for subject, manifests := range refs.refs {
|
||||
fallback := ocispecs.Index{
|
||||
Versioned: specs.Versioned{SchemaVersion: 2},
|
||||
MediaType: ocispecs.MediaTypeImageIndex,
|
||||
Manifests: manifests,
|
||||
}
|
||||
dt, err := json.Marshal(fallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
desc := ocispecs.Descriptor{
|
||||
MediaType: ocispecs.MediaTypeImageIndex,
|
||||
Digest: digest.FromBytes(dt),
|
||||
Size: int64(len(dt)),
|
||||
}
|
||||
w, err := store.Writer(ctx, content.WithRef(desc.Digest.String()), content.WithDescriptor(desc))
|
||||
if err != nil && !errdefs.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
if err := content.Copy(ctx, w, bytes.NewReader(dt), desc.Size, desc.Digest); err != nil && !errdefs.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := idx.Put(desc, ociindex.Tag("sha256-"+subject.Encoded())); err != nil {
|
||||
if err := idx.Put(desc, ociindex.Tag("latest")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return writePendingOCILayoutReferrers(ctx, r.ociReferrers.take(ref.OCILayout().Path), r.GetDescriptor, idx, ref)
|
||||
}
|
||||
|
||||
func detectMediaType(dt []byte) (string, error) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package imagetools
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
@@ -38,6 +37,7 @@ type Resolver struct {
|
||||
buffer contentutil.Buffer
|
||||
localStoreMu sync.Mutex
|
||||
localStores map[string]content.Store
|
||||
ociReferrers ociLayoutReferrerRecorder
|
||||
}
|
||||
|
||||
func New(opt Opt) *Resolver {
|
||||
@@ -172,7 +172,7 @@ func (r *Resolver) localStore(path string) (content.Store, error) {
|
||||
|
||||
func (r *Resolver) FetchReferrers(ctx context.Context, loc *Location, dgst digest.Digest, opts ...remotes.FetchReferrersOpt) ([]ocispecs.Descriptor, error) {
|
||||
if loc.IsOCILayout() {
|
||||
return r.fetchOCILayoutReferrers(ctx, loc, dgst)
|
||||
return fetchOCILayoutReferrers(ctx, r.GetDescriptor, loc, dgst)
|
||||
}
|
||||
f, err := r.registryResolver().Fetcher(ctx, loc.String())
|
||||
if err != nil {
|
||||
@@ -248,30 +248,6 @@ func (r *Resolver) resolveOCILayout(ctx context.Context, loc *Location) (string,
|
||||
return loc.String(), *desc, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) fetchOCILayoutReferrers(ctx context.Context, loc *Location, dgst digest.Digest) ([]ocispecs.Descriptor, error) {
|
||||
idx := ociindex.NewStoreIndex(loc.OCILayout().Path)
|
||||
// TODO: temporary fallback tag, should use annotations instead
|
||||
desc, err := idx.Get("sha256-" + dgst.Encoded())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if desc == nil {
|
||||
return nil, errors.WithStack(errdefs.ErrNotFound)
|
||||
}
|
||||
dt, err := r.GetDescriptor(ctx, loc, *desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if desc.MediaType != ocispecs.MediaTypeImageIndex {
|
||||
return nil, errors.Errorf("unsupported referrers media type %s", desc.MediaType)
|
||||
}
|
||||
var referrersIndex ocispecs.Index
|
||||
if err := json.Unmarshal(dt, &referrersIndex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return referrersIndex.Manifests, nil
|
||||
}
|
||||
|
||||
func parseRef(s string) (reference.Named, error) {
|
||||
ref, err := reference.ParseNormalizedNamed(s)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
package imagetools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/v2/core/images"
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/moby/buildkit/client/ociindex"
|
||||
"github.com/moby/buildkit/util/attestation"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ociLayoutReferrerRecorder struct {
|
||||
mu sync.Mutex
|
||||
refs map[string]map[digest.Digest][]ocispecs.Descriptor
|
||||
}
|
||||
|
||||
func (r *ociLayoutReferrerRecorder) record(path string, subject digest.Digest, descs []ocispecs.Descriptor) {
|
||||
if len(descs) == 0 {
|
||||
return
|
||||
}
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if r.refs == nil {
|
||||
r.refs = map[string]map[digest.Digest][]ocispecs.Descriptor{}
|
||||
}
|
||||
if r.refs[path] == nil {
|
||||
r.refs[path] = map[digest.Digest][]ocispecs.Descriptor{}
|
||||
}
|
||||
r.refs[path][subject] = dedupeDescriptors(append(r.refs[path][subject], descs...))
|
||||
}
|
||||
|
||||
func (r *ociLayoutReferrerRecorder) take(path string) map[digest.Digest][]ocispecs.Descriptor {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if r.refs == nil {
|
||||
return nil
|
||||
}
|
||||
out := r.refs[path]
|
||||
delete(r.refs, path)
|
||||
return out
|
||||
}
|
||||
|
||||
func hasSubjectAnnotation(desc ocispecs.Descriptor) bool {
|
||||
return desc.Annotations[images.AnnotationManifestSubject] != ""
|
||||
}
|
||||
|
||||
// fetchOCILayoutReferrers resolves referrers for a subject from an OCI layout by
|
||||
// combining directly indexed subject entries with referrers reachable from the
|
||||
// regular named roots in index.json.
|
||||
func fetchOCILayoutReferrers(ctx context.Context, getDescriptor func(context.Context, *Location, ocispecs.Descriptor) ([]byte, error), loc *Location, subject digest.Digest) ([]ocispecs.Descriptor, error) {
|
||||
idx, err := ociindex.NewStoreIndex(loc.OCILayout().Path).Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := map[digest.Digest]ocispecs.Descriptor{}
|
||||
visited := map[digest.Digest]struct{}{}
|
||||
for _, desc := range idx.Manifests {
|
||||
if hasSubjectAnnotation(desc) {
|
||||
continue
|
||||
}
|
||||
if err := collectReachableOCILayoutReferrers(ctx, getDescriptor, loc, desc, subject, visited, out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, desc := range idx.Manifests {
|
||||
if desc.Annotations[images.AnnotationManifestSubject] == subject.String() {
|
||||
out[desc.Digest] = desc
|
||||
}
|
||||
}
|
||||
|
||||
if len(out) == 0 {
|
||||
return nil, errors.WithStack(errdefs.ErrNotFound)
|
||||
}
|
||||
|
||||
refs := make([]ocispecs.Descriptor, 0, len(out))
|
||||
for _, desc := range out {
|
||||
refs = append(refs, desc)
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
// collectReachableOCILayoutReferrers walks a regular OCI layout root and records
|
||||
// referrer manifests for the requested subject that are already reachable from it.
|
||||
func collectReachableOCILayoutReferrers(ctx context.Context, getDescriptor func(context.Context, *Location, ocispecs.Descriptor) ([]byte, error), loc *Location, desc ocispecs.Descriptor, subject digest.Digest, visited map[digest.Digest]struct{}, out map[digest.Digest]ocispecs.Descriptor) error {
|
||||
if _, ok := visited[desc.Digest]; ok {
|
||||
return nil
|
||||
}
|
||||
visited[desc.Digest] = struct{}{}
|
||||
|
||||
if desc.Annotations[attestation.DockerAnnotationReferenceDigest] == subject.String() {
|
||||
out[desc.Digest] = desc
|
||||
}
|
||||
|
||||
switch desc.MediaType {
|
||||
case ocispecs.MediaTypeImageIndex:
|
||||
dt, err := getDescriptor(ctx, loc, desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var idx ocispecs.Index
|
||||
if err := json.Unmarshal(dt, &idx); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
for _, child := range idx.Manifests {
|
||||
if err := collectReachableOCILayoutReferrers(ctx, getDescriptor, loc, child, subject, visited, out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case ocispecs.MediaTypeImageManifest:
|
||||
dt, err := getDescriptor(ctx, loc, desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var mfst ocispecs.Manifest
|
||||
if err := json.Unmarshal(dt, &mfst); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if mfst.Subject != nil && mfst.Subject.Digest == subject {
|
||||
out[desc.Digest] = desc
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writePendingOCILayoutReferrers adds copied referrers to index.json only when
|
||||
// they are not already reachable from the regular top-level roots.
|
||||
func writePendingOCILayoutReferrers(
|
||||
ctx context.Context,
|
||||
pending map[digest.Digest][]ocispecs.Descriptor,
|
||||
getDescriptor func(context.Context, *Location, ocispecs.Descriptor) ([]byte, error),
|
||||
idx ociindex.StoreIndex,
|
||||
loc *Location,
|
||||
) error {
|
||||
if len(pending) == 0 {
|
||||
return nil
|
||||
}
|
||||
current, err := idx.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reachable := map[digest.Digest]struct{}{}
|
||||
visited := map[digest.Digest]struct{}{}
|
||||
for _, desc := range current.Manifests {
|
||||
if err := collectReachableDigests(ctx, getDescriptor, loc, desc, visited, reachable); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for subject, manifests := range pending {
|
||||
for _, desc := range manifests {
|
||||
if err := putSubjectReferrerIndexEntry(idx, reachable, subject, desc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectReachableDigests records descriptors reachable from regular OCI layout
|
||||
// roots so standalone subject-indexed referrers are not duplicated in index.json.
|
||||
func collectReachableDigests(ctx context.Context, getDescriptor func(context.Context, *Location, ocispecs.Descriptor) ([]byte, error), loc *Location, desc ocispecs.Descriptor, visited map[digest.Digest]struct{}, reachable map[digest.Digest]struct{}) error {
|
||||
if hasSubjectAnnotation(desc) {
|
||||
return nil
|
||||
}
|
||||
if _, ok := visited[desc.Digest]; ok {
|
||||
return nil
|
||||
}
|
||||
visited[desc.Digest] = struct{}{}
|
||||
reachable[desc.Digest] = struct{}{}
|
||||
|
||||
if desc.MediaType != ocispecs.MediaTypeImageIndex {
|
||||
return nil
|
||||
}
|
||||
|
||||
dt, err := getDescriptor(ctx, loc, desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var idx ocispecs.Index
|
||||
if err := json.Unmarshal(dt, &idx); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
for _, child := range idx.Manifests {
|
||||
if err := collectReachableDigests(ctx, getDescriptor, loc, child, visited, reachable); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func putSubjectReferrerIndexEntry(idx ociindex.StoreIndex, reachable map[digest.Digest]struct{}, subject digest.Digest, desc ocispecs.Descriptor) error {
|
||||
if _, ok := reachable[desc.Digest]; ok {
|
||||
return nil
|
||||
}
|
||||
if desc.Annotations == nil {
|
||||
desc.Annotations = map[string]string{}
|
||||
}
|
||||
if !hasSubjectAnnotation(desc) {
|
||||
desc.Annotations[images.AnnotationManifestSubject] = subject.String()
|
||||
}
|
||||
return idx.Put(desc)
|
||||
}
|
||||
Reference in New Issue
Block a user