dockerfile: allow uid/gid in cache mounts
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>docker-19.03
parent
bb03b93a3d
commit
aed5e98d92
|
@ -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{}{}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue