bake: allow overriding declared secret sources
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
+52
-7
@@ -606,9 +606,12 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
|
|||||||
override.Append = appendTo
|
override.Append = appendTo
|
||||||
override.ArrValue = append(override.ArrValue, parts[1])
|
override.ArrValue = append(override.ArrValue, parts[1])
|
||||||
}
|
}
|
||||||
case "resources":
|
case "resources", "secret":
|
||||||
if len(keys) != 3 {
|
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])
|
||||||
}
|
}
|
||||||
override.Value = parts[1]
|
override.Value = parts[1]
|
||||||
case "args":
|
case "args":
|
||||||
@@ -974,6 +977,8 @@ func (t *Target) Merge(t2 *Target) {
|
|||||||
func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementConf) error {
|
func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementConf) error {
|
||||||
// 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/bake-reference.md and https://docs.docker.com/build/bake/overrides/
|
// 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 {
|
for key, o := range overrides {
|
||||||
value := o.Value
|
value := o.Value
|
||||||
keys := strings.SplitN(key, ".", 2)
|
keys := strings.SplitN(key, ".", 2)
|
||||||
@@ -1061,21 +1066,24 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
|
|||||||
t.Target = &value
|
t.Target = &value
|
||||||
case "call":
|
case "call":
|
||||||
t.Call = &value
|
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":
|
case "secrets":
|
||||||
secrets, err := parseArrValue[buildflags.Secret](o.ArrValue)
|
secrets, err := parseArrValue[buildflags.Secret](o.ArrValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "invalid value for outputs")
|
return errors.Wrap(err, "invalid value for outputs")
|
||||||
}
|
}
|
||||||
|
for _, s := range secrets {
|
||||||
|
secretEntitlements[s.ID] = struct{}{}
|
||||||
|
}
|
||||||
if o.Append {
|
if o.Append {
|
||||||
t.Secrets = t.Secrets.Merge(secrets)
|
t.Secrets = t.Secrets.Merge(secrets)
|
||||||
} else {
|
} else {
|
||||||
t.Secrets = secrets
|
t.Secrets = secrets
|
||||||
}
|
}
|
||||||
for _, s := range t.Secrets {
|
|
||||||
if s.FilePath != "" {
|
|
||||||
ent.FSRead = append(ent.FSRead, s.FilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "ssh":
|
case "ssh":
|
||||||
ssh, err := parseArrValue[buildflags.SSH](o.ArrValue)
|
ssh, err := parseArrValue[buildflags.SSH](o.ArrValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1189,9 +1197,46 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
|
|||||||
return errors.Errorf("unknown key: %s", keys[0])
|
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
|
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) {
|
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{
|
content, _, err := block.Body.PartialContent(&hcl.BodySchema{
|
||||||
Attributes: []hcl.AttributeSchema{{Name: "matrix"}},
|
Attributes: []hcl.AttributeSchema{{Name: "matrix"}},
|
||||||
|
|||||||
@@ -237,6 +237,49 @@ target "webapp" {
|
|||||||
require.Equal(t, "BAR", m["webapp"].Secrets[1].ID)
|
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) {
|
t.Run("ShmSizeOverride", func(t *testing.T) {
|
||||||
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.shm-size=256m"}, nil, nil, &EntitlementConf{})
|
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.shm-size=256m"}, nil, nil, &EntitlementConf{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -1001,6 +1001,14 @@ RUN --mount=type=secret,id=KUBECONFIG,env=KUBECONFIG \
|
|||||||
helm upgrade --install
|
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`
|
### `target.shm-size`
|
||||||
|
|
||||||
Sets the size of the shared memory allocated for build containers when using
|
Sets the size of the shared memory allocated for build containers when using
|
||||||
|
|||||||
@@ -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.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.contexts.bar=../bar # overrides 'bar' named context
|
||||||
$ docker buildx bake --set target.resources.memory=2g # overrides memory resource limit
|
$ 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]
|
> [!NOTE]
|
||||||
@@ -464,6 +465,7 @@ You can override the following fields:
|
|||||||
* `pull`
|
* `pull`
|
||||||
* `push`
|
* `push`
|
||||||
* `resources`
|
* `resources`
|
||||||
|
* `secret.<id>`
|
||||||
* `secrets`
|
* `secrets`
|
||||||
* `ssh`
|
* `ssh`
|
||||||
* `tags`
|
* `tags`
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
|
|||||||
testBakePrint,
|
testBakePrint,
|
||||||
testBakePrintSensitive,
|
testBakePrintSensitive,
|
||||||
testBakePrintOverrideEmpty,
|
testBakePrintOverrideEmpty,
|
||||||
|
testBakeSecretSourceOverride,
|
||||||
testBakePrintKeepEscaped,
|
testBakePrintKeepEscaped,
|
||||||
testBakePrintRemoteContextSubdir,
|
testBakePrintRemoteContextSubdir,
|
||||||
testBakeLocal,
|
testBakeLocal,
|
||||||
@@ -474,6 +475,52 @@ target "default" {
|
|||||||
}`, stdout.String())
|
}`, 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) {
|
func testBakePrintKeepEscaped(t *testing.T, sb integration.Sandbox) {
|
||||||
bakefile := []byte(`
|
bakefile := []byte(`
|
||||||
target "default" {
|
target "default" {
|
||||||
|
|||||||
Reference in New Issue
Block a user