control: handle duplicate inline cache exporter

Inline cache exporter can be set with multiple ways, doesn't
have any attributes and can always only run one time.

Instead of allowing multiple inline exporters where one
gets ignored later when there is an attribute difference, or
erroring when attributes are unset, just ignore the extra ones.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi committed 2025-10-06 08:36:32 -07:00
1 parent fe65d5ff62
commit 9d6d33dd72
2 files changed
+73 -7

No files matched your search

+17 -6
View File
@@ -425,15 +425,17 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
expis = append(expis, expi)
}
if c, err := findDuplicateCacheOptions(req.Cache.Exports); err != nil {
rest, dupes, err := findDuplicateCacheOptions(req.Cache.Exports)
if err != nil {
return nil, err
} else if c != nil {
} else if len(dupes) > 0 {
types := []string{}
for _, c := range c {
for _, c := range dupes {
types = append(types, c.Type)
}
return nil, errors.Errorf("duplicate cache exports %s", types)
}
req.Cache.Exports = rest
var cacheExporters []llbsolver.RemoteCacheExporter
for _, e := range req.Cache.Exports {
cacheExporterFunc, ok := c.opt.ResolveCacheExporterFuncs[e.Type]
@@ -727,25 +729,34 @@ func toPBCDIDevices(manager *cdidevices.Manager) []*apitypes.CDIDevice {
return out
}
func findDuplicateCacheOptions(cacheOpts []*controlapi.CacheOptionsEntry) ([]*controlapi.CacheOptionsEntry, error) {
func findDuplicateCacheOptions(cacheOpts []*controlapi.CacheOptionsEntry) ([]*controlapi.CacheOptionsEntry, []*controlapi.CacheOptionsEntry, error) {
seen := map[string]*controlapi.CacheOptionsEntry{}
duplicate := map[string]struct{}{}
hasInline := false
rest := make([]*controlapi.CacheOptionsEntry, 0, len(cacheOpts))
for _, opt := range cacheOpts {
if opt.Type == "inline" && hasInline {
continue
}
k, err := cacheOptKey(opt)
if err != nil {
return nil, err
return nil, nil, err
}
if _, ok := seen[k]; ok {
duplicate[k] = struct{}{}
}
seen[k] = opt
if opt.Type == "inline" {
hasInline = true
}
rest = append(rest, opt)
}
var duplicates []*controlapi.CacheOptionsEntry
for k := range duplicate {
duplicates = append(duplicates, seen[k])
}
return duplicates, nil
return rest, duplicates, nil
}
func cacheOptKey(opt *controlapi.CacheOptionsEntry) (string, error) {
+56 -1
View File
@@ -12,6 +12,7 @@ func TestDuplicateCacheOptions(t *testing.T) {
name string
opts []*controlapi.CacheOptionsEntry
expected []*controlapi.CacheOptionsEntry
rest []*controlapi.CacheOptionsEntry
}{
{
name: "avoids unique opts",
@@ -74,13 +75,67 @@ func TestDuplicateCacheOptions(t *testing.T) {
},
},
},
{
name: "skip inline with attrs",
opts: []*controlapi.CacheOptionsEntry{
{
Type: "inline",
},
{
Type: "registry",
Attrs: map[string]string{
"ref": "example.com/ref:v1.0.0",
},
},
{
Type: "inline",
Attrs: map[string]string{
"foo": "bar",
},
},
},
rest: []*controlapi.CacheOptionsEntry{
{
Type: "inline",
},
{
Type: "registry",
Attrs: map[string]string{
"ref": "example.com/ref:v1.0.0",
},
},
},
expected: nil,
},
{
name: "skip inline simple",
opts: []*controlapi.CacheOptionsEntry{
{
Type: "inline",
},
{
Type: "inline",
},
},
rest: []*controlapi.CacheOptionsEntry{
{
Type: "inline",
},
},
expected: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := findDuplicateCacheOptions(tc.opts)
rest, result, err := findDuplicateCacheOptions(tc.opts)
require.NoError(t, err)
require.ElementsMatch(t, tc.expected, result)
if tc.rest != nil {
require.ElementsMatch(t, tc.rest, rest)
} else if len(result) == 0 {
require.ElementsMatch(t, tc.opts, rest)
}
})
}
}