Skip to content

Commit

Permalink
Implements frontend side of moby#2122.
Browse files Browse the repository at this point in the history
This adds the syntax to Dockerfile frontend.
I purposedly chose to use a simple format for this as it's likely going
to be debated. As implemented, the following format is supported:

```
RUN --mount=type=secret,id=MYSECRET,env
```
or, more explicitly:

```
RUN --mount=type=secret,id=MYSECRET,env=true
```

will mount the secret with id MYSECRET as a new environment variable with the same name.

Using 'target', it's possible to create a different environment
variable:
```
RUN --mount=type=secret,id=mysecret,target=MY_SECRET,env
```
will mount 'mysecret' secret as MY_SECRET environment variable.

Any suggestions on making it more ergonomic are welcome.

Signed-off-by: a-palchikov <deemok@gmail.com>
  • Loading branch information
a-palchikov committed Aug 12, 2024
1 parent d1e6c91 commit 4e0b718
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 1 deletion.
6 changes: 6 additions & 0 deletions frontend/dockerfile/dockerfile2llb/convert_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ func dispatchSecret(d *dispatchState, m *instructions.Mount, loc []parser.Range)
if !m.Required {
opts = append(opts, llb.SecretOptional)
}
if m.SecretAsEnv {
if m.Target == "" {
target = path.Base(id)
}
opts = append(opts, llb.SecretAsEnv(true))
}

if m.UID != nil || m.GID != nil || m.Mode != nil {
var uid, gid, mode int
Expand Down
62 changes: 62 additions & 0 deletions frontend/dockerfile/dockerfile_secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
var secretsTests = integration.TestFuncs(
testSecretFileParams,
testSecretRequiredWithoutValue,
testSecretAsEnviron,
testSecretAsEnvironWithOverride,
)

func init() {
Expand Down Expand Up @@ -80,3 +82,63 @@ RUN --mount=type=secret,required,id=mysecret foo
require.Error(t, err)
require.Contains(t, err.Error(), "secret mysecret: not found")
}

func testSecretAsEnviron(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
f := getFrontend(t, sb)

dockerfile := []byte(`
FROM busybox
RUN --mount=type=secret,id=mysecret,env=true [ "$mysecret" == "pw" ] || false
`)

dir := integration.Tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)

c, err := client.New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
"mysecret": []byte("pw"),
})},
}, nil)
require.NoError(t, err)
}

func testSecretAsEnvironWithOverride(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
f := getFrontend(t, sb)

dockerfile := []byte(`
FROM busybox
RUN --mount=type=secret,id=mysecret,target=MY_SECRET,env [ "$MY_SECRET" == "pw" ] || false
`)

dir := integration.Tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)

c, err := client.New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
"mysecret": []byte("pw"),
})},
}, nil)
require.NoError(t, err)
}
12 changes: 11 additions & 1 deletion frontend/dockerfile/instructions/commands_runmount.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type Mount struct {
CacheID string
CacheSharing ShareMode
Required bool
SecretAsEnv bool
Mode *uint64
UID *uint64
GID *uint64
Expand Down Expand Up @@ -154,6 +155,9 @@ func parseMount(val string, expander SingleWordExpander) (*Mount, error) {
m.ReadOnly = false
roAuto = false
continue
case "env":
m.SecretAsEnv = true
continue
case "required":
if m.Type == MountTypeSecret || m.Type == MountTypeSSH {
m.Required = true
Expand Down Expand Up @@ -252,9 +256,15 @@ func parseMount(val string, expander SingleWordExpander) (*Mount, error) {
return nil, errors.Errorf("invalid value %s for gid", value)
}
m.GID = &gid
case "env":
env, err := strconv.ParseBool(value)
if err != nil {
return nil, errors.Errorf("invalid value for %q: %q", key, value)
}
m.SecretAsEnv = env
default:
allKeys := []string{
"type", "from", "source", "target", "readonly", "id", "sharing", "required", "mode", "uid", "gid", "src", "dst", "ro", "rw", "readwrite",
"type", "from", "source", "target", "readonly", "id", "sharing", "required", "mode", "uid", "gid", "src", "dst", "ro", "rw", "readwrite", "env",
}
return nil, suggest.WrapError(errors.Errorf("unexpected key '%s' in '%s'", key, field), key, allKeys, true)
}
Expand Down

0 comments on commit 4e0b718

Please sign in to comment.