Skip to content

Commit c481eaf

Browse files
committed
The initial work on supporting multiple concurrent exporters was
unfinished in the sense that exporter responses (which are abstracted as a map of key/value pairs) are combined into a single map at the end of the export: https://github.com/moby/buildkit/blob/55a7483b0564a7ad5b2ce5e62512789dce327bca/solver/llbsolver/solver.go#L808-L809 In order to provide the correct exporter response, each response (currently at least each container image exporter) needs to be dedicated to the exporter instance. To achieve this, assign and propagate exporter IDs from the client and have corresponding exporters annotate their responses with the respective ID so the client can order outputs per exporter instance. Fixes moby#5556.
1 parent 7d7a919 commit c481eaf

File tree

10 files changed

+90
-57
lines changed

10 files changed

+90
-57
lines changed

client/solve.go

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"maps"
99
"os"
10+
"strconv"
1011
"strings"
1112
"time"
1213

@@ -130,7 +131,8 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG
130131
return nil, err
131132
}
132133

133-
storesToUpdate := []string{}
134+
// maps image exporter id -> store path
135+
storesToUpdate := make(map[string]ociStore)
134136

135137
if !opt.SessionPreInitialized {
136138
if len(syncedDirs) > 0 {
@@ -195,7 +197,7 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG
195197
return nil, err
196198
}
197199
contentStores["export"] = cs
198-
storesToUpdate = append(storesToUpdate, ex.OutputDir)
200+
storesToUpdate[strconv.Itoa(exID)] = ociStore{path: ex.OutputDir}
199201
default:
200202
syncTargets = append(syncTargets, filesync.WithFSSyncDir(exID, ex.OutputDir))
201203
}
@@ -260,6 +262,8 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG
260262
exportDeprecated = exp.Type
261263
exportAttrDeprecated = exp.Attrs
262264
}
265+
// FIXME(dima): make this a dedicated attribute on the Exporter
266+
exp.Attrs[exptypes.ClientKeyID] = strconv.Itoa(i)
263267
exports = append(exports, &controlapi.Exporter{
264268
Type: exp.Type,
265269
Attrs: exp.Attrs,
@@ -350,29 +354,56 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG
350354
}
351355
}
352356
}
353-
if manifestDescDt := res.ExporterResponse[exptypes.ExporterImageDescriptorKey]; manifestDescDt != "" {
354-
manifestDescDt, err := base64.StdEncoding.DecodeString(manifestDescDt)
357+
358+
if len(storesToUpdate) == 0 {
359+
return res, nil
360+
}
361+
for id, store := range storesToUpdate {
362+
manifestDesc, err := getManifestDescriptor(id, res.ExporterResponse)
355363
if err != nil {
356364
return nil, err
357365
}
358-
var manifestDesc ocispecs.Descriptor
359-
if err = json.Unmarshal([]byte(manifestDescDt), &manifestDesc); err != nil {
360-
return nil, err
366+
if manifestDesc == nil {
367+
continue
361368
}
362-
for _, storePath := range storesToUpdate {
363-
tag := "latest"
364-
if t, ok := res.ExporterResponse["image.name"]; ok {
365-
tag = t
366-
}
367-
idx := ociindex.NewStoreIndex(storePath)
368-
if err := idx.Put(tag, manifestDesc); err != nil {
369-
return nil, err
370-
}
369+
tag := "latest"
370+
if t, ok := res.ExporterResponse["image.name"]; ok {
371+
tag = t
372+
}
373+
idx := ociindex.NewStoreIndex(store.path)
374+
if err := idx.Put(tag, *manifestDesc); err != nil {
375+
return nil, err
371376
}
372377
}
373378
return res, nil
374379
}
375380

381+
func getManifestDescriptor(exporterID string, resp map[string]string) (*ocispecs.Descriptor, error) {
382+
if manifestDescDt := resp[exptypes.FormatImageDescriptorKey(exporterID)]; manifestDescDt != "" {
383+
return unmarshalManifestDescriptor(manifestDescDt)
384+
}
385+
if manifestDescDt := resp[exptypes.ExporterImageDescriptorKey]; manifestDescDt != "" {
386+
return unmarshalManifestDescriptor(manifestDescDt)
387+
}
388+
return nil, nil
389+
}
390+
391+
func unmarshalManifestDescriptor(manifestDesc string) (*ocispecs.Descriptor, error) {
392+
manifestDescDt, err := base64.StdEncoding.DecodeString(manifestDesc)
393+
if err != nil {
394+
return nil, err
395+
}
396+
var desc ocispecs.Descriptor
397+
if err = json.Unmarshal([]byte(manifestDescDt), &desc); err != nil {
398+
return nil, err
399+
}
400+
return &desc, nil
401+
}
402+
403+
type ociStore struct {
404+
path string
405+
}
406+
376407
func prepareSyncedFiles(def *llb.Definition, localMounts map[string]fsutil.FS) (filesync.StaticDirSource, error) {
377408
resetUIDAndGID := func(p string, st *fstypes.Stat) fsutil.MapResult {
378409
st.Uid = 0

control/control.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,13 +400,13 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
400400
}
401401

402402
var expis []exporter.ExporterInstance
403-
for i, ex := range req.Exporters {
403+
for _, ex := range req.Exporters {
404404
exp, err := w.Exporter(ex.Type, c.opt.SessionManager)
405405
if err != nil {
406406
return nil, err
407407
}
408408
bklog.G(ctx).Debugf("resolve exporter %s with %v", ex.Type, ex.Attrs)
409-
expi, err := exp.Resolve(ctx, i, ex.Attrs)
409+
expi, err := exp.Resolve(ctx, ex.Attrs)
410410
if err != nil {
411411
return nil, err
412412
}

exporter/containerimage/export.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,9 @@ func New(opt Opt) (exporter.Exporter, error) {
6666
return im, nil
6767
}
6868

69-
func (e *imageExporter) Resolve(ctx context.Context, id int, opt map[string]string) (exporter.ExporterInstance, error) {
69+
func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
7070
i := &imageExporterInstance{
7171
imageExporter: e,
72-
id: id,
7372
attrs: opt,
7473
opts: ImageCommitOpts{
7574
RefCfg: cacheconfig.RefConfig{
@@ -87,6 +86,8 @@ func (e *imageExporter) Resolve(ctx context.Context, id int, opt map[string]stri
8786

8887
for k, v := range opt {
8988
switch exptypes.ImageExporterOptKey(k) {
89+
case exptypes.ClientKeyID:
90+
i.id = v
9091
case exptypes.OptKeyPush:
9192
if v == "" {
9293
i.push = true
@@ -171,7 +172,7 @@ func (e *imageExporter) Resolve(ctx context.Context, id int, opt map[string]stri
171172

172173
type imageExporterInstance struct {
173174
*imageExporter
174-
id int
175+
id string
175176
attrs map[string]string
176177

177178
opts ImageCommitOpts
@@ -186,10 +187,6 @@ type imageExporterInstance struct {
186187
meta map[string][]byte
187188
}
188189

189-
func (e *imageExporterInstance) ID() int {
190-
return e.id
191-
}
192-
193190
func (e *imageExporterInstance) Name() string {
194191
return "exporting to image"
195192
}
@@ -357,7 +354,7 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
357354
if err != nil {
358355
return nil, nil, err
359356
}
360-
resp[exptypes.ExporterImageDescriptorKey] = base64.StdEncoding.EncodeToString(dtdesc)
357+
resp[exptypes.FormatImageDescriptorKey(e.id)] = base64.StdEncoding.EncodeToString(dtdesc)
361358

362359
return resp, nil, nil
363360
}

exporter/containerimage/exptypes/keys.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package exptypes
22

3-
import commonexptypes "github.com/moby/buildkit/exporter/exptypes"
3+
import (
4+
"fmt"
5+
6+
commonexptypes "github.com/moby/buildkit/exporter/exptypes"
7+
)
48

59
type ImageExporterOptKey string
610

@@ -77,3 +81,12 @@ var (
7781
// Value: bool <true|false>
7882
OptKeyRewriteTimestamp ImageExporterOptKey = "rewrite-timestamp"
7983
)
84+
85+
const (
86+
// ClientKeyID optionally identifies the exporter
87+
ClientKeyID = "__clientid"
88+
)
89+
90+
func FormatImageDescriptorKey(id string) string {
91+
return fmt.Sprint(ExporterImageDescriptorKey, "-", id)
92+
}

exporter/exporter.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ type Source = result.Result[cache.ImmutableRef]
1515
type Attestation = result.Attestation[cache.ImmutableRef]
1616

1717
type Exporter interface {
18-
Resolve(context.Context, int, map[string]string) (ExporterInstance, error)
18+
Resolve(context.Context, map[string]string) (ExporterInstance, error)
1919
}
2020

2121
type ExporterInstance interface {
22-
ID() int
2322
Name() string
2423
Config() *Config
2524
Type() string

exporter/exptypes/keys.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const (
77
type ExporterOptKey string
88

99
// Options keys supported by all exporters.
10-
var (
10+
const (
1111
// Clamp produced timestamps. For more information see the
1212
// SOURCE_DATE_EPOCH specification.
1313
// Value: int (number of seconds since Unix epoch)

exporter/local/export.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ func New(opt Opt) (exporter.Exporter, error) {
3636
return le, nil
3737
}
3838

39-
func (e *localExporter) Resolve(ctx context.Context, id int, opt map[string]string) (exporter.ExporterInstance, error) {
39+
func (e *localExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
4040
i := &localExporterInstance{
41-
id: id,
4241
attrs: opt,
4342
localExporter: e,
4443
}
@@ -47,21 +46,21 @@ func (e *localExporter) Resolve(ctx context.Context, id int, opt map[string]stri
4746
return nil, err
4847
}
4948

49+
if id, ok := opt[exptypes.ClientKeyID]; ok {
50+
i.id = id
51+
}
52+
5053
return i, nil
5154
}
5255

5356
type localExporterInstance struct {
5457
*localExporter
55-
id int
58+
id string
5659
attrs map[string]string
5760

5861
opts CreateFSOpts
5962
}
6063

61-
func (e *localExporterInstance) ID() int {
62-
return e.id
63-
}
64-
6564
func (e *localExporterInstance) Name() string {
6665
return "exporting to client directory"
6766
}

exporter/oci/export.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,9 @@ func New(opt Opt) (exporter.Exporter, error) {
6060
return im, nil
6161
}
6262

63-
func (e *imageExporter) Resolve(ctx context.Context, id int, opt map[string]string) (exporter.ExporterInstance, error) {
63+
func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
6464
i := &imageExporterInstance{
6565
imageExporter: e,
66-
id: id,
6766
attrs: opt,
6867
tar: true,
6968
opts: containerimage.ImageCommitOpts{
@@ -81,6 +80,8 @@ func (e *imageExporter) Resolve(ctx context.Context, id int, opt map[string]stri
8180

8281
for k, v := range opt {
8382
switch k {
83+
case exptypes.ClientKeyID:
84+
i.id = v
8485
case keyTar:
8586
if v == "" {
8687
i.tar = true
@@ -103,18 +104,14 @@ func (e *imageExporter) Resolve(ctx context.Context, id int, opt map[string]stri
103104

104105
type imageExporterInstance struct {
105106
*imageExporter
106-
id int
107+
id string
107108
attrs map[string]string
108109

109110
opts containerimage.ImageCommitOpts
110111
tar bool
111112
meta map[string][]byte
112113
}
113114

114-
func (e *imageExporterInstance) ID() int {
115-
return e.id
116-
}
117-
118115
func (e *imageExporterInstance) Name() string {
119116
return fmt.Sprintf("exporting to %s image format", e.opt.Variant)
120117
}
@@ -192,7 +189,7 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
192189
if err != nil {
193190
return nil, nil, err
194191
}
195-
resp[exptypes.ExporterImageDescriptorKey] = base64.StdEncoding.EncodeToString(dtdesc)
192+
resp[exptypes.FormatImageDescriptorKey(e.id)] = base64.StdEncoding.EncodeToString(dtdesc)
196193

197194
if n, ok := src.Metadata["image.name"]; e.opts.ImageName == "*" && ok {
198195
e.opts.ImageName = string(n)

exporter/tar/export.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,30 @@ func New(opt Opt) (exporter.Exporter, error) {
3434
return le, nil
3535
}
3636

37-
func (e *localExporter) Resolve(ctx context.Context, id int, opt map[string]string) (exporter.ExporterInstance, error) {
37+
func (e *localExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
3838
li := &localExporterInstance{
3939
localExporter: e,
40-
id: id,
4140
attrs: opt,
4241
}
4342
_, err := li.opts.Load(opt)
4443
if err != nil {
4544
return nil, err
4645
}
47-
_ = opt
46+
if id, ok := opt[exptypes.ClientKeyID]; ok {
47+
li.id = id
48+
}
4849

4950
return li, nil
5051
}
5152

5253
type localExporterInstance struct {
5354
*localExporter
54-
id int
55+
id string
5556
attrs map[string]string
5657

5758
opts local.CreateFSOpts
5859
}
5960

60-
func (e *localExporterInstance) ID() int {
61-
return e.id
62-
}
63-
6461
func (e *localExporterInstance) Name() string {
6562
return "exporting to client tarball"
6663
}

session/filesync/filesync.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ func (sp *fsSyncAttachable) DiffCopy(stream FileSend_DiffCopyServer) (err error)
340340
return writeTargetFile(stream, wc)
341341
}
342342

343-
func CopyToCaller(ctx context.Context, fs fsutil.FS, id int, c session.Caller, progress func(int, bool)) error {
343+
func CopyToCaller(ctx context.Context, fs fsutil.FS, id string, c session.Caller, progress func(int, bool)) error {
344344
method := session.MethodURL(FileSend_ServiceDesc.ServiceName, "diffcopy")
345345
if !c.Supports(method) {
346346
return errors.Errorf("method %s not supported by the client", method)
@@ -355,7 +355,7 @@ func CopyToCaller(ctx context.Context, fs fsutil.FS, id int, c session.Caller, p
355355
if existingVal, ok := opts[keyExporterID]; ok {
356356
bklog.G(ctx).Warnf("overwriting grpc metadata key %q from value %+v to %+v", keyExporterID, existingVal, id)
357357
}
358-
opts[keyExporterID] = []string{fmt.Sprint(id)}
358+
opts[keyExporterID] = []string{id}
359359
ctx = metadata.NewOutgoingContext(ctx, opts)
360360

361361
cc, err := client.DiffCopy(ctx)
@@ -366,7 +366,7 @@ func CopyToCaller(ctx context.Context, fs fsutil.FS, id int, c session.Caller, p
366366
return sendDiffCopy(cc, fs, progress)
367367
}
368368

369-
func CopyFileWriter(ctx context.Context, md map[string]string, id int, c session.Caller) (io.WriteCloser, error) {
369+
func CopyFileWriter(ctx context.Context, md map[string]string, id string, c session.Caller) (io.WriteCloser, error) {
370370
method := session.MethodURL(FileSend_ServiceDesc.ServiceName, "diffcopy")
371371
if !c.Supports(method) {
372372
return nil, errors.Errorf("method %s not supported by the client", method)
@@ -388,7 +388,7 @@ func CopyFileWriter(ctx context.Context, md map[string]string, id int, c session
388388
if existingVal, ok := opts[keyExporterID]; ok {
389389
bklog.G(ctx).Warnf("overwriting grpc metadata key %q from value %+v to %+v", keyExporterID, existingVal, id)
390390
}
391-
opts[keyExporterID] = []string{fmt.Sprint(id)}
391+
opts[keyExporterID] = []string{id}
392392
ctx = metadata.NewOutgoingContext(ctx, opts)
393393

394394
cc, err := client.DiffCopy(ctx)

0 commit comments

Comments
 (0)