Skip to content

Commit 3771fe2

Browse files
authored
Merge pull request #2814 from jsternberg/bake-composable-attributes-phase2
bake: various fixes for composable attributes
2 parents 567361d + 5dd4ae0 commit 3771fe2

20 files changed

+925
-350
lines changed

bake/bake.go

+53-94
Original file line numberDiff line numberDiff line change
@@ -698,30 +698,30 @@ type Target struct {
698698
// Inherits is the only field that cannot be overridden with --set
699699
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
700700

701-
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
702-
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
703-
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
704-
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
705-
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
706-
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
707-
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
708-
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
709-
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
710-
CacheFrom []*buildflags.CacheOptionsEntry `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
711-
CacheTo []*buildflags.CacheOptionsEntry `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
712-
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
713-
Secrets []*buildflags.Secret `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
714-
SSH []*buildflags.SSH `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
715-
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
716-
Outputs []*buildflags.ExportEntry `json:"output,omitempty" hcl:"output,optional" cty:"output"`
717-
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
718-
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
719-
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
720-
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
721-
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
722-
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
723-
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
724-
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
701+
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
702+
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
703+
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
704+
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
705+
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
706+
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
707+
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
708+
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
709+
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
710+
CacheFrom buildflags.CacheOptions `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
711+
CacheTo buildflags.CacheOptions `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
712+
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
713+
Secrets buildflags.Secrets `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
714+
SSH buildflags.SSHKeys `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
715+
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
716+
Outputs buildflags.Exports `json:"output,omitempty" hcl:"output,optional" cty:"output"`
717+
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
718+
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
719+
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
720+
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
721+
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
722+
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
723+
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
724+
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
725725
// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.
726726

727727
// linked is a private field to mark a target used as a linked one
@@ -739,12 +739,12 @@ func (t *Target) normalize() {
739739
t.Annotations = removeDupesStr(t.Annotations)
740740
t.Attest = removeAttestDupes(t.Attest)
741741
t.Tags = removeDupesStr(t.Tags)
742-
t.Secrets = removeDupes(t.Secrets)
743-
t.SSH = removeDupes(t.SSH)
742+
t.Secrets = t.Secrets.Normalize()
743+
t.SSH = t.SSH.Normalize()
744744
t.Platforms = removeDupesStr(t.Platforms)
745-
t.CacheFrom = removeDupes(t.CacheFrom)
746-
t.CacheTo = removeDupes(t.CacheTo)
747-
t.Outputs = removeDupes(t.Outputs)
745+
t.CacheFrom = t.CacheFrom.Normalize()
746+
t.CacheTo = t.CacheTo.Normalize()
747+
t.Outputs = t.Outputs.Normalize()
748748
t.NoCacheFilter = removeDupesStr(t.NoCacheFilter)
749749
t.Ulimits = removeDupesStr(t.Ulimits)
750750

@@ -815,16 +815,16 @@ func (t *Target) Merge(t2 *Target) {
815815
t.Attest = removeAttestDupes(t.Attest)
816816
}
817817
if t2.Secrets != nil { // merge
818-
t.Secrets = append(t.Secrets, t2.Secrets...)
818+
t.Secrets = t.Secrets.Merge(t2.Secrets)
819819
}
820820
if t2.SSH != nil { // merge
821-
t.SSH = append(t.SSH, t2.SSH...)
821+
t.SSH = t.SSH.Merge(t2.SSH)
822822
}
823823
if t2.Platforms != nil { // no merge
824824
t.Platforms = t2.Platforms
825825
}
826826
if t2.CacheFrom != nil { // merge
827-
t.CacheFrom = append(t.CacheFrom, t2.CacheFrom...)
827+
t.CacheFrom = t.CacheFrom.Merge(t2.CacheFrom)
828828
}
829829
if t2.CacheTo != nil { // no merge
830830
t.CacheTo = t2.CacheTo
@@ -1333,30 +1333,19 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
13331333
}
13341334
bo.Platforms = platforms
13351335

1336-
secrets := make([]*controllerapi.Secret, len(t.Secrets))
1337-
for i, s := range t.Secrets {
1338-
secrets[i] = s.ToPB()
1339-
}
1340-
bo.SecretSpecs = secrets
1341-
1342-
secretAttachment, err := controllerapi.CreateSecrets(secrets)
1336+
bo.SecretSpecs = t.Secrets.ToPB()
1337+
secretAttachment, err := controllerapi.CreateSecrets(bo.SecretSpecs)
13431338
if err != nil {
13441339
return nil, err
13451340
}
13461341
bo.Session = append(bo.Session, secretAttachment)
13471342

1348-
var sshSpecs []*controllerapi.SSH
1349-
if len(t.SSH) > 0 {
1350-
sshSpecs := make([]*controllerapi.SSH, len(t.SSH))
1351-
for i, s := range t.SSH {
1352-
sshSpecs[i] = s.ToPB()
1353-
}
1354-
} else if buildflags.IsGitSSH(bi.ContextPath) || (inp != nil && buildflags.IsGitSSH(inp.URL)) {
1355-
sshSpecs = []*controllerapi.SSH{{ID: "default"}}
1343+
bo.SSHSpecs = t.SSH.ToPB()
1344+
if len(bo.SSHSpecs) == 0 && buildflags.IsGitSSH(bi.ContextPath) || (inp != nil && buildflags.IsGitSSH(inp.URL)) {
1345+
bo.SSHSpecs = []*controllerapi.SSH{{ID: "default"}}
13561346
}
1357-
bo.SSHSpecs = sshSpecs
13581347

1359-
sshAttachment, err := controllerapi.CreateSSH(sshSpecs)
1348+
sshAttachment, err := controllerapi.CreateSSH(bo.SSHSpecs)
13601349
if err != nil {
13611350
return nil, err
13621351
}
@@ -1372,24 +1361,14 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
13721361
}
13731362
}
13741363

1375-
cacheImports := make([]*controllerapi.CacheOptionsEntry, len(t.CacheFrom))
1376-
for i, ci := range t.CacheFrom {
1377-
cacheImports[i] = ci.ToPB()
1378-
}
1379-
bo.CacheFrom = controllerapi.CreateCaches(cacheImports)
1380-
1381-
cacheExports := make([]*controllerapi.CacheOptionsEntry, len(t.CacheTo))
1382-
for i, ce := range t.CacheTo {
1383-
cacheExports[i] = ce.ToPB()
1364+
if t.CacheFrom != nil {
1365+
bo.CacheFrom = controllerapi.CreateCaches(t.CacheFrom.ToPB())
13841366
}
1385-
bo.CacheTo = controllerapi.CreateCaches(cacheExports)
1386-
1387-
outputs := make([]*controllerapi.ExportEntry, len(t.Outputs))
1388-
for i, output := range t.Outputs {
1389-
outputs[i] = output.ToPB()
1367+
if t.CacheTo != nil {
1368+
bo.CacheTo = controllerapi.CreateCaches(t.CacheTo.ToPB())
13901369
}
13911370

1392-
bo.Exports, bo.ExportsLocalPathsTemporary, err = controllerapi.CreateExports(outputs)
1371+
bo.Exports, bo.ExportsLocalPathsTemporary, err = controllerapi.CreateExports(t.Outputs.ToPB())
13931372
if err != nil {
13941373
return nil, err
13951374
}
@@ -1434,34 +1413,6 @@ func defaultTarget() *Target {
14341413
return &Target{}
14351414
}
14361415

1437-
type comparable[E any] interface {
1438-
Equal(other E) bool
1439-
}
1440-
1441-
func removeDupes[E comparable[E]](s []E) []E {
1442-
// Move backwards through the slice.
1443-
// For each element, any elements after the current element are unique.
1444-
// If we find our current element conflicts with an existing element,
1445-
// then we swap the offender with the end of the slice and chop it off.
1446-
1447-
// Start at the second to last element.
1448-
// The last element is always unique.
1449-
for i := len(s) - 2; i >= 0; i-- {
1450-
elem := s[i]
1451-
// Check for duplicates after our current element.
1452-
for j := i + 1; j < len(s); j++ {
1453-
if elem.Equal(s[j]) {
1454-
// Found a duplicate, exchange the
1455-
// duplicate with the last element.
1456-
s[j], s[len(s)-1] = s[len(s)-1], s[j]
1457-
s = s[:len(s)-1]
1458-
break
1459-
}
1460-
}
1461-
}
1462-
return s
1463-
}
1464-
14651416
func removeDupesStr(s []string) []string {
14661417
i := 0
14671418
seen := make(map[string]struct{}, len(s))
@@ -1616,6 +1567,10 @@ type arrValue[B any] interface {
16161567
func parseArrValue[T any, PT arrValue[T]](s []string) ([]*T, error) {
16171568
outputs := make([]*T, 0, len(s))
16181569
for _, text := range s {
1570+
if text == "" {
1571+
continue
1572+
}
1573+
16191574
output := new(T)
16201575
if err := PT(output).UnmarshalText([]byte(text)); err != nil {
16211576
return nil, err
@@ -1625,9 +1580,13 @@ func parseArrValue[T any, PT arrValue[T]](s []string) ([]*T, error) {
16251580
return outputs, nil
16261581
}
16271582

1628-
func parseCacheArrValues(s []string) ([]*buildflags.CacheOptionsEntry, error) {
1629-
outs := make([]*buildflags.CacheOptionsEntry, 0, len(s))
1583+
func parseCacheArrValues(s []string) (buildflags.CacheOptions, error) {
1584+
var outs buildflags.CacheOptions
16301585
for _, in := range s {
1586+
if in == "" {
1587+
continue
1588+
}
1589+
16311590
if !strings.Contains(in, "=") {
16321591
// This is ref only format. Each field in the CSV is its own entry.
16331592
fields, err := csvvalue.Fields(in, nil)

bake/bake_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -2019,6 +2019,26 @@ target "app" {
20192019
})
20202020
}
20212021

2022+
// https://github.com/docker/buildx/pull/428
2023+
// https://github.com/docker/buildx/issues/2822
2024+
func TestEmptyAttribute(t *testing.T) {
2025+
fp := File{
2026+
Name: "docker-bake.hcl",
2027+
Data: []byte(`
2028+
target "app" {
2029+
output = [""]
2030+
}
2031+
`),
2032+
}
2033+
2034+
ctx := context.TODO()
2035+
2036+
m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil, &EntitlementConf{})
2037+
require.Equal(t, 1, len(m))
2038+
require.Len(t, m["app"].Outputs, 0)
2039+
require.NoError(t, err)
2040+
}
2041+
20222042
func stringify[V fmt.Stringer](values []V) []string {
20232043
s := make([]string, len(values))
20242044
for i, v := range values {

bake/compose.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -353,28 +353,28 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
353353
if err != nil {
354354
return err
355355
}
356-
t.CacheFrom = removeDupes(append(t.CacheFrom, cacheFrom...))
356+
t.CacheFrom = t.CacheFrom.Merge(cacheFrom)
357357
}
358358
if len(xb.CacheTo) > 0 {
359359
cacheTo, err := parseCacheArrValues(xb.CacheTo)
360360
if err != nil {
361361
return err
362362
}
363-
t.CacheTo = removeDupes(append(t.CacheTo, cacheTo...))
363+
t.CacheTo = t.CacheTo.Merge(cacheTo)
364364
}
365365
if len(xb.Secrets) > 0 {
366366
secrets, err := parseArrValue[buildflags.Secret](xb.Secrets)
367367
if err != nil {
368368
return err
369369
}
370-
t.Secrets = removeDupes(append(t.Secrets, secrets...))
370+
t.Secrets = t.Secrets.Merge(secrets)
371371
}
372372
if len(xb.SSH) > 0 {
373373
ssh, err := parseArrValue[buildflags.SSH](xb.SSH)
374374
if err != nil {
375375
return err
376376
}
377-
t.SSH = removeDupes(append(t.SSH, ssh...))
377+
t.SSH = t.SSH.Merge(ssh)
378378
slices.SortFunc(t.SSH, func(a, b *buildflags.SSH) int {
379379
return a.Less(b)
380380
})
@@ -387,7 +387,7 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
387387
if err != nil {
388388
return err
389389
}
390-
t.Outputs = removeDupes(append(t.Outputs, outputs...))
390+
t.Outputs = t.Outputs.Merge(outputs)
391391
}
392392
if xb.Pull != nil {
393393
t.Pull = xb.Pull

bake/hcl_test.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
606606
target "app" {
607607
cache-from = [
608608
{ type = "registry", ref = "user/app:cache" },
609-
{ type = "local", src = "path/to/cache" },
609+
"type=local,src=path/to/cache",
610610
]
611611
612612
cache-to = [
@@ -615,6 +615,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
615615
616616
output = [
617617
{ type = "oci", dest = "../out.tar" },
618+
"type=local,dest=../out",
618619
]
619620
620621
secret = [
@@ -633,7 +634,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
633634
require.NoError(t, err)
634635

635636
require.Equal(t, 1, len(c.Targets))
636-
require.Equal(t, []string{"type=oci,dest=../out.tar"}, stringify(c.Targets[0].Outputs))
637+
require.Equal(t, []string{"type=local,dest=../out", "type=oci,dest=../out.tar"}, stringify(c.Targets[0].Outputs))
637638
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom))
638639
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[0].CacheTo))
639640
require.Equal(t, []string{"id=mysecret,src=/local/secret", "id=mysecret2,env=TOKEN"}, stringify(c.Targets[0].Secrets))
@@ -649,13 +650,14 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
649650
target "app" {
650651
cache-from = [
651652
{ type = "registry", ref = "user/app:cache" },
652-
{ type = "local", src = "path/to/cache" },
653+
"type=local,src=path/to/cache",
653654
]
654655
655656
cache-to = [ target.app.cache-from[0] ]
656657
657658
output = [
658659
{ type = "oci", dest = "../out.tar" },
660+
"type=local,dest=../out",
659661
]
660662
661663
secret = [
@@ -674,7 +676,7 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
674676
output = [ "type=oci,dest=../${foo}.tar" ]
675677
676678
secret = [
677-
{ id = target.app.output[0].type, src = "/local/secret" },
679+
{ id = target.app.output[0].type, src = "/${target.app.cache-from[1].type}/secret" },
678680
]
679681
}
680682
`)
@@ -696,7 +698,7 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
696698
}
697699

698700
app := findTarget(t, "app")
699-
require.Equal(t, []string{"type=oci,dest=../out.tar"}, stringify(app.Outputs))
701+
require.Equal(t, []string{"type=local,dest=../out", "type=oci,dest=../out.tar"}, stringify(app.Outputs))
700702
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(app.CacheFrom))
701703
require.Equal(t, []string{"user/app:cache"}, stringify(app.CacheTo))
702704
require.Equal(t, []string{"id=mysecret,src=/local/secret"}, stringify(app.Secrets))

0 commit comments

Comments
 (0)