Skip to content

Commit 4ab3e37

Browse files
authored
Merge pull request #6330 from tonistiigi/image-source-metadata
image: move image source resolver away from old interface
2 parents 5b97613 + 2fc7854 commit 4ab3e37

File tree

37 files changed

+3334
-1059
lines changed

37 files changed

+3334
-1059
lines changed

client/client_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
250250
testHTTPResolveMetaReuse,
251251
testHTTPResolveMultiBuild,
252252
testGitResolveMutatedSource,
253+
testImageResolveAttestationChainRequiresNetwork,
253254
}
254255

255256
func TestIntegration(t *testing.T) {
@@ -12249,6 +12250,92 @@ func testHTTPResolveSourceMetadata(t *testing.T, sb integration.Sandbox) {
1224912250
require.NoError(t, err)
1225012251
}
1225112252

12253+
func testImageResolveAttestationChainRequiresNetwork(t *testing.T, sb integration.Sandbox) {
12254+
// this test temporarily requires direct registry access as the integration test
12255+
// mirroring system does not support mirroring attestation chains yet.
12256+
// Support is coming in future buildx release.
12257+
ctx := sb.Context()
12258+
c, err := New(ctx, sb.Address())
12259+
require.NoError(t, err)
12260+
defer c.Close()
12261+
12262+
amd64, err := platforms.Parse("linux/amd64")
12263+
require.NoError(t, err)
12264+
12265+
_, err = c.Build(ctx, SolveOpt{}, "test", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
12266+
const rootDigest = "sha256:4e91099af134f4b1f509fecbd1a55981dfff18d8029d6782e28413210ef468b7"
12267+
const imageDigest = "sha256:d2f0b39234a66c3d58f24200d3fda9e4e3d2263c09f9b7286a826ab639713047"
12268+
const attestationDigest = "sha256:fb4c46b14f52d1bf790f593921c52dddc698fc6780792fb8469fc60efc0e609b"
12269+
const sigDigest = "sha256:bd0a6b088440ba9838e8eec79e736128fe52afce934578eae30ca8675f6d3142"
12270+
12271+
id := "registry-1-stage.docker.io/docker/github-builder-test@" + rootDigest
12272+
md, err := c.ResolveSourceMetadata(ctx, &pb.SourceOp{
12273+
Identifier: "docker-image://" + id,
12274+
}, sourceresolver.Opt{
12275+
ImageOpt: &sourceresolver.ResolveImageOpt{
12276+
NoConfig: true,
12277+
AttestationChain: true,
12278+
Platform: &amd64,
12279+
},
12280+
})
12281+
if err != nil {
12282+
return nil, err
12283+
}
12284+
require.Equal(t, rootDigest, md.Image.Digest.String())
12285+
require.Nil(t, md.Image.Config)
12286+
require.NotNil(t, md.Image)
12287+
require.NotNil(t, md.Image.AttestationChain)
12288+
ac := md.Image.AttestationChain
12289+
require.Equal(t, rootDigest, ac.Root.String())
12290+
require.Equal(t, imageDigest, ac.ImageManifest.String())
12291+
require.Equal(t, attestationDigest, ac.AttestationManifest.String())
12292+
require.Len(t, ac.SignatureManifests, 1)
12293+
require.Equal(t, sigDigest, ac.SignatureManifests[0].String())
12294+
12295+
desc := ac.Blobs[ac.Root]
12296+
require.Equal(t, rootDigest, desc.Descriptor.Digest.String())
12297+
require.Len(t, desc.Data, int(desc.Descriptor.Size))
12298+
require.Equal(t, ocispecs.MediaTypeImageIndex, desc.Descriptor.MediaType)
12299+
chk := digest.FromBytes(desc.Data)
12300+
require.Equal(t, rootDigest, chk.String())
12301+
12302+
desc = ac.Blobs[ac.ImageManifest]
12303+
// image manifest is expected to be missing as content is not needed to verify attestation chain
12304+
_, ok := ac.Blobs[digest.Digest(imageDigest)]
12305+
require.False(t, ok)
12306+
12307+
desc = ac.Blobs[ac.AttestationManifest]
12308+
require.Equal(t, attestationDigest, desc.Descriptor.Digest.String())
12309+
require.Equal(t, ocispecs.MediaTypeImageManifest, desc.Descriptor.MediaType)
12310+
require.Len(t, desc.Data, int(desc.Descriptor.Size))
12311+
chk = digest.FromBytes(desc.Data)
12312+
require.Equal(t, attestationDigest, chk.String())
12313+
12314+
desc = ac.Blobs[ac.SignatureManifests[0]]
12315+
require.Equal(t, sigDigest, desc.Descriptor.Digest.String())
12316+
require.Equal(t, ocispecs.MediaTypeImageManifest, desc.Descriptor.MediaType)
12317+
require.Len(t, desc.Data, int(desc.Descriptor.Size))
12318+
chk = digest.FromBytes(desc.Data)
12319+
require.Equal(t, sigDigest, chk.String())
12320+
12321+
var sigMfst ocispecs.Manifest
12322+
err = json.Unmarshal(ac.Blobs[ac.SignatureManifests[0]].Data, &sigMfst)
12323+
require.NoError(t, err)
12324+
require.Equal(t, 1, len(sigMfst.Layers))
12325+
sigLayer := sigMfst.Layers[0]
12326+
sigDesc := ac.Blobs[digest.Digest(sigLayer.Digest)]
12327+
require.Equal(t, sigLayer.Digest, sigDesc.Descriptor.Digest)
12328+
require.Len(t, sigDesc.Data, int(sigDesc.Descriptor.Size))
12329+
require.Equal(t, "application/vnd.dev.sigstore.bundle.v0.3+json", sigDesc.Descriptor.MediaType)
12330+
chk = digest.FromBytes(sigDesc.Data)
12331+
require.Equal(t, sigLayer.Digest, chk)
12332+
12333+
require.Len(t, md.Image.AttestationChain.Blobs, 4)
12334+
return nil, nil
12335+
}, nil)
12336+
require.NoError(t, err)
12337+
}
12338+
1225212339
func testHTTPPruneAfterCacheKey(t *testing.T, sb integration.Sandbox) {
1225312340
// this test depends on hitting race condition in internal functions.
1225412341
// If debugging and expecting failure you can add small sleep in beginning of source/http.Exec() to hit reliably

client/llb/imagemetaresolver/resolver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ func (imr *imageMetaResolver) ResolveImageConfig(ctx context.Context, ref string
8686
defer imr.locker.Unlock(ref)
8787

8888
platform := imr.platform
89-
if opt.Platform != nil {
90-
platform = opt.Platform
89+
if imgOpt := opt.ImageOpt; imgOpt != nil && imgOpt.Platform != nil {
90+
platform = imgOpt.Platform
9191
}
9292

9393
k := imr.key(ref, platform)

client/llb/resolver_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ func (r *testResolver) ResolveImageConfig(ctx context.Context, ref string, opt s
8787

8888
img.Config.WorkingDir = r.dir
8989

90-
if opt.Platform != nil {
91-
r.platform = platforms.Format(*opt.Platform)
90+
if imgOpt := opt.ImageOpt; imgOpt != nil && imgOpt.Platform != nil {
91+
r.platform = platforms.Format(*imgOpt.Platform)
9292
}
9393

9494
dt, err := json.Marshal(img)

client/llb/source.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ func Image(ref string, opts ...ImageOption) State {
146146
p = c.Platform
147147
}
148148
_, _, dt, err := info.metaResolver.ResolveImageConfig(ctx, ref, sourceresolver.Opt{
149-
Platform: p,
150149
ImageOpt: &sourceresolver.ResolveImageOpt{
150+
Platform: p,
151151
ResolveMode: info.resolveMode.String(),
152152
},
153153
})
@@ -163,8 +163,8 @@ func Image(ref string, opts ...ImageOption) State {
163163
p = c.Platform
164164
}
165165
ref, dgst, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, sourceresolver.Opt{
166-
Platform: p,
167166
ImageOpt: &sourceresolver.ResolveImageOpt{
167+
Platform: p,
168168
ResolveMode: info.resolveMode.String(),
169169
},
170170
})

client/llb/sourceresolver/types.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ type MetaResolver interface {
2424
type Opt struct {
2525
LogName string
2626
SourcePolicies []*spb.Policy
27-
Platform *ocispecs.Platform
2827

2928
ImageOpt *ResolveImageOpt
3029
OCILayoutOpt *ResolveOCILayoutOpt
@@ -40,12 +39,29 @@ type MetaResponse struct {
4039
}
4140

4241
type ResolveImageOpt struct {
43-
ResolveMode string
42+
Platform *ocispecs.Platform
43+
ResolveMode string
44+
NoConfig bool
45+
AttestationChain bool
4446
}
4547

4648
type ResolveImageResponse struct {
47-
Digest digest.Digest
48-
Config []byte
49+
Digest digest.Digest
50+
Config []byte
51+
AttestationChain *AttestationChain
52+
}
53+
54+
type AttestationChain struct {
55+
Root digest.Digest
56+
ImageManifest digest.Digest
57+
AttestationManifest digest.Digest
58+
SignatureManifests []digest.Digest
59+
Blobs map[digest.Digest]Blob
60+
}
61+
62+
type Blob struct {
63+
Descriptor ocispecs.Descriptor
64+
Data []byte
4965
}
5066

5167
type ResolveGitOpt struct {
@@ -67,7 +83,8 @@ type ResolveHTTPResponse struct {
6783
}
6884

6985
type ResolveOCILayoutOpt struct {
70-
Store ResolveImageConfigOptStore
86+
Platform *ocispecs.Platform
87+
Store ResolveImageConfigOptStore
7188
}
7289

7390
type ResolveImageConfigOptStore struct {

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,9 +541,9 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
541541
}
542542
prefix += "internal]"
543543
mutRef, dgst, dt, err := metaResolver.ResolveImageConfig(ctx, d.stage.BaseName, sourceresolver.Opt{
544-
LogName: fmt.Sprintf("%s load metadata for %s", prefix, d.stage.BaseName),
545-
Platform: platform,
544+
LogName: fmt.Sprintf("%s load metadata for %s", prefix, d.stage.BaseName),
546545
ImageOpt: &sourceresolver.ResolveImageOpt{
546+
Platform: platform,
547547
ResolveMode: opt.ImageResolveMode.String(),
548548
},
549549
})

frontend/dockerui/namedcontext.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ func (nc *NamedContext) load(ctx context.Context, count int) (*llb.State, *docke
9494
named = reference.TagNameOnly(named)
9595

9696
ref, dgst, data, err := nc.bc.client.ResolveImageConfig(ctx, named.String(), sourceresolver.Opt{
97-
LogName: fmt.Sprintf("[context %s] load metadata for %s", nc.nameWithPlatform, ref),
98-
Platform: opt.Platform,
97+
LogName: fmt.Sprintf("[context %s] load metadata for %s", nc.nameWithPlatform, ref),
9998
ImageOpt: &sourceresolver.ResolveImageOpt{
99+
Platform: opt.Platform,
100100
ResolveMode: opt.ResolveMode,
101101
},
102102
})
@@ -183,9 +183,9 @@ func (nc *NamedContext) load(ctx context.Context, count int) (*llb.State, *docke
183183
}
184184

185185
_, dgst, data, err := nc.bc.client.ResolveImageConfig(ctx, dummyRef.String(), sourceresolver.Opt{
186-
LogName: fmt.Sprintf("[context %s] load metadata for %s", nc.nameWithPlatform, dummyRef.String()),
187-
Platform: opt.Platform,
186+
LogName: fmt.Sprintf("[context %s] load metadata for %s", nc.nameWithPlatform, dummyRef.String()),
188187
OCILayoutOpt: &sourceresolver.ResolveOCILayoutOpt{
188+
Platform: opt.Platform,
189189
Store: sourceresolver.ResolveImageConfigOptStore{
190190
SessionID: nc.bc.bopts.SessionID,
191191
StoreID: named.Name(),

frontend/gateway/gateway.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,10 +631,17 @@ func (lbf *llbBridgeForwarder) ResolveSourceMeta(ctx context.Context, req *pb.Re
631631
resolveopt := sourceresolver.Opt{
632632
LogName: req.LogName,
633633
SourcePolicies: req.SourcePolicies,
634-
Platform: platform,
635634
}
636635
resolveopt.ImageOpt = &sourceresolver.ResolveImageOpt{
637636
ResolveMode: req.ResolveMode,
637+
Platform: platform,
638+
}
639+
if req.Image != nil {
640+
resolveopt.ImageOpt.NoConfig = req.Image.NoConfig
641+
resolveopt.ImageOpt.AttestationChain = req.Image.AttestationChain
642+
}
643+
resolveopt.OCILayoutOpt = &sourceresolver.ResolveOCILayoutOpt{
644+
Platform: platform,
638645
}
639646
if req.Git != nil {
640647
resolveopt.GitOpt = &sourceresolver.ResolveGitOpt{
@@ -656,6 +663,9 @@ func (lbf *llbBridgeForwarder) ResolveSourceMeta(ctx context.Context, req *pb.Re
656663
Digest: string(resp.Image.Digest),
657664
Config: resp.Image.Config,
658665
}
666+
if resp.Image.AttestationChain != nil {
667+
r.Image.AttestationChain = toPBAttestationChain(resp.Image.AttestationChain)
668+
}
659669
}
660670
if resp.Git != nil {
661671
r.Git = &pb.ResolveSourceGitResponse{
@@ -698,18 +708,19 @@ func (lbf *llbBridgeForwarder) ResolveImageConfig(ctx context.Context, req *pb.R
698708
resolveopt := sourceresolver.Opt{
699709
LogName: req.LogName,
700710
SourcePolicies: req.SourcePolicies,
701-
Platform: platform,
702711
}
703712
if sourceresolver.ResolverType(req.ResolverType) == sourceresolver.ResolverTypeRegistry {
704713
resolveopt.ImageOpt = &sourceresolver.ResolveImageOpt{
705714
ResolveMode: req.ResolveMode,
715+
Platform: platform,
706716
}
707717
} else if sourceresolver.ResolverType(req.ResolverType) == sourceresolver.ResolverTypeOCILayout {
708718
resolveopt.OCILayoutOpt = &sourceresolver.ResolveOCILayoutOpt{
709719
Store: sourceresolver.ResolveImageConfigOptStore{
710720
SessionID: req.SessionID,
711721
StoreID: req.StoreID,
712722
},
723+
Platform: platform,
713724
}
714725
}
715726

@@ -1694,6 +1705,33 @@ func getCaps(label string) map[string]struct{} {
16941705
return out
16951706
}
16961707

1708+
func toPBAttestationChain(ac *sourceresolver.AttestationChain) *pb.AttestationChain {
1709+
if ac == nil {
1710+
return nil
1711+
}
1712+
out := &pb.AttestationChain{
1713+
Root: string(ac.Root),
1714+
ImageManifest: string(ac.ImageManifest),
1715+
AttestationManifest: string(ac.AttestationManifest),
1716+
Blobs: make(map[string]*pb.Blob),
1717+
}
1718+
for _, s := range ac.SignatureManifests {
1719+
out.SignatureManifests = append(out.SignatureManifests, string(s))
1720+
}
1721+
for k, v := range ac.Blobs {
1722+
out.Blobs[k.String()] = &pb.Blob{
1723+
Descriptor_: &pb.Descriptor{
1724+
MediaType: v.Descriptor.MediaType,
1725+
Size: v.Descriptor.Size,
1726+
Digest: string(v.Descriptor.Digest),
1727+
Annotations: maps.Clone(v.Descriptor.Annotations),
1728+
},
1729+
Data: v.Data,
1730+
}
1731+
}
1732+
return out
1733+
}
1734+
16971735
func addCapsForKnownFrontends(caps map[string]struct{}, dgst digest.Digest) {
16981736
// these frontends were built without caps detection but do support inputs
16991737
defaults := map[digest.Digest]struct{}{

0 commit comments

Comments
 (0)