Skip to content

Commit 4635e8c

Browse files
committed
Add support for disambiguating multiple responses for cache exporters
by adding IDs similar to output exporters. Signed-off-by: a-palchikov <deemok@gmail.com>
1 parent 4a9d08b commit 4635e8c

File tree

7 files changed

+410
-152
lines changed

7 files changed

+410
-152
lines changed

api/services/control/control.pb.go

Lines changed: 238 additions & 88 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/services/control/control.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ message CacheOptionsEntry {
102102
// Attrs are like mode=(min,max), ref=example.com:5000/foo/bar .
103103
// See cache importer/exporter implementations' documentation.
104104
map<string, string> Attrs = 2;
105+
// ID identifies this exporter.
106+
// ID should be treated by the exporter as opaque.
107+
string ID = 3;
105108
}
106109

107110
message SolveResponse {

api/services/control/control_vtproto.pb.go

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/solve.go

Lines changed: 103 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -47,50 +47,63 @@ type SolveOpt struct {
4747
CacheImports []CacheOptionsEntry
4848
Session []session.Attachable
4949
AllowedEntitlements []string
50-
// When the session is custom-initialized, ParseExporterOpts need to be used to correctly
51-
// set up the session for export.
50+
// When the session is custom-initialized, Init can be used to
51+
// set up the session for export automatically.
5252
SharedSession *session.Session // TODO: refactor to better session syncing
5353
SessionPreInitialized bool // TODO: refactor to better session syncing
5454
Internal bool
5555
SourcePolicy *spb.Policy
5656
Ref string
5757

58-
// internal exporter state
58+
// internal solver state
5959
s solverState
6060
}
6161

6262
type solverState struct {
63-
// storesToUpdate maps exporter ID -> oci store
64-
storesToUpdate map[string]ociStore
65-
cacheOpt *cacheOptions
63+
exporterOpt *exporterOptions
64+
cacheOpt *cacheOptions
6665
// Only one of runGateway or def can be set.
6766
// runGateway optionally defines the gateway callback
6867
runGateway runGatewayCB
6968
// def optionally defines the LLB definition for the client
7069
def *llb.Definition
7170
}
7271

72+
type exporterOptions struct {
73+
// storesToUpdate maps exporter ID -> oci store
74+
storesToUpdate map[string]ociStore
75+
}
76+
7377
type cacheOptions struct {
7478
options controlapi.CacheOptions
7579
contentStores map[string]content.Store // key: ID of content store ("local:" + csDir)
76-
storesToUpdate map[string]string // key: path to content store, value: tag
80+
storesToUpdate map[string]ociStore // key: exporter ID
7781
frontendAttrs map[string]string
7882
}
7983

8084
type ociStore struct {
8185
path string
86+
tag string
8287
}
8388

8489
type ExportEntry struct {
8590
Type string
8691
Attrs map[string]string
8792
Output filesync.FileOutputFunc // for ExporterOCI and ExporterDocker
8893
OutputDir string // for ExporterLocal
94+
95+
// id identifies the exporter in the configuration.
96+
// Will be assigned automatically and should not be set by the user.
97+
id string
8998
}
9099

91100
type CacheOptionsEntry struct {
92101
Type string
93102
Attrs map[string]string
103+
104+
// id identifies the exporter in the configuration.
105+
// Will be assigned automatically and should not be set by the user.
106+
id string
94107
}
95108

96109
// Solve calls Solve on the controller.
@@ -115,9 +128,32 @@ func (c *Client) Solve(ctx context.Context, def *llb.Definition, opt SolveOpt, s
115128

116129
type runGatewayCB func(ref string, s *session.Session, opts map[string]string) error
117130

118-
// ParseExporterOpts configures the specified session with the underlying exporter configuration.
131+
// Init initializes the SolveOpt.
132+
// It parses and initializes the cache exports/imports and output exporters.
133+
func (opt *SolveOpt) Init(ctx context.Context, s *session.Session) error {
134+
opt.initExporterIDs()
135+
if err := opt.parseCacheOptions(ctx); err != nil {
136+
return err
137+
}
138+
return opt.parseExporterOptions(s)
139+
}
140+
141+
func (opt *SolveOpt) initExporterIDs() {
142+
for i := range opt.Exports {
143+
opt.Exports[i].id = strconv.Itoa(i)
144+
}
145+
for i := range opt.CacheExports {
146+
opt.CacheExports[i].id = strconv.Itoa(i)
147+
}
148+
}
149+
150+
// parseExporterOptions configures the specified session with the underlying exporter configuration.
119151
// It needs to be invoked *after* ParseCacheOpts
120-
func ParseExporterOpts(opt *SolveOpt, s *session.Session) error {
152+
func (opt *SolveOpt) parseExporterOptions(s *session.Session) error {
153+
if opt.s.exporterOpt != nil {
154+
return nil
155+
}
156+
121157
mounts, err := prepareMounts(opt)
122158
if err != nil {
123159
return err
@@ -145,8 +181,9 @@ func ParseExporterOpts(opt *SolveOpt, s *session.Session) error {
145181
contentStores[key2] = store
146182
}
147183

184+
opt.s.exporterOpt = &exporterOptions{}
148185
var syncTargets []filesync.FSSyncTarget
149-
for exID, ex := range opt.Exports {
186+
for _, ex := range opt.Exports {
150187
var supportFile bool
151188
var supportDir bool
152189
switch ex.Type {
@@ -171,7 +208,7 @@ func ParseExporterOpts(opt *SolveOpt, s *session.Session) error {
171208
if ex.Output == nil {
172209
return errors.Errorf("output file writer is required for %s exporter", ex.Type)
173210
}
174-
syncTargets = append(syncTargets, filesync.WithFSSync(exID, ex.Output))
211+
syncTargets = append(syncTargets, filesync.WithFSSync(ex.id, ex.Output))
175212
}
176213
if supportDir {
177214
if ex.OutputDir == "" {
@@ -187,12 +224,12 @@ func ParseExporterOpts(opt *SolveOpt, s *session.Session) error {
187224
return err
188225
}
189226
contentStores["export"] = cs
190-
if opt.s.storesToUpdate == nil {
191-
opt.s.storesToUpdate = make(map[string]ociStore)
227+
if opt.s.exporterOpt.storesToUpdate == nil {
228+
opt.s.exporterOpt.storesToUpdate = make(map[string]ociStore)
192229
}
193-
opt.s.storesToUpdate[strconv.Itoa(exID)] = ociStore{path: ex.OutputDir}
230+
opt.s.exporterOpt.storesToUpdate[ex.id] = ociStore{path: ex.OutputDir}
194231
default:
195-
syncTargets = append(syncTargets, filesync.WithFSSyncDir(exID, ex.OutputDir))
232+
syncTargets = append(syncTargets, filesync.WithFSSyncDir(ex.id, ex.OutputDir))
196233
}
197234
}
198235
}
@@ -236,13 +273,15 @@ func (c *Client) solve(ctx context.Context, opt SolveOpt, statusChan chan *Solve
236273
}
237274
}
238275

239-
err := ParseCacheOptions(ctx, &opt)
276+
opt.initExporterIDs()
277+
278+
err := opt.parseCacheOptions(ctx)
240279
if err != nil {
241280
return nil, err
242281
}
243282

244283
if !opt.SessionPreInitialized {
245-
if err := ParseExporterOpts(&opt, s); err != nil {
284+
if err := opt.parseExporterOptions(s); err != nil {
246285
return nil, err
247286
}
248287

@@ -299,8 +338,7 @@ func (c *Client) solve(ctx context.Context, opt SolveOpt, statusChan chan *Solve
299338
exports = append(exports, &controlapi.Exporter{
300339
Type: exp.Type,
301340
Attrs: exp.Attrs,
302-
// Keep this in sync with SetupExporters id assignment
303-
ID: strconv.Itoa(i),
341+
ID: exp.id,
304342
})
305343
}
306344

@@ -330,6 +368,7 @@ func (c *Client) solve(ctx context.Context, opt SolveOpt, statusChan chan *Solve
330368
for _, resp := range resp.ExporterResponses {
331369
res.ExporterResponses = append(res.ExporterResponses, ExporterResponse{
332370
ID: resp.Metadata.ID,
371+
Type: resp.Metadata.Type,
333372
Data: resp.Data,
334373
})
335374
}
@@ -389,26 +428,27 @@ func (c *Client) solve(ctx context.Context, opt SolveOpt, statusChan chan *Solve
389428
if err := eg.Wait(); err != nil {
390429
return nil, err
391430
}
392-
// Update index.json of exported cache content store
393-
// FIXME(AkihiroSuda): dedupe const definition of cache/remotecache.ExporterResponseManifestDesc = "cache.manifest"
394-
if manifestDescJSON := res.ExporterResponse["cache.manifest"]; manifestDescJSON != "" {
395-
var manifestDesc ocispecs.Descriptor
396-
if err = json.Unmarshal([]byte(manifestDescJSON), &manifestDesc); err != nil {
431+
432+
for id, store := range opt.s.cacheOpt.storesToUpdate {
433+
// Update index.json of exported cache content store
434+
manifestDesc, err := getCacheManifestDescriptor(id, res)
435+
if err != nil {
397436
return nil, err
398437
}
399-
for storePath, tag := range opt.s.cacheOpt.storesToUpdate {
400-
idx := ociindex.NewStoreIndex(storePath)
401-
if err := idx.Put(manifestDesc, ociindex.Tag(tag)); err != nil {
402-
return nil, err
403-
}
438+
if manifestDesc == nil {
439+
continue
440+
}
441+
idx := ociindex.NewStoreIndex(store.path)
442+
if err := idx.Put(*manifestDesc, ociindex.Tag(store.tag)); err != nil {
443+
return nil, err
404444
}
405445
}
406446

407-
if len(opt.s.storesToUpdate) == 0 {
447+
if len(opt.s.exporterOpt.storesToUpdate) == 0 {
408448
return res, nil
409449
}
410-
for id, store := range opt.s.storesToUpdate {
411-
manifestDesc, err := getManifestDescriptor(id, res)
450+
for id, store := range opt.s.exporterOpt.storesToUpdate {
451+
manifestDesc, err := getImageManifestDescriptor(id, res)
412452
if err != nil {
413453
return nil, err
414454
}
@@ -433,25 +473,43 @@ func (c *Client) solve(ctx context.Context, opt SolveOpt, statusChan chan *Solve
433473
return res, nil
434474
}
435475

436-
func getManifestDescriptor(exporterID string, resp *SolveResponse) (*ocispecs.Descriptor, error) {
476+
func getCacheManifestDescriptor(exporterID string, resp *SolveResponse) (*ocispecs.Descriptor, error) {
477+
const exporterResponseManifestDesc = "cache.manifest"
478+
if resp := resp.cacheExporter(exporterID); resp != nil {
479+
// FIXME(AkihiroSuda): dedupe const definition of cache/remotecache.ExporterResponseManifestDesc = "cache.manifest"
480+
if manifestDescDt := resp.Data[exporterResponseManifestDesc]; manifestDescDt != "" {
481+
return unmarshalManifestDescriptor(manifestDescDt)
482+
}
483+
}
484+
if manifestDescDt := resp.ExporterResponse[exporterResponseManifestDesc]; manifestDescDt != "" {
485+
return unmarshalManifestDescriptor(manifestDescDt)
486+
}
487+
return nil, nil
488+
}
489+
490+
func getImageManifestDescriptor(exporterID string, resp *SolveResponse) (*ocispecs.Descriptor, error) {
437491
if resp := resp.exporter(exporterID); resp != nil {
438492
if manifestDescDt := resp.Data[exptypes.ExporterImageDescriptorKey]; manifestDescDt != "" {
439-
return unmarshalManifestDescriptor(manifestDescDt)
493+
return unmarshalEncodedManifestDescriptor(manifestDescDt)
440494
}
441495
}
442496
if manifestDescDt := resp.ExporterResponse[exptypes.ExporterImageDescriptorKey]; manifestDescDt != "" {
443-
return unmarshalManifestDescriptor(manifestDescDt)
497+
return unmarshalEncodedManifestDescriptor(manifestDescDt)
444498
}
445499
return nil, nil
446500
}
447501

448-
func unmarshalManifestDescriptor(manifestDesc string) (*ocispecs.Descriptor, error) {
449-
manifestDescDt, err := base64.StdEncoding.DecodeString(manifestDesc)
502+
func unmarshalEncodedManifestDescriptor(base64Payload string) (*ocispecs.Descriptor, error) {
503+
manifestDescDt, err := base64.StdEncoding.DecodeString(base64Payload)
450504
if err != nil {
451505
return nil, err
452506
}
507+
return unmarshalManifestDescriptor(string(manifestDescDt))
508+
}
509+
510+
func unmarshalManifestDescriptor(manifestDescJSON string) (*ocispecs.Descriptor, error) {
453511
var desc ocispecs.Descriptor
454-
if err = json.Unmarshal([]byte(manifestDescDt), &desc); err != nil {
512+
if err := json.Unmarshal([]byte(manifestDescJSON), &desc); err != nil {
455513
return nil, err
456514
}
457515
return &desc, nil
@@ -502,13 +560,16 @@ func prepareSyncedFiles(def *llb.Definition, localMounts map[string]fsutil.FS) (
502560
return result, nil
503561
}
504562

505-
func ParseCacheOptions(ctx context.Context, opt *SolveOpt) error {
563+
func (opt *SolveOpt) parseCacheOptions(ctx context.Context) error {
564+
if opt.s.cacheOpt != nil {
565+
return nil
566+
}
506567
var (
507568
cacheExports []*controlapi.CacheOptionsEntry
508569
cacheImports []*controlapi.CacheOptionsEntry
509570
)
510571
contentStores := make(map[string]content.Store)
511-
storesToUpdate := make(map[string]string)
572+
storesToUpdate := make(map[string]ociStore)
512573
frontendAttrs := make(map[string]string)
513574
for _, ex := range opt.CacheExports {
514575
if ex.Type == "local" {
@@ -529,8 +590,7 @@ func ParseCacheOptions(ctx context.Context, opt *SolveOpt) error {
529590
if t, ok := ex.Attrs["tag"]; ok {
530591
tag = t
531592
}
532-
// TODO(AkihiroSuda): support custom index JSON path and tag
533-
storesToUpdate[csDir] = tag
593+
storesToUpdate[ex.id] = ociStore{path: csDir, tag: tag}
534594
}
535595
if ex.Type == "registry" {
536596
regRef := ex.Attrs["ref"]
@@ -541,6 +601,7 @@ func ParseCacheOptions(ctx context.Context, opt *SolveOpt) error {
541601
cacheExports = append(cacheExports, &controlapi.CacheOptionsEntry{
542602
Type: ex.Type,
543603
Attrs: ex.Attrs,
604+
ID: ex.id,
544605
})
545606
}
546607
for _, im := range opt.CacheImports {

0 commit comments

Comments
 (0)