Merge pull request #3031 from crazy-max/bake-set-append

bake: support += operator to append with overrides
This commit is contained in:
Tõnis Tiigi 2025-03-04 09:33:57 -08:00 committed by GitHub
commit 9a204c44c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 149 additions and 21 deletions

View File

@ -45,6 +45,7 @@ type File struct {
type Override struct { type Override struct {
Value string Value string
ArrValue []string ArrValue []string
Append bool
} }
func defaultFilenames() []string { func defaultFilenames() []string {
@ -528,9 +529,12 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
m := map[string]map[string]Override{} m := map[string]map[string]Override{}
for _, v := range v { for _, v := range v {
parts := strings.SplitN(v, "=", 2) parts := strings.SplitN(v, "=", 2)
keys := strings.SplitN(parts[0], ".", 3)
skey := strings.TrimSuffix(parts[0], "+")
appendTo := strings.HasSuffix(parts[0], "+")
keys := strings.SplitN(skey, ".", 3)
if len(keys) < 2 { if len(keys) < 2 {
return nil, errors.Errorf("invalid override key %s, expected target.name", parts[0]) return nil, errors.Errorf("invalid override key %s, expected target.name", skey)
} }
pattern := keys[0] pattern := keys[0]
@ -543,8 +547,7 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
return nil, err return nil, err
} }
kk := strings.SplitN(parts[0], ".", 2) okey := strings.Join(keys[1:], ".")
for _, name := range names { for _, name := range names {
t, ok := m[name] t, ok := m[name]
if !ok { if !ok {
@ -552,14 +555,15 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
m[name] = t m[name] = t
} }
o := t[kk[1]] override := t[okey]
// IMPORTANT: if you add more fields here, do not forget to update // IMPORTANT: if you add more fields here, do not forget to update
// docs/reference/buildx_bake.md (--set) and https://docs.docker.com/build/bake/overrides/ // docs/reference/buildx_bake.md (--set) and https://docs.docker.com/build/bake/overrides/
switch keys[1] { switch keys[1] {
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest", "entitlements", "network", "annotations": case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest", "entitlements", "network", "annotations":
if len(parts) == 2 { if len(parts) == 2 {
o.ArrValue = append(o.ArrValue, parts[1]) override.Append = appendTo
override.ArrValue = append(override.ArrValue, parts[1])
} }
case "args": case "args":
if len(keys) != 3 { if len(keys) != 3 {
@ -570,7 +574,7 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
if !ok { if !ok {
continue continue
} }
o.Value = v override.Value = v
} }
fallthrough fallthrough
case "contexts": case "contexts":
@ -580,11 +584,11 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
fallthrough fallthrough
default: default:
if len(parts) == 2 { if len(parts) == 2 {
o.Value = parts[1] override.Value = parts[1]
} }
} }
t[kk[1]] = o t[okey] = override
} }
} }
return m, nil return m, nil
@ -896,13 +900,21 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
} }
t.Labels[keys[1]] = &value t.Labels[keys[1]] = &value
case "tags": case "tags":
t.Tags = o.ArrValue if o.Append {
t.Tags = append(t.Tags, o.ArrValue...)
} else {
t.Tags = o.ArrValue
}
case "cache-from": case "cache-from":
cacheFrom, err := buildflags.ParseCacheEntry(o.ArrValue) cacheFrom, err := buildflags.ParseCacheEntry(o.ArrValue)
if err != nil { if err != nil {
return err return err
} }
t.CacheFrom = cacheFrom if o.Append {
t.CacheFrom = t.CacheFrom.Merge(cacheFrom)
} else {
t.CacheFrom = cacheFrom
}
for _, c := range t.CacheFrom { for _, c := range t.CacheFrom {
if c.Type == "local" { if c.Type == "local" {
if v, ok := c.Attrs["src"]; ok { if v, ok := c.Attrs["src"]; ok {
@ -915,7 +927,11 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
if err != nil { if err != nil {
return err return err
} }
t.CacheTo = cacheTo if o.Append {
t.CacheTo = t.CacheTo.Merge(cacheTo)
} else {
t.CacheTo = cacheTo
}
for _, c := range t.CacheTo { for _, c := range t.CacheTo {
if c.Type == "local" { if c.Type == "local" {
if v, ok := c.Attrs["dest"]; ok { if v, ok := c.Attrs["dest"]; ok {
@ -932,7 +948,11 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
if err != nil { if err != nil {
return errors.Wrap(err, "invalid value for outputs") return errors.Wrap(err, "invalid value for outputs")
} }
t.Secrets = secrets if o.Append {
t.Secrets = t.Secrets.Merge(secrets)
} else {
t.Secrets = secrets
}
for _, s := range t.Secrets { for _, s := range t.Secrets {
if s.FilePath != "" { if s.FilePath != "" {
ent.FSRead = append(ent.FSRead, s.FilePath) ent.FSRead = append(ent.FSRead, s.FilePath)
@ -943,18 +963,30 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
if err != nil { if err != nil {
return errors.Wrap(err, "invalid value for outputs") return errors.Wrap(err, "invalid value for outputs")
} }
t.SSH = ssh if o.Append {
t.SSH = t.SSH.Merge(ssh)
} else {
t.SSH = ssh
}
for _, s := range t.SSH { for _, s := range t.SSH {
ent.FSRead = append(ent.FSRead, s.Paths...) ent.FSRead = append(ent.FSRead, s.Paths...)
} }
case "platform": case "platform":
t.Platforms = o.ArrValue if o.Append {
t.Platforms = append(t.Platforms, o.ArrValue...)
} else {
t.Platforms = o.ArrValue
}
case "output": case "output":
outputs, err := parseArrValue[buildflags.ExportEntry](o.ArrValue) outputs, err := parseArrValue[buildflags.ExportEntry](o.ArrValue)
if err != nil { if err != nil {
return errors.Wrap(err, "invalid value for outputs") return errors.Wrap(err, "invalid value for outputs")
} }
t.Outputs = outputs if o.Append {
t.Outputs = t.Outputs.Merge(outputs)
} else {
t.Outputs = outputs
}
for _, o := range t.Outputs { for _, o := range t.Outputs {
if o.Destination != "" { if o.Destination != "" {
ent.FSWrite = append(ent.FSWrite, o.Destination) ent.FSWrite = append(ent.FSWrite, o.Destination)
@ -984,11 +1016,19 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
} }
t.NoCache = &noCache t.NoCache = &noCache
case "no-cache-filter": case "no-cache-filter":
t.NoCacheFilter = o.ArrValue if o.Append {
t.NoCacheFilter = append(t.NoCacheFilter, o.ArrValue...)
} else {
t.NoCacheFilter = o.ArrValue
}
case "shm-size": case "shm-size":
t.ShmSize = &value t.ShmSize = &value
case "ulimits": case "ulimits":
t.Ulimits = o.ArrValue if o.Append {
t.Ulimits = append(t.Ulimits, o.ArrValue...)
} else {
t.Ulimits = o.ArrValue
}
case "network": case "network":
t.NetworkMode = &value t.NetworkMode = &value
case "pull": case "pull":

View File

@ -37,6 +37,15 @@ target "webapp" {
annotations = [ annotations = [
"index,manifest:org.opencontainers.image.authors=dvdksn" "index,manifest:org.opencontainers.image.authors=dvdksn"
] ]
attest = [
"type=provenance,mode=max"
]
platforms = [
"linux/amd64"
]
secret = [
"id=FOO,env=FOO"
]
inherits = ["webDEP"] inherits = ["webDEP"]
}`), }`),
} }
@ -127,6 +136,22 @@ target "webapp" {
require.Equal(t, []string{"webapp"}, g["default"].Targets) require.Equal(t, []string{"webapp"}, g["default"].Targets)
}) })
t.Run("AttestOverride", func(t *testing.T) {
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.attest=type=sbom"}, nil, &EntitlementConf{})
require.NoError(t, err)
require.Len(t, m["webapp"].Attest, 2)
require.Equal(t, "provenance", m["webapp"].Attest[0].Type)
require.Equal(t, "sbom", m["webapp"].Attest[1].Type)
})
t.Run("AttestAppend", func(t *testing.T) {
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.attest+=type=sbom"}, nil, &EntitlementConf{})
require.NoError(t, err)
require.Len(t, m["webapp"].Attest, 2)
require.Equal(t, "provenance", m["webapp"].Attest[0].Type)
require.Equal(t, "sbom", m["webapp"].Attest[1].Type)
})
t.Run("ContextOverride", func(t *testing.T) { t.Run("ContextOverride", func(t *testing.T) {
t.Parallel() t.Parallel()
_, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context"}, nil, &EntitlementConf{}) _, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context"}, nil, &EntitlementConf{})
@ -148,6 +173,49 @@ target "webapp" {
require.Equal(t, []string{"webapp"}, g["default"].Targets) require.Equal(t, []string{"webapp"}, g["default"].Targets)
}) })
t.Run("PlatformOverride", func(t *testing.T) {
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.platform=linux/arm64"}, nil, &EntitlementConf{})
require.NoError(t, err)
require.Equal(t, []string{"linux/arm64"}, m["webapp"].Platforms)
})
t.Run("PlatformAppend", func(t *testing.T) {
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.platform+=linux/arm64"}, nil, &EntitlementConf{})
require.NoError(t, err)
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, m["webapp"].Platforms)
})
t.Run("PlatformAppendMulti", func(t *testing.T) {
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.platform+=linux/arm64", "webapp.platform+=linux/riscv64"}, nil, &EntitlementConf{})
require.NoError(t, err)
require.Equal(t, []string{"linux/amd64", "linux/arm64", "linux/riscv64"}, m["webapp"].Platforms)
})
t.Run("PlatformAppendMultiLastOverride", func(t *testing.T) {
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.platform+=linux/arm64", "webapp.platform=linux/riscv64"}, nil, &EntitlementConf{})
require.NoError(t, err)
require.Equal(t, []string{"linux/arm64", "linux/riscv64"}, m["webapp"].Platforms)
})
t.Run("SecretsOverride", func(t *testing.T) {
t.Setenv("FOO", "foo")
t.Setenv("BAR", "bar")
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.secrets=id=BAR,env=BAR"}, nil, &EntitlementConf{})
require.NoError(t, err)
require.Len(t, m["webapp"].Secrets, 1)
require.Equal(t, "BAR", m["webapp"].Secrets[0].ID)
})
t.Run("SecretsAppend", func(t *testing.T) {
t.Setenv("FOO", "foo")
t.Setenv("BAR", "bar")
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.secrets+=id=BAR,env=BAR"}, nil, &EntitlementConf{})
require.NoError(t, err)
require.Len(t, m["webapp"].Secrets, 2)
require.Equal(t, "FOO", m["webapp"].Secrets[0].ID)
require.Equal(t, "BAR", m["webapp"].Secrets[1].ID)
})
t.Run("ShmSizeOverride", func(t *testing.T) { t.Run("ShmSizeOverride", func(t *testing.T) {
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.shm-size=256m"}, nil, &EntitlementConf{}) m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.shm-size=256m"}, nil, &EntitlementConf{})
require.NoError(t, err) require.NoError(t, err)

View File

@ -347,19 +347,22 @@ is defined in https://golang.org/pkg/path/#Match.
```console ```console
$ docker buildx bake --set target.args.mybuildarg=value $ docker buildx bake --set target.args.mybuildarg=value
$ docker buildx bake --set target.platform=linux/arm64 $ docker buildx bake --set target.platform=linux/arm64
$ docker buildx bake --set foo*.args.mybuildarg=value # overrides build arg for all targets starting with 'foo' $ docker buildx bake --set foo*.args.mybuildarg=value # overrides build arg for all targets starting with 'foo'
$ docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets $ docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets
$ docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with 'foo' $ docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with 'foo'
$ docker buildx bake --set target.platform+=linux/arm64 # appends 'linux/arm64' to the platform list
``` ```
You can override the following fields: You can override the following fields:
* `annotations` * `annotations`
* `attest`
* `args` * `args`
* `cache-from` * `cache-from`
* `cache-to` * `cache-to`
* `context` * `context`
* `dockerfile` * `dockerfile`
* `entitlements`
* `labels` * `labels`
* `load` * `load`
* `no-cache` * `no-cache`
@ -372,3 +375,20 @@ You can override the following fields:
* `ssh` * `ssh`
* `tags` * `tags`
* `target` * `target`
You can append using `+=` operator for the following fields:
* `annotations`¹
* `attest`¹
* `cache-from`
* `cache-to`
* `entitlements`¹
* `no-cache-filter`
* `output`
* `platform`
* `secrets`
* `ssh`
* `tags`
> [!NOTE]
> ¹ These fields already append by default.