diff --git a/cmd/buildctl/main.go b/cmd/buildctl/main.go index 19534497..07edc812 100644 --- a/cmd/buildctl/main.go +++ b/cmd/buildctl/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "syscall" bccommon "github.com/moby/buildkit/cmd/buildctl/common" "github.com/moby/buildkit/util/apicaps" @@ -16,7 +15,6 @@ import ( func init() { apicaps.ExportedProduct = "buildkit" - syscall.Umask(0) } func main() { diff --git a/cmd/buildctl/main_unix.go b/cmd/buildctl/main_unix.go new file mode 100644 index 00000000..93533adb --- /dev/null +++ b/cmd/buildctl/main_unix.go @@ -0,0 +1,11 @@ +// +build !windows + +package main + +import ( + "syscall" +) + +func init() { + syscall.Umask(0) +} diff --git a/cmd/buildkitd/main.go b/cmd/buildkitd/main.go index c8355f10..c9e42565 100644 --- a/cmd/buildkitd/main.go +++ b/cmd/buildkitd/main.go @@ -13,7 +13,6 @@ import ( "sort" "strconv" "strings" - "syscall" "time" "github.com/BurntSushi/toml" @@ -56,7 +55,6 @@ import ( func init() { apicaps.ExportedProduct = "buildkit" seed.WithTimeAndRand() - syscall.Umask(0) } type workerInitializerOpt struct { diff --git a/cmd/buildkitd/main_unix.go b/cmd/buildkitd/main_unix.go new file mode 100644 index 00000000..93533adb --- /dev/null +++ b/cmd/buildkitd/main_unix.go @@ -0,0 +1,11 @@ +// +build !windows + +package main + +import ( + "syscall" +) + +func init() { + syscall.Umask(0) +} diff --git a/solver/llbsolver/file/backend.go b/solver/llbsolver/file/backend.go index 865bf264..554d869b 100644 --- a/solver/llbsolver/file/backend.go +++ b/solver/llbsolver/file/backend.go @@ -3,6 +3,7 @@ package file import ( "context" "io/ioutil" + "log" "os" "path/filepath" @@ -11,24 +12,34 @@ import ( "github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes" "github.com/moby/buildkit/solver/pb" "github.com/pkg/errors" + "github.com/sirupsen/logrus" copy "github.com/tonistiigi/fsutil/copy" "golang.org/x/sys/unix" ) -func mkdir(ctx context.Context, d string, action pb.FileActionMkDir) error { +func mkdir(ctx context.Context, d string, action pb.FileActionMkDir, user *uidgid) error { p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path))) if err != nil { return err } if action.MakeParents { - if err := os.MkdirAll(p, os.FileMode(action.Mode)&0777); err != nil { + if err := mkdirAll(p, os.FileMode(action.Mode)&0777, user); err != nil { return err } } else { if err := os.Mkdir(p, os.FileMode(action.Mode)&0777); err != nil { + if os.IsExist(err) { + return nil + } return err } + if user != nil { + if err := os.Chown(p, user.uid, user.gid); err != nil { + return err + } + } + } if action.Timestamp != -1 { @@ -42,7 +53,7 @@ func mkdir(ctx context.Context, d string, action pb.FileActionMkDir) error { return nil } -func mkfile(ctx context.Context, d string, action pb.FileActionMkFile) error { +func mkfile(ctx context.Context, d string, action pb.FileActionMkFile, user *uidgid) error { p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path))) if err != nil { return err @@ -52,6 +63,12 @@ func mkfile(ctx context.Context, d string, action pb.FileActionMkFile) error { return err } + if user != nil { + if err := os.Chown(p, user.uid, user.gid); err != nil { + return err + } + } + if action.Timestamp != -1 { st := unix.Timespec{Sec: action.Timestamp / 1e9, Nsec: action.Timestamp % 1e9} timespec := []unix.Timespec{st, st} @@ -59,6 +76,7 @@ func mkfile(ctx context.Context, d string, action pb.FileActionMkFile) error { return errors.Wrapf(err, "failed to utime %s", p) } } + return nil } @@ -78,7 +96,7 @@ func rm(ctx context.Context, d string, action pb.FileActionRm) error { return nil } -func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy) error { +func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy, u *uidgid) error { // // src is the source path // Src string `protobuf:"bytes,1,opt,name=src,proto3" json:"src,omitempty"` // // dest path @@ -118,6 +136,19 @@ func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy) err opt = append(opt, copy.AllowWildcards) } + if u != nil { + opt = append(opt, func(ci *copy.CopyInfo) { + ci.Chown = ©.ChownOpt{Uid: u.uid, Gid: u.gid} + }) + } + + xattrErrorHandler := func(dst, src, key string, err error) error { + log.Println(err) + return nil + } + + opt = append(opt, copy.WithXAttrErrorHandler(xattrErrorHandler)) + if err := copy.Copy(ctx, srcp, destp, opt...); err != nil { return err } @@ -128,7 +159,7 @@ func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy) err type Backend struct { } -func (fb *Backend) Mkdir(ctx context.Context, m fileoptypes.Mount, action pb.FileActionMkDir) error { +func (fb *Backend) Mkdir(ctx context.Context, m, user, group fileoptypes.Mount, action pb.FileActionMkDir) error { mnt, ok := m.(*Mount) if !ok { return errors.Errorf("invalid mount type %T", m) @@ -141,10 +172,15 @@ func (fb *Backend) Mkdir(ctx context.Context, m fileoptypes.Mount, action pb.Fil } defer lm.Unmount() - return mkdir(ctx, dir, action) + u, err := readUser(action.Owner, user, group) + if err != nil { + return err + } + + return mkdir(ctx, dir, action, u) } -func (fb *Backend) Mkfile(ctx context.Context, m fileoptypes.Mount, action pb.FileActionMkFile) error { +func (fb *Backend) Mkfile(ctx context.Context, m, user, group fileoptypes.Mount, action pb.FileActionMkFile) error { mnt, ok := m.(*Mount) if !ok { return errors.Errorf("invalid mount type %T", m) @@ -157,7 +193,12 @@ func (fb *Backend) Mkfile(ctx context.Context, m fileoptypes.Mount, action pb.Fi } defer lm.Unmount() - return mkfile(ctx, dir, action) + u, err := readUser(action.Owner, user, group) + if err != nil { + return err + } + + return mkfile(ctx, dir, action, u) } func (fb *Backend) Rm(ctx context.Context, m fileoptypes.Mount, action pb.FileActionRm) error { mnt, ok := m.(*Mount) @@ -174,7 +215,7 @@ func (fb *Backend) Rm(ctx context.Context, m fileoptypes.Mount, action pb.FileAc return rm(ctx, dir, action) } -func (fb *Backend) Copy(ctx context.Context, m1 fileoptypes.Mount, m2 fileoptypes.Mount, action pb.FileActionCopy) error { +func (fb *Backend) Copy(ctx context.Context, m1, m2, user, group fileoptypes.Mount, action pb.FileActionCopy) error { mnt1, ok := m1.(*Mount) if !ok { return errors.Errorf("invalid mount type %T", m1) @@ -198,5 +239,12 @@ func (fb *Backend) Copy(ctx context.Context, m1 fileoptypes.Mount, m2 fileoptype } defer lm2.Unmount() - return docopy(ctx, src, dest, action) + u, err := readUser(action.Owner, user, group) + if err != nil { + return err + } + + logrus.Debugf("copy %+v %+v %+v", action.Owner, user, group) + + return docopy(ctx, src, dest, action, u) } diff --git a/solver/llbsolver/file/chown.go b/solver/llbsolver/file/chown.go new file mode 100644 index 00000000..4c305cf8 --- /dev/null +++ b/solver/llbsolver/file/chown.go @@ -0,0 +1,62 @@ +package file + +import ( + "os" + "syscall" +) + +// mkdirAll is forked os.MkdirAll +func mkdirAll(path string, perm os.FileMode, user *uidgid) error { + // Fast path: if we can tell whether path is a directory or file, stop with success or error. + dir, err := os.Stat(path) + if err == nil { + if dir.IsDir() { + return nil + } + return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} + } + + // Slow path: make sure parent exists and then call Mkdir for path. + i := len(path) + for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator. + i-- + } + + j := i + for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element. + j-- + } + + if j > 1 { + // Create parent. + err = mkdirAll(fixRootDirectory(path[:j-1]), perm, user) + if err != nil { + return err + } + } + + dir, err1 := os.Lstat(path) + if err1 == nil && dir.IsDir() { + return nil + } + + // Parent now exists; invoke Mkdir and use its result. + err = os.Mkdir(path, perm) + if err != nil { + // Handle arguments like "foo/." by + // double-checking that directory doesn't exist. + dir, err1 := os.Lstat(path) + if err1 == nil && dir.IsDir() { + return nil + } + return err + } + + if user != nil { + if err := os.Chown(path, user.uid, user.gid); err != nil { + return err + } + } + + return nil +} diff --git a/solver/llbsolver/file/chown_unix.go b/solver/llbsolver/file/chown_unix.go new file mode 100644 index 00000000..4796302b --- /dev/null +++ b/solver/llbsolver/file/chown_unix.go @@ -0,0 +1,7 @@ +// +build !windows + +package file + +func fixRootDirectory(p string) string { + return p +} diff --git a/solver/llbsolver/file/chown_windows.go b/solver/llbsolver/file/chown_windows.go new file mode 100644 index 00000000..b0874673 --- /dev/null +++ b/solver/llbsolver/file/chown_windows.go @@ -0,0 +1,14 @@ +// +build windows + +package file + +import "os" + +func fixRootDirectory(p string) string { + if len(p) == len(`\\?\c:`) { + if os.IsPathSeparator(p[0]) && os.IsPathSeparator(p[1]) && p[2] == '?' && os.IsPathSeparator(p[3]) && p[5] == ':' { + return p + `\` + } + } + return p +} diff --git a/solver/llbsolver/file/user.go b/solver/llbsolver/file/user.go new file mode 100644 index 00000000..665319b7 --- /dev/null +++ b/solver/llbsolver/file/user.go @@ -0,0 +1,122 @@ +package file + +import ( + "os" + + "github.com/containerd/continuity/fs" + "github.com/moby/buildkit/snapshot" + "github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes" + "github.com/moby/buildkit/solver/pb" + "github.com/opencontainers/runc/libcontainer/user" + "github.com/pkg/errors" +) + +type uidgid struct { + uid, gid int +} + +func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*uidgid, error) { + if chopt == nil { + return nil, nil + } + var us uidgid + if chopt.User != nil { + switch u := chopt.User.User.(type) { + case *pb.UserOpt_ByName: + if mu == nil { + return nil, errors.Errorf("invalid missing user mount") + } + mmu, ok := mu.(*Mount) + if !ok { + return nil, errors.Errorf("invalid mount type %T", mu) + } + lm := snapshot.LocalMounter(mmu.m) + dir, err := lm.Mount() + if err != nil { + return nil, err + } + defer lm.Unmount() + + passwdPath, err := user.GetPasswdPath() + if err != nil { + return nil, err + } + + passwdPath, err = fs.RootPath(dir, passwdPath) + if err != nil { + return nil, err + } + + ufile, err := os.Open(passwdPath) + if err != nil { + return nil, err + } + defer ufile.Close() + + users, err := user.ParsePasswdFilter(ufile, func(uu user.User) bool { + return uu.Name == u.ByName.Name + }) + if err != nil { + return nil, err + } + + if len(users) > 0 { + us.uid = users[0].Uid + us.gid = users[0].Gid + } + case *pb.UserOpt_ByID: + us.uid = int(u.ByID) + us.gid = int(u.ByID) + } + } + + if chopt.Group != nil { + switch u := chopt.Group.User.(type) { + case *pb.UserOpt_ByName: + if mg == nil { + return nil, errors.Errorf("invalid missing group mount") + } + mmg, ok := mg.(*Mount) + if !ok { + return nil, errors.Errorf("invalid mount type %T", mg) + } + lm := snapshot.LocalMounter(mmg.m) + dir, err := lm.Mount() + if err != nil { + return nil, err + } + defer lm.Unmount() + + groupPath, err := user.GetGroupPath() + if err != nil { + return nil, err + } + + groupPath, err = fs.RootPath(dir, groupPath) + if err != nil { + return nil, err + } + + gfile, err := os.Open(groupPath) + if err != nil { + return nil, err + } + defer gfile.Close() + + groups, err := user.ParseGroupFilter(gfile, func(g user.Group) bool { + return g.Name == u.ByName.Name + }) + if err != nil { + return nil, err + } + + if len(groups) > 0 { + us.gid = groups[0].Gid + } + case *pb.UserOpt_ByID: + us.gid = int(u.ByID) + } + } + + return &us, nil +} diff --git a/solver/llbsolver/ops/file.go b/solver/llbsolver/ops/file.go index 423e54ae..013778fe 100644 --- a/solver/llbsolver/ops/file.go +++ b/solver/llbsolver/ops/file.go @@ -55,7 +55,7 @@ func (f *fileOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, boo switch a := action.Action.(type) { case *pb.FileAction_Mkdir: p := *a.Mkdir - p.Owner = nil + processOwner(p.Owner, selectors) dt, err = json.Marshal(p) if err != nil { return nil, false, err @@ -63,6 +63,7 @@ func (f *fileOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, boo case *pb.FileAction_Mkfile: p := *a.Mkfile p.Owner = nil + processOwner(p.Owner, selectors) dt, err = json.Marshal(p) if err != nil { return nil, false, err @@ -75,6 +76,7 @@ func (f *fileOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, boo } case *pb.FileAction_Copy: p := *a.Copy + processOwner(p.Owner, selectors) p.Owner = nil if action.SecondaryInput != -1 && int(action.SecondaryInput) < f.numInputs { p.Src = path.Base(p.Src) @@ -213,6 +215,29 @@ func dedupeSelectors(m map[llbsolver.Selector]struct{}) []llbsolver.Selector { return selectors } +func processOwner(chopt *pb.ChownOpt, selectors map[int]map[llbsolver.Selector]struct{}) error { + if chopt == nil { + return nil + } + if chopt.User != nil { + if u, ok := chopt.User.User.(*pb.UserOpt_ByName); ok { + if u.ByName.Input < 0 { + return errors.Errorf("invalid user index %d", u.ByName.Input) + } + addSelector(selectors, int(u.ByName.Input), "/etc/passwd", false, true) + } + } + if chopt.Group != nil { + if u, ok := chopt.Group.User.(*pb.UserOpt_ByName); ok { + if u.ByName.Input < 0 { + return errors.Errorf("invalid user index %d", u.ByName.Input) + } + addSelector(selectors, int(u.ByName.Input), "/etc/group", false, true) + } + } + return nil +} + func NewFileOpSolver(b fileoptypes.Backend, r fileoptypes.RefManager) *FileOpSolver { return &FileOpSolver{ b: b, @@ -354,6 +379,13 @@ func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptyp return inp, nil } + var toRelease []fileoptypes.Mount + defer func() { + for _, m := range toRelease { + m.Release(context.TODO()) + } + }() + var inpMount, inpMountSecondary fileoptypes.Mount action := actions[idx-len(inputs)] @@ -388,6 +420,7 @@ func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptyp return err } inpMountSecondary = m + toRelease = append(toRelease, m) return nil } inpMountSecondary = inp.mount @@ -395,6 +428,51 @@ func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptyp } } + loadUser := func(ctx context.Context, uopt *pb.UserOpt) (fileoptypes.Mount, error) { + if uopt == nil { + return nil, nil + } + switch u := uopt.User.(type) { + case *pb.UserOpt_ByName: + var m fileoptypes.Mount + if u.ByName.Input < 0 { + return nil, errors.Errorf("invalid user index: %d", u.ByName.Input) + } + inp, err := s.getInput(ctx, int(u.ByName.Input), inputs, actions) + if err != nil { + return nil, err + } + if inp.ref != nil { + mm, err := s.r.Prepare(ctx, inp.ref, true) + if err != nil { + return nil, err + } + toRelease = append(toRelease, mm) + m = mm + } else { + m = inp.mount + } + return m, nil + default: + return nil, nil + } + } + + loadOwner := func(ctx context.Context, chopt *pb.ChownOpt) (fileoptypes.Mount, fileoptypes.Mount, error) { + if chopt == nil { + return nil, nil, nil + } + um, err := loadUser(ctx, chopt.User) + if err != nil { + return nil, nil, err + } + gm, err := loadUser(ctx, chopt.Group) + if err != nil { + return nil, nil, err + } + return um, gm, nil + } + if action.Input != -1 && action.SecondaryInput != -1 { eg, ctx := errgroup.WithContext(ctx) eg.Go(loadInput(ctx)) @@ -425,11 +503,19 @@ func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptyp switch a := action.Action.(type) { case *pb.FileAction_Mkdir: - if err := s.b.Mkdir(ctx, inpMount, *a.Mkdir); err != nil { + user, group, err := loadOwner(ctx, a.Mkdir.Owner) + if err != nil { + return nil, err + } + if err := s.b.Mkdir(ctx, inpMount, user, group, *a.Mkdir); err != nil { return nil, err } case *pb.FileAction_Mkfile: - if err := s.b.Mkfile(ctx, inpMount, *a.Mkfile); err != nil { + user, group, err := loadOwner(ctx, a.Mkfile.Owner) + if err != nil { + return nil, err + } + if err := s.b.Mkfile(ctx, inpMount, user, group, *a.Mkfile); err != nil { return nil, err } case *pb.FileAction_Rm: @@ -444,7 +530,11 @@ func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptyp } inpMountSecondary = m } - if err := s.b.Copy(ctx, inpMountSecondary, inpMount, *a.Copy); err != nil { + user, group, err := loadOwner(ctx, a.Copy.Owner) + if err != nil { + return nil, err + } + if err := s.b.Copy(ctx, inpMountSecondary, inpMount, user, group, *a.Copy); err != nil { return nil, err } default: diff --git a/solver/llbsolver/ops/file_test.go b/solver/llbsolver/ops/file_test.go index 0baf3067..1890d4fe 100644 --- a/solver/llbsolver/ops/file_test.go +++ b/solver/llbsolver/ops/file_test.go @@ -54,6 +54,135 @@ func TestMkdirMkfile(t *testing.T) { require.Equal(t, fo.Actions[1].Action.(*pb.FileAction_Mkfile).Mkfile, o.mount.chain[1].mkfile) } +func TestChownOpt(t *testing.T) { + fo := &pb.FileOp{ + Actions: []*pb.FileAction{ + { + Input: 0, + SecondaryInput: -1, + Output: -1, + Action: &pb.FileAction_Mkdir{ + Mkdir: &pb.FileActionMkDir{ + Path: "/foo/bar", + MakeParents: true, + Mode: 0700, + Owner: &pb.ChownOpt{ + User: &pb.UserOpt{ + User: &pb.UserOpt_ByName{ + ByName: &pb.NamedUserOpt{ + Input: 1, + Name: "myuser", + }, + }, + }, + Group: &pb.UserOpt{ + User: &pb.UserOpt_ByName{ + ByName: &pb.NamedUserOpt{ + Input: 1, + Name: "myuser", + }, + }, + }, + }, + }, + }, + }, + { + Input: 2, + SecondaryInput: -1, + Output: 0, + Action: &pb.FileAction_Mkfile{ + Mkfile: &pb.FileActionMkFile{ + Path: "/foo/bar/baz", + Mode: 0700, + Owner: &pb.ChownOpt{ + User: &pb.UserOpt{ + User: &pb.UserOpt_ByID{ + ByID: 100, + }, + }, + }, + }, + }, + }, + }, + } + + s, rb := newTestFileSolver() + inp := rb.NewRef("ref1") + inp2 := rb.NewRef("usermount") + outs, err := s.Solve(context.TODO(), []fileoptypes.Ref{inp, inp2}, fo.Actions) + require.NoError(t, err) + require.Equal(t, len(outs), 1) + rb.checkReleased(t, append(outs, inp, inp2)) + + o := outs[0].(*testFileRef) + require.Equal(t, "mount-ref1-mkdir#u(mount-usermount)#g(mount-usermount)-mkfile-commit", o.id) + require.Equal(t, 2, len(o.mount.chain)) + require.Equal(t, fo.Actions[0].Action.(*pb.FileAction_Mkdir).Mkdir, o.mount.chain[0].mkdir) + require.Equal(t, fo.Actions[1].Action.(*pb.FileAction_Mkfile).Mkfile, o.mount.chain[1].mkfile) +} + +func TestChownCopy(t *testing.T) { + fo := &pb.FileOp{ + Actions: []*pb.FileAction{ + { + Input: -1, + SecondaryInput: -1, + Output: -1, + Action: &pb.FileAction_Mkfile{ + Mkfile: &pb.FileActionMkFile{ + Path: "/foo/bar/baz", + Mode: 0700, + }, + }, + }, + { + Input: 1, + SecondaryInput: 0, + Output: 0, + Action: &pb.FileAction_Copy{ + Copy: &pb.FileActionCopy{ + Src: "/src", + Dest: "/dest", + Owner: &pb.ChownOpt{ + User: &pb.UserOpt{ + User: &pb.UserOpt_ByName{ + ByName: &pb.NamedUserOpt{ + Input: 1, + Name: "myuser", + }, + }, + }, + Group: &pb.UserOpt{ + User: &pb.UserOpt_ByName{ + ByName: &pb.NamedUserOpt{ + Input: 2, + Name: "mygroup", + }, + }, + }, + }, + }, + }, + }, + }, + } + + s, rb := newTestFileSolver() + inpSrc := rb.NewRef("src") + inpDest := rb.NewRef("dest") + outs, err := s.Solve(context.TODO(), []fileoptypes.Ref{inpSrc, inpDest}, fo.Actions) + require.NoError(t, err) + require.Equal(t, len(outs), 1) + rb.checkReleased(t, append(outs, inpSrc, inpDest)) + + o := outs[0].(*testFileRef) + require.Equal(t, "mount-dest-copy(mount-src)#u(mount-dest)#g(mount-scratch-mkfile)-commit", o.id) + require.Equal(t, 1, len(o.mount.chain)) + require.Equal(t, fo.Actions[1].Action.(*pb.FileAction_Copy).Copy, o.mount.chain[0].copy) +} + func TestInvalidNoOutput(t *testing.T) { fo := &pb.FileOp{ Actions: []*pb.FileAction{ @@ -267,6 +396,37 @@ func TestFileFromScratch(t *testing.T) { require.Equal(t, fo.Actions[1].Action.(*pb.FileAction_Mkfile).Mkfile, o.mount.chain[1].mkfile) } +func TestFileCopyInputSrc(t *testing.T) { + fo := &pb.FileOp{ + Actions: []*pb.FileAction{ + { + Input: 1, + SecondaryInput: 0, + Output: 0, + Action: &pb.FileAction_Copy{ + Copy: &pb.FileActionCopy{ + Src: "/src", + Dest: "/dest", + }, + }, + }, + }, + } + + s, rb := newTestFileSolver() + inp0 := rb.NewRef("srcref") + inp1 := rb.NewRef("destref") + outs, err := s.Solve(context.TODO(), []fileoptypes.Ref{inp0, inp1}, fo.Actions) + require.NoError(t, err) + require.Equal(t, len(outs), 1) + rb.checkReleased(t, append(outs, inp0, inp1)) + + o := outs[0].(*testFileRef) + require.Equal(t, "mount-destref-copy(mount-srcref)-commit", o.id) + require.Equal(t, 1, len(o.mount.chain)) + require.Equal(t, fo.Actions[0].Action.(*pb.FileAction_Copy).Copy, o.mount.chain[0].copy) +} + func TestFileCopyInputRm(t *testing.T) { fo := &pb.FileOp{ Actions: []*pb.FileAction{ @@ -409,6 +569,17 @@ type testMount struct { active *testFileRef } +func (tm *testMount) addUser(user, group fileoptypes.Mount) { + if user != nil { + um := user.(*testMount) + tm.id += "#u(" + um.id + ")" + } + if group != nil { + gm := group.(*testMount) + tm.id += "#g(" + gm.id + ")" + } +} + type mod struct { mkdir *pb.FileActionMkDir rm *pb.FileActionRm @@ -419,7 +590,7 @@ type mod struct { func (m *testMount) IsFileOpMount() {} func (m *testMount) Release(ctx context.Context) error { - if m.initID != m.id { + if m.b.mounts[m.initID] != m { return m.b.mounts[m.initID].Release(ctx) } if m.unmounted { @@ -435,19 +606,21 @@ func (m *testMount) Release(ctx context.Context) error { type testFileBackend struct { } -func (b *testFileBackend) Mkdir(_ context.Context, m fileoptypes.Mount, a pb.FileActionMkDir) error { +func (b *testFileBackend) Mkdir(_ context.Context, m, user, group fileoptypes.Mount, a pb.FileActionMkDir) error { mm := m.(*testMount) if mm.callback != nil { mm.callback() } mm.id += "-mkdir" + mm.addUser(user, group) mm.chain = append(mm.chain, mod{mkdir: &a}) return nil } -func (b *testFileBackend) Mkfile(_ context.Context, m fileoptypes.Mount, a pb.FileActionMkFile) error { +func (b *testFileBackend) Mkfile(_ context.Context, m, user, group fileoptypes.Mount, a pb.FileActionMkFile) error { mm := m.(*testMount) mm.id += "-mkfile" + mm.addUser(user, group) mm.chain = append(mm.chain, mod{mkfile: &a}) return nil } @@ -457,10 +630,11 @@ func (b *testFileBackend) Rm(_ context.Context, m fileoptypes.Mount, a pb.FileAc mm.chain = append(mm.chain, mod{rm: &a}) return nil } -func (b *testFileBackend) Copy(_ context.Context, m1 fileoptypes.Mount, m fileoptypes.Mount, a pb.FileActionCopy) error { +func (b *testFileBackend) Copy(_ context.Context, m1, m, user, group fileoptypes.Mount, a pb.FileActionCopy) error { mm := m.(*testMount) mm1 := m1.(*testMount) mm.id += "-copy(" + mm1.id + ")" + mm.addUser(user, group) mm.chain = append(mm.chain, mod{copy: &a, copySrc: mm1.chain}) return nil } @@ -524,6 +698,6 @@ loop0: } for _, m := range b.mounts { - require.True(t, m.unmounted, "%s still mounted", m.id) + require.True(t, m.unmounted, "%s %p still mounted", m.id, m) } } diff --git a/solver/llbsolver/ops/fileoptypes/types.go b/solver/llbsolver/ops/fileoptypes/types.go index 3243e382..67aab026 100644 --- a/solver/llbsolver/ops/fileoptypes/types.go +++ b/solver/llbsolver/ops/fileoptypes/types.go @@ -16,10 +16,10 @@ type Mount interface { } type Backend interface { - Mkdir(context.Context, Mount, pb.FileActionMkDir) error - Mkfile(context.Context, Mount, pb.FileActionMkFile) error + Mkdir(context.Context, Mount, Mount, Mount, pb.FileActionMkDir) error + Mkfile(context.Context, Mount, Mount, Mount, pb.FileActionMkFile) error Rm(context.Context, Mount, pb.FileActionRm) error - Copy(context.Context, Mount, Mount, pb.FileActionCopy) error + Copy(context.Context, Mount, Mount, Mount, Mount, pb.FileActionCopy) error } type RefManager interface {