diff --git a/bake/bake.go b/bake/bake.go index a226773d..b3427c07 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -620,7 +620,7 @@ var _ hclparser.WithEvalContexts = &Group{} var _ hclparser.WithGetName = &Group{} func (t *Target) normalize() { - t.Attest = removeDupes(t.Attest) + t.Attest = removeAttestDupes(t.Attest) t.Tags = removeDupes(t.Tags) t.Secrets = removeDupes(t.Secrets) t.SSH = removeDupes(t.SSH) @@ -682,6 +682,7 @@ func (t *Target) Merge(t2 *Target) { } if t2.Attest != nil { // merge t.Attest = append(t.Attest, t2.Attest...) + t.Attest = removeAttestDupes(t.Attest) } if t2.Secrets != nil { // merge t.Secrets = append(t.Secrets, t2.Secrets...) @@ -1193,6 +1194,26 @@ func removeDupes(s []string) []string { return s[:i] } +func removeAttestDupes(s []string) []string { + res := []string{} + m := map[string]int{} + for _, v := range s { + att, err := buildflags.ParseAttest(v) + if err != nil { + res = append(res, v) + continue + } + + if i, ok := m[att.Type]; ok { + res[i] = v + } else { + m[att.Type] = len(res) + res = append(res, v) + } + } + return res +} + func parseOutputType(str string) string { csvReader := csv.NewReader(strings.NewReader(str)) fields, err := csvReader.Read() diff --git a/bake/bake_test.go b/bake/bake_test.go index cd543ff3..f3341c06 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -1417,3 +1417,36 @@ func TestReadLocalFilesDefault(t *testing.T) { }) } } + +func TestAttestDuplicates(t *testing.T) { + fp := File{ + Name: "docker-bake.hcl", + Data: []byte( + `target "default" { + attest = ["type=sbom", "type=sbom,generator=custom", "type=sbom,foo=bar", "type=provenance,mode=max"] + }`), + } + ctx := context.TODO() + + m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil) + require.Equal(t, []string{"type=sbom,foo=bar", "type=provenance,mode=max"}, m["default"].Attest) + require.NoError(t, err) + + opts, err := TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Equal(t, map[string]*string{ + "sbom": ptrstr("type=sbom,foo=bar"), + "provenance": ptrstr("type=provenance,mode=max"), + }, opts["default"].Attests) + + m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=true"}, nil) + require.Equal(t, []string{"type=sbom,disabled=true", "type=provenance,mode=max"}, m["default"].Attest) + require.NoError(t, err) + + opts, err = TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Equal(t, map[string]*string{ + "sbom": nil, + "provenance": ptrstr("type=provenance,mode=max"), + }, opts["default"].Attests) +} diff --git a/util/buildflags/attests.go b/util/buildflags/attests.go index c14c9f4e..71150d27 100644 --- a/util/buildflags/attests.go +++ b/util/buildflags/attests.go @@ -25,7 +25,7 @@ func ParseAttests(in []string) ([]*controllerapi.Attest, error) { found := map[string]struct{}{} for _, in := range in { in := in - attest, err := parseAttest(in) + attest, err := ParseAttest(in) if err != nil { return nil, err } @@ -40,7 +40,7 @@ func ParseAttests(in []string) ([]*controllerapi.Attest, error) { return out, nil } -func parseAttest(in string) (*controllerapi.Attest, error) { +func ParseAttest(in string) (*controllerapi.Attest, error) { if in == "" { return nil, nil }