@@ -15,6 +15,7 @@ import (
15
15
"github.com/containerd/platforms"
16
16
"github.com/distribution/reference"
17
17
"github.com/moby/buildkit/exporter/containerimage/exptypes"
18
+ "github.com/moby/buildkit/util/attestation"
18
19
"github.com/moby/buildkit/util/contentutil"
19
20
"github.com/opencontainers/go-digest"
20
21
"github.com/opencontainers/image-spec/specs-go"
@@ -23,12 +24,34 @@ import (
23
24
"golang.org/x/sync/errgroup"
24
25
)
25
26
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
+
26
37
type Source struct {
27
38
Desc ocispecs.Descriptor
28
39
Ref reference.Named
29
40
}
30
41
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 ) {
32
55
eg , ctx := errgroup .WithContext (ctx )
33
56
34
57
dts := make ([][]byte , len (srcs ))
@@ -250,7 +273,28 @@ func (r *Resolver) Copy(ctx context.Context, src *Source, dest reference.Named)
250
273
source , repo := u .Hostname (), strings .TrimPrefix (u .Path , "/" )
251
274
desc .Annotations ["containerd.io/distribution.source." + source ] = repo
252
275
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 ... )
254
298
if err != nil {
255
299
return err
256
300
}
@@ -288,6 +332,159 @@ func (r *Resolver) loadPlatform(ctx context.Context, p2 *ocispecs.Platform, in s
288
332
return nil
289
333
}
290
334
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
+
291
488
func detectMediaType (dt []byte ) (string , error ) {
292
489
var mfst struct {
293
490
MediaType string `json:"mediaType"`
0 commit comments