Skip to content

Commit 5dd4ae0

Browse files
committed
bake: various fixes for composable attributes
This changes how the composable attributes are implemented and provides various fixes to the first iteration. Cache-from and cache-to now no longer print sensitive values that are automatically added. These automatically added attributes are added when the protobuf is created rather than at the time of parsing so they will no longer be printed. If they are part of the original configuration file, they will still be printed. Empty strings will now be skipped. This was the original behavior and composable attributes removed this functionality accidentally. This functionality is now restored. This also expands the available syntax that works with each of the composable attributes. It is now possible to interleave the csv syntax with the object syntax without any problems. The canonical form is still the object syntax and variables are resolved according to that syntax. Signed-off-by: Jonathan A. Sternberg <[email protected]>
1 parent 567361d commit 5dd4ae0

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)