From aed5e98d92d77c4ff27d882deb25eff9a3e91c4f Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 21 May 2019 22:11:44 -0700 Subject: [PATCH] dockerfile: allow uid/gid in cache mounts Signed-off-by: Tonis Tiigi --- .../dockerfile2llb/convert_runmount.go | 44 +++++++++++++++++++ frontend/dockerfile/dockerfile_mount_test.go | 34 ++++++++++++++ frontend/dockerfile/docs/experimental.md | 3 ++ .../instructions/commands_runmount.go | 8 ++-- 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/frontend/dockerfile/dockerfile2llb/convert_runmount.go b/frontend/dockerfile/dockerfile2llb/convert_runmount.go index 0246e992e..1ff4c1a30 100644 --- a/frontend/dockerfile/dockerfile2llb/convert_runmount.go +++ b/frontend/dockerfile/dockerfile2llb/convert_runmount.go @@ -3,8 +3,12 @@ package dockerfile2llb import ( + "fmt" + "os" "path" "path/filepath" + "strconv" + "strings" "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/frontend/dockerfile/instructions" @@ -40,6 +44,40 @@ func detectRunMount(cmd *command, allDispatchStates *dispatchStates) bool { return false } +func setCacheUIDGIDFileOp(m *instructions.Mount, st llb.State) llb.State { + uid := 0 + gid := 0 + mode := os.FileMode(0755) + if m.UID != nil { + uid = int(*m.UID) + } + if m.GID != nil { + gid = int(*m.GID) + } + if m.Mode != nil { + mode = os.FileMode(*m.Mode) + } + return st.File(llb.Mkdir("/cache", mode, llb.WithUIDGID(uid, gid)), llb.WithCustomName("[internal] settings cache mount permissions")) +} + +func setCacheUIDGID(m *instructions.Mount, st llb.State, fileop bool) llb.State { + if fileop { + return setCacheUIDGIDFileOp(m, st) + } + + var b strings.Builder + if m.UID != nil { + b.WriteString(fmt.Sprintf("chown %d /mnt/cache;", *m.UID)) + } + if m.GID != nil { + b.WriteString(fmt.Sprintf("chown :%d /mnt/cache;", *m.GID)) + } + if m.Mode != nil { + b.WriteString(fmt.Sprintf("chmod %s /mnt/cache;", strconv.FormatUint(*m.Mode, 8))) + } + return llb.Image("busybox").Run(llb.Shlex(fmt.Sprintf("sh -c 'mkdir -p /mnt/cache;%s'", b.String())), llb.WithCustomName("[internal] settings cache mount permissions")).AddMount("/mnt", st) +} + func dispatchRunMounts(d *dispatchState, c *instructions.RunCommand, sources []*dispatchState, opt dispatchOpt) ([]llb.RunOption, error) { var out []llb.RunOption mounts := instructions.GetMounts(c) @@ -97,7 +135,13 @@ func dispatchRunMounts(d *dispatchState, c *instructions.RunCommand, sources []* } if src := path.Join("/", mount.Source); src != "/" { mountOpts = append(mountOpts, llb.SourcePath(src)) + } else { + if mount.UID != nil || mount.GID != nil || mount.Mode != nil { + st = setCacheUIDGID(mount, st, useFileOp(opt.buildArgValues, opt.llbCaps)) + mountOpts = append(mountOpts, llb.SourcePath("/cache")) + } } + out = append(out, llb.AddMount(target, st, mountOpts...)) d.ctxPaths[path.Join("/", filepath.ToSlash(mount.Source))] = struct{}{} diff --git a/frontend/dockerfile/dockerfile_mount_test.go b/frontend/dockerfile/dockerfile_mount_test.go index dfc10b536..ead09bcef 100644 --- a/frontend/dockerfile/dockerfile_mount_test.go +++ b/frontend/dockerfile/dockerfile_mount_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strconv" "testing" "github.com/containerd/continuity/fs/fstest" @@ -24,6 +25,8 @@ var mountTests = []integration.Test{ func init() { allTests = append(allTests, mountTests...) + + fileOpTests = append(fileOpTests, testCacheMountUser) } func testMountContext(t *testing.T, sb integration.Sandbox) { @@ -159,3 +162,34 @@ COPY --from=second /unique /unique require.NoError(t, err) require.Equal(t, dt1, dt2) } + +func testCacheMountUser(t *testing.T, sb integration.Sandbox) { + f := getFrontend(t, sb) + isFileOp := getFileOp(t, sb) + + dockerfile := []byte(` +FROM busybox +RUN --mount=type=cache,target=/mycache,uid=1001,gid=1002,mode=0751 [ "$(stat -c "%u %g %f" /mycache)" == "1001 1002 41e9" ] +`) + + dir, err := tmpdir( + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + c, err := client.New(context.TODO(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + _, err = f.Solve(context.TODO(), c, client.SolveOpt{ + FrontendAttrs: map[string]string{ + "build-arg:BUILDKIT_DISABLE_FILEOP": strconv.FormatBool(!isFileOp), + }, + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + }, + }, nil) + require.NoError(t, err) +} diff --git a/frontend/dockerfile/docs/experimental.md b/frontend/dockerfile/docs/experimental.md index 03abf3ea1..347cd3a20 100644 --- a/frontend/dockerfile/docs/experimental.md +++ b/frontend/dockerfile/docs/experimental.md @@ -41,6 +41,9 @@ This mount type allows the build container to cache directories for compilers an |`sharing` | One of `shared`, `private`, or `locked`. Defaults to `shared`. A `shared` cache mount can be used concurrently by multiple writers. `private` creates a new mount if there are multiple writers. `locked` pauses the second writer until the first one releases the mount.| |`from` | Build stage to use as a base of the cache mount. Defaults to empty directory.| |`source` | Subpath in the `from` to mount. Defaults to the root of the `from`.| +|`mode` | File mode for new cache directory in octal. Default 0755.| +|`uid` | User ID for new cache directory. Default 0.| +|`gid` | Group ID for new cache directory. Default 0.| #### Example: cache Go packages diff --git a/frontend/dockerfile/instructions/commands_runmount.go b/frontend/dockerfile/instructions/commands_runmount.go index 7b9295c68..143525ea8 100644 --- a/frontend/dockerfile/instructions/commands_runmount.go +++ b/frontend/dockerfile/instructions/commands_runmount.go @@ -206,18 +206,18 @@ func parseMount(value string) (*Mount, error) { } } - fileInfoAllowed := m.Type == MountTypeSecret || m.Type == MountTypeSSH + fileInfoAllowed := m.Type == MountTypeSecret || m.Type == MountTypeSSH || m.Type == MountTypeCache if m.Mode != nil && !fileInfoAllowed { - return nil, errors.Errorf("mode not allowed for %q type mounts") + return nil, errors.Errorf("mode not allowed for %q type mounts", m.Type) } if m.UID != nil && !fileInfoAllowed { - return nil, errors.Errorf("uid not allowed for %q type mounts") + return nil, errors.Errorf("uid not allowed for %q type mounts", m.Type) } if m.GID != nil && !fileInfoAllowed { - return nil, errors.Errorf("gid not allowed for %q type mounts") + return nil, errors.Errorf("gid not allowed for %q type mounts", m.Type) } if roAuto {