Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 52 additions & 7 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,12 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
override.Append = appendTo
override.ArrValue = append(override.ArrValue, parts[1])
}
case "resources":
case "resources", "secret":
if len(keys) != 3 {
return nil, errors.Errorf("invalid key %s, resources requires name", parts[0])
return nil, errors.Errorf("invalid key %s, %s requires name", parts[0], keys[1])
}
if appendTo {
return nil, errors.Errorf("invalid key %s, %s does not support append", parts[0], keys[1])
Comment on lines +613 to +614

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added a small parser guard so --set target.secret.foo+=env=FOO fails instead of being silently treated like --set target.secret.foo=env=FOO.

This is intentional because secret.<id> is a source replacement for one already declared secret, not an appendable list field. The existing secrets field remains the appendable form for full secret definitions. I kept the actual source override application deferred until after regular secrets overrides, so combinations like --set target.secrets=id=foo,src=local followed by --set target.secret.foo=env=FOO resolve against the final declared secret set.

Note that this also affects resources.<name>+=.... Today that syntax is already not a real append, because resources fields are scalar values and the parser ends up treating += the same as =. The new guard makes that explicit instead of silently accepting misleading syntax. I think that's fine because there is no meaningful append behavior for resources.memory, resources.cpu-quota, or similar fields.

}
override.Value = parts[1]
case "args":
Expand Down Expand Up @@ -974,6 +977,8 @@ func (t *Target) Merge(t2 *Target) {
func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementConf) error {
// IMPORTANT: if you add more fields here, do not forget to update
// docs/bake-reference.md and https://docs.docker.com/build/bake/overrides/
secretOverrides := map[string]Override{}
secretEntitlements := map[string]struct{}{}
for key, o := range overrides {
value := o.Value
keys := strings.SplitN(key, ".", 2)
Expand Down Expand Up @@ -1061,21 +1066,24 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
t.Target = &value
case "call":
t.Call = &value
case "secret":
if len(keys) != 2 {
return errors.Errorf("invalid format for secret, expecting secret.<id>=<value>")
}
secretOverrides[keys[1]] = o
case "secrets":
secrets, err := parseArrValue[buildflags.Secret](o.ArrValue)
if err != nil {
return errors.Wrap(err, "invalid value for outputs")
}
for _, s := range secrets {
secretEntitlements[s.ID] = struct{}{}
}
if o.Append {
t.Secrets = t.Secrets.Merge(secrets)
} else {
t.Secrets = secrets
}
for _, s := range t.Secrets {
if s.FilePath != "" {
ent.FSRead = append(ent.FSRead, s.FilePath)
}
}
case "ssh":
ssh, err := parseArrValue[buildflags.SSH](o.ArrValue)
if err != nil {
Expand Down Expand Up @@ -1189,9 +1197,46 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
return errors.Errorf("unknown key: %s", keys[0])
}
}
for id, o := range secretOverrides {
if err := t.updateSecret(id, o.Value); err != nil {
return err
}
secretEntitlements[id] = struct{}{}
}
for _, s := range t.Secrets {
if _, ok := secretEntitlements[s.ID]; ok && s.FilePath != "" {
ent.FSRead = append(ent.FSRead, s.FilePath)
}
}
return nil
}

func (t *Target) updateSecret(id, value string) error {
if id == "" {
return errors.Errorf("invalid format for secret, expecting secret.<id>=<value>")
}

for _, s := range t.Secrets {
if s.ID != id {
continue
}

var next buildflags.Secret
if err := next.UnmarshalText([]byte(value)); err != nil {
return err
}
if next.ID != "" && next.ID != id {
return errors.Errorf("secret override id %q does not match declared secret %q", next.ID, id)
}

s.Env = next.Env
s.FilePath = next.FilePath
return nil
}

return errors.Errorf("secret %q must be declared before it can be overridden", id)
}

func (g *Group) GetEvalContexts(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func(hcl.Expression) hcl.Diagnostics) ([]*hcl.EvalContext, error) {
content, _, err := block.Body.PartialContent(&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{{Name: "matrix"}},
Expand Down
43 changes: 43 additions & 0 deletions bake/bake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,49 @@ target "webapp" {
require.Equal(t, "BAR", m["webapp"].Secrets[1].ID)
})

t.Run("SecretSourceOverrideEnv", func(t *testing.T) {
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.secret.FOO=env=BAR"}, nil, nil, &EntitlementConf{})
require.NoError(t, err)
require.Len(t, m["webapp"].Secrets, 1)
require.Equal(t, "FOO", m["webapp"].Secrets[0].ID)
require.Equal(t, "BAR", m["webapp"].Secrets[0].Env)
require.Empty(t, m["webapp"].Secrets[0].FilePath)
})

t.Run("SecretSourceOverrideFile", func(t *testing.T) {
ent := &EntitlementConf{}
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.secret.FOO=src=/tmp/foo"}, nil, nil, ent)
require.NoError(t, err)
require.Len(t, m["webapp"].Secrets, 1)
require.Equal(t, "FOO", m["webapp"].Secrets[0].ID)
require.Equal(t, "/tmp/foo", m["webapp"].Secrets[0].FilePath)
require.Empty(t, m["webapp"].Secrets[0].Env)
require.Equal(t, []string{"/tmp/foo"}, ent.FSRead)
})

t.Run("SecretSourceOverrideUsesFinalSourceForEntitlements", func(t *testing.T) {
ent := &EntitlementConf{}
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.secrets=id=FOO,src=/tmp/foo", "webapp.secret.FOO=env=BAR"}, nil, nil, ent)
require.NoError(t, err)
require.Len(t, m["webapp"].Secrets, 1)
require.Equal(t, "FOO", m["webapp"].Secrets[0].ID)
require.Equal(t, "BAR", m["webapp"].Secrets[0].Env)
require.Empty(t, m["webapp"].Secrets[0].FilePath)
require.Empty(t, ent.FSRead)
})

t.Run("SecretSourceOverrideUndeclared", func(t *testing.T) {
_, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.secret.BAR=env=BAR"}, nil, nil, &EntitlementConf{})
require.Error(t, err)
require.Equal(t, `secret "BAR" must be declared before it can be overridden`, err.Error())
})

t.Run("SecretSourceOverrideMismatchedID", func(t *testing.T) {
_, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.secret.FOO=id=BAR,env=BAR"}, nil, nil, &EntitlementConf{})
require.Error(t, err)
require.Equal(t, `secret override id "BAR" does not match declared secret "FOO"`, err.Error())
})

t.Run("ShmSizeOverride", func(t *testing.T) {
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.shm-size=256m"}, nil, nil, &EntitlementConf{})
require.NoError(t, err)
Expand Down
8 changes: 8 additions & 0 deletions docs/bake-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,14 @@ RUN --mount=type=secret,id=KUBECONFIG,env=KUBECONFIG \
helm upgrade --install
```

You can override the source for an existing secret without changing the target's
secret IDs. The secret must already be declared by the target.

```console
$ docker buildx bake --set default.secret.aws=env=AWS_CREDENTIALS
$ docker buildx bake --set default.secret.KUBECONFIG=src=/path/to/kubeconfig
```

### `target.shm-size`

Sets the size of the shared memory allocated for build containers when using
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/buildx_bake.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ $ docker buildx bake --set foo*.no-cache # bypass caching only fo
$ docker buildx bake --set target.platform+=linux/arm64 # appends 'linux/arm64' to the platform list
$ docker buildx bake --set target.contexts.bar=../bar # overrides 'bar' named context
$ docker buildx bake --set target.resources.memory=2g # overrides memory resource limit
$ docker buildx bake --set target.secret.aws=env=AWS # overrides source for an existing secret
```

> [!NOTE]
Expand Down Expand Up @@ -464,6 +465,7 @@ You can override the following fields:
* `pull`
* `push`
* `resources`
* `secret.<id>`
* `secrets`
* `ssh`
* `tags`
Expand Down
47 changes: 47 additions & 0 deletions tests/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakePrint,
testBakePrintSensitive,
testBakePrintOverrideEmpty,
testBakeSecretSourceOverride,
testBakePrintKeepEscaped,
testBakePrintRemoteContextSubdir,
testBakeLocal,
Expand Down Expand Up @@ -474,6 +475,52 @@ target "default" {
}`, stdout.String())
}

func testBakeSecretSourceOverride(t *testing.T, sb integration.Sandbox) {
bakefile := []byte(`
target "build" {
secret = [
"id=aws,src=aws-default",
"id=token,env=TOKEN",
]
}
`)
dir := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
fstest.CreateFile("Dockerfile", []byte("FROM scratch\n"), 0600),
fstest.CreateFile("aws-default", []byte("default"), 0600),
fstest.CreateFile("tokenfile", []byte("token"), 0600),
)

cmd := buildxCmd(sb, withDir(dir), withArgs(
"bake", "--print", "build",
"--set", "build.secret.aws=env=AWS_CREDENTIALS",
"--set", "build.secret.token=src=tokenfile",
))
stdout := bytes.Buffer{}
stderr := bytes.Buffer{}
cmd.Stdout = &stdout
cmd.Stderr = &stderr
require.NoError(t, cmd.Run(), stdout.String(), stderr.String())

var def struct {
Target map[string]*bake.Target `json:"target"`
}
require.NoError(t, json.Unmarshal(stdout.Bytes(), &def))
require.Contains(t, def.Target, "build")
require.Len(t, def.Target["build"].Secrets, 2)
require.Equal(t, "aws", def.Target["build"].Secrets[0].ID)
require.Equal(t, "AWS_CREDENTIALS", def.Target["build"].Secrets[0].Env)
require.Empty(t, def.Target["build"].Secrets[0].FilePath)
require.Equal(t, "token", def.Target["build"].Secrets[1].ID)
require.Equal(t, "tokenfile", def.Target["build"].Secrets[1].FilePath)
require.Empty(t, def.Target["build"].Secrets[1].Env)

out, err := bakeCmd(sb, withDir(dir), withArgs("--print", "build", "--set", "build.secret.missing=env=MISSING"))
require.Error(t, err)
require.Contains(t, out, `secret "missing" must be declared before it can be overridden`)
}

func testBakePrintKeepEscaped(t *testing.T, sb integration.Sandbox) {
bakefile := []byte(`
target "default" {
Expand Down
Loading