@@ -15,6 +15,7 @@ import (
1515 "github.com/containerd/platforms"
1616 "github.com/distribution/reference"
1717 "github.com/moby/buildkit/exporter/containerimage/exptypes"
18+ "github.com/moby/buildkit/util/attestation"
1819 "github.com/moby/buildkit/util/contentutil"
1920 "github.com/opencontainers/go-digest"
2021 "github.com/opencontainers/image-spec/specs-go"
@@ -23,12 +24,34 @@ import (
2324 "golang.org/x/sync/errgroup"
2425)
2526
27+ const (
28+ artifactTypeAttestationManifest = "application/vnd.docker.attestation.manifest.v1+json"
29+ artifactTypeCosignSignature = "application/vnd.dev.cosign.artifact.sig.v1+json"
30+ )
31+
32+ var supportedArtifactTypes = map [string ]struct {}{
33+ artifactTypeAttestationManifest : {},
34+ artifactTypeCosignSignature : {},
35+ }
36+
2637type Source struct {
2738 Desc ocispecs.Descriptor
2839 Ref reference.Named
2940}
3041
31- func (r * Resolver ) Combine (ctx context.Context , srcs []* Source , ann map [exptypes.AnnotationKey ]string , preferIndex bool ) ([]byte , ocispecs.Descriptor , map [digest.Digest ]* Source , error ) {
42+ func (r * Resolver ) Combine (ctx context.Context , srcs []* Source , ann map [exptypes.AnnotationKey ]string , preferIndex bool , platforms []ocispecs.Platform ) ([]byte , ocispecs.Descriptor , []DescWithSource , error ) {
43+ dt , desc , srcMap , err := r .combine (ctx , srcs , ann , preferIndex )
44+ if err != nil {
45+ return nil , ocispecs.Descriptor {}, nil , err
46+ }
47+ dt , desc , mfstsWithSource , err := r .filterPlatforms (ctx , dt , desc , srcMap , platforms )
48+ if err != nil {
49+ return nil , ocispecs.Descriptor {}, nil , err
50+ }
51+ return dt , desc , mfstsWithSource , nil
52+ }
53+
54+ func (r * Resolver ) combine (ctx context.Context , srcs []* Source , ann map [exptypes.AnnotationKey ]string , preferIndex bool ) ([]byte , ocispecs.Descriptor , map [digest.Digest ]* Source , error ) {
3255 eg , ctx := errgroup .WithContext (ctx )
3356
3457 dts := make ([][]byte , len (srcs ))
@@ -250,7 +273,28 @@ func (r *Resolver) Copy(ctx context.Context, src *Source, dest reference.Named)
250273 source , repo := u .Hostname (), strings .TrimPrefix (u .Path , "/" )
251274 desc .Annotations ["containerd.io/distribution.source." + source ] = repo
252275
253- err = contentutil .CopyChain (ctx , contentutil .FromPusher (p ), contentutil .FromFetcher (f ), desc )
276+ referrersFetcher , ok := f .(remotes.ReferrersFetcher )
277+ if ! ok {
278+ return errors .Errorf ("fetcher for %s does not support referrers" , src .Ref .String ())
279+ }
280+
281+ opts := []contentutil.CopyOption {
282+ contentutil .WithReferrers (func (ctx context.Context , desc ocispecs.Descriptor ) ([]ocispecs.Descriptor , error ) {
283+ descs , err := referrersFetcher .FetchReferrers (ctx , desc .Digest , "" )
284+ if err != nil {
285+ return nil , err
286+ }
287+ var filtered []ocispecs.Descriptor
288+ for _ , d := range descs {
289+ if _ , ok := supportedArtifactTypes [d .ArtifactType ]; ok {
290+ filtered = append (filtered , d )
291+ }
292+ }
293+ return filtered , nil
294+ }),
295+ }
296+
297+ err = contentutil .CopyChain (ctx , contentutil .FromPusher (p ), contentutil .FromFetcher (f ), desc , opts ... )
254298 if err != nil {
255299 return err
256300 }
@@ -288,6 +332,159 @@ func (r *Resolver) loadPlatform(ctx context.Context, p2 *ocispecs.Platform, in s
288332 return nil
289333}
290334
335+ type DescWithSource struct {
336+ ocispecs.Descriptor
337+ Source * Source
338+ }
339+
340+ func (r * Resolver ) filterPlatforms (ctx context.Context , dt []byte , desc ocispecs.Descriptor , srcMap map [digest.Digest ]* Source , plats []ocispecs.Platform ) ([]byte , ocispecs.Descriptor , []DescWithSource , error ) {
341+ matcher := platforms .Any (plats ... )
342+ if len (plats ) == 0 {
343+ matcher = platforms .All
344+ }
345+
346+ if ! images .IsIndexType (desc .MediaType ) {
347+ var mfst ocispecs.Manifest
348+ if err := json .Unmarshal (dt , & mfst ); err != nil {
349+ return nil , ocispecs.Descriptor {}, nil , errors .Wrapf (err , "failed to parse manifest" )
350+ }
351+ if desc .Platform == nil {
352+ return nil , ocispecs.Descriptor {}, nil , errors .Errorf ("cannot filter platforms from a manifest without platform information" )
353+ }
354+ if ! matcher .Match (* desc .Platform ) {
355+ return nil , ocispecs.Descriptor {}, nil , errors .Errorf ("input platform %s does not match any of the provided platforms" , platforms .Format (* desc .Platform ))
356+ }
357+ return dt , desc , nil , nil
358+ }
359+
360+ var idx ocispecs.Index
361+ if err := json .Unmarshal (dt , & idx ); err != nil {
362+ return nil , ocispecs.Descriptor {}, nil , errors .Wrapf (err , "failed to parse index" )
363+ }
364+
365+ var manifestMap = map [digest.Digest ]ocispecs.Descriptor {}
366+ for _ , m := range idx .Manifests {
367+ manifestMap [m .Digest ] = m
368+ }
369+ var references = map [digest.Digest ]ocispecs.Descriptor {}
370+ var matchedManifests = map [digest.Digest ]struct {}{}
371+ for _ , m := range idx .Manifests {
372+ if m .Platform == nil || matcher .Match (* m .Platform ) {
373+ matchedManifests [m .Digest ] = struct {}{}
374+ }
375+ if refType , ok := m .Annotations [attestation .DockerAnnotationReferenceType ]; ok && refType == attestation .DockerAnnotationReferenceTypeDefault {
376+ dgstStr , ok := m .Annotations [attestation .DockerAnnotationReferenceDigest ]
377+ if ! ok {
378+ continue
379+ }
380+ dgst , err := digest .Parse (dgstStr )
381+ if err != nil {
382+ continue
383+ }
384+ subject , ok := manifestMap [dgst ]
385+ if ! ok {
386+ continue
387+ }
388+ if subject .Platform == nil || matcher .Match (* subject .Platform ) {
389+ references [m .Digest ] = subject
390+ }
391+ }
392+ }
393+
394+ var mfsts []ocispecs.Descriptor
395+ var mfstsWithSource []DescWithSource
396+
397+ for _ , m := range idx .Manifests {
398+ _ , isRef := references [m .Digest ]
399+ if isRef || m .Platform == nil || matcher .Match (* m .Platform ) {
400+ src , ok := srcMap [m .Digest ]
401+ if ! ok {
402+ defaultSource , ok := srcMap [desc .Digest ]
403+ if ! ok {
404+ return nil , ocispecs.Descriptor {}, nil , errors .Errorf ("internal error: no source found for %s" , m .Digest )
405+ }
406+ src = defaultSource
407+ }
408+ mfsts = append (mfsts , m )
409+ mfstsWithSource = append (mfstsWithSource , DescWithSource {
410+ Descriptor : m ,
411+ Source : src ,
412+ })
413+ }
414+ }
415+
416+ if len (mfsts ) == 0 {
417+ return nil , ocispecs.Descriptor {}, nil , errors .Errorf ("none of the manifests match the provided platforms" )
418+ }
419+
420+ // try to pull in attestation manifest via referrer if one exists
421+ addedRef := false
422+ for d := range matchedManifests {
423+ hasRef := false
424+ for _ , subject := range references {
425+ if subject .Digest == d {
426+ hasRef = true
427+ break
428+ }
429+ }
430+ if hasRef {
431+ continue
432+ }
433+ f , err := r .resolver ().Fetcher (ctx , srcMap [d ].Ref .String ())
434+ if err != nil {
435+ return nil , ocispecs.Descriptor {}, nil , err
436+ }
437+ rf , ok := f .(remotes.ReferrersFetcher )
438+ if ! ok {
439+ return nil , ocispecs.Descriptor {}, nil , errors .Errorf ("fetcher for %s does not support referrers" , srcMap [d ].Ref .String ())
440+ }
441+ refs , err := rf .FetchReferrers (ctx , d , artifactTypeAttestationManifest )
442+ if err != nil {
443+ if errors .Is (err , errdefs .ErrNotFound ) {
444+ continue
445+ }
446+ return nil , ocispecs.Descriptor {}, nil , err
447+ }
448+ for _ , ref := range refs {
449+ if _ , ok := references [ref .Digest ]; ok {
450+ continue
451+ }
452+ ref .Platform = & ocispecs.Platform {
453+ OS : "unknown" , Architecture : "unknown" ,
454+ }
455+ if ref .Annotations == nil {
456+ ref .Annotations = map [string ]string {}
457+ }
458+ ref .Annotations [attestation .DockerAnnotationReferenceType ] = attestation .DockerAnnotationReferenceTypeDefault
459+ ref .Annotations [attestation .DockerAnnotationReferenceDigest ] = d .String ()
460+ ref .ArtifactType = ""
461+ mfsts = append (mfsts , ref )
462+ addedRef = true
463+ break
464+ }
465+ }
466+
467+ if len (mfsts ) == len (idx .Manifests ) && ! addedRef {
468+ // all platforms matched, no need to rewrite index
469+ return dt , desc , mfstsWithSource , nil
470+ }
471+
472+ idx .Manifests = mfsts
473+ idxBytes , err := json .MarshalIndent (& idx , "" , " " )
474+ if err != nil {
475+ return nil , ocispecs.Descriptor {}, nil , errors .Wrap (err , "failed to marshal index" )
476+ }
477+
478+ desc = ocispecs.Descriptor {
479+ MediaType : desc .MediaType ,
480+ Size : int64 (len (idxBytes )),
481+ Digest : digest .FromBytes (idxBytes ),
482+ Annotations : desc .Annotations ,
483+ }
484+
485+ return idxBytes , desc , mfstsWithSource , nil
486+ }
487+
291488func detectMediaType (dt []byte ) (string , error ) {
292489 var mfst struct {
293490 MediaType string `json:"mediaType"`
0 commit comments