fileop: updates with new fsutil copy pkg
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>docker-19.03
parent
8a4674bab4
commit
0d17ac323e
|
@ -558,12 +558,13 @@ func (cc *cacheContext) lazyChecksum(ctx context.Context, m *mount, p string) (*
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, follow bool) (*CacheRecord, bool, error) {
|
func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, follow bool) (*CacheRecord, bool, error) {
|
||||||
|
origk := k
|
||||||
k, cr, err := getFollowLinks(root, k, follow)
|
k, cr, err := getFollowLinks(root, k, follow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
if cr == nil {
|
if cr == nil {
|
||||||
return nil, false, errors.Wrapf(errNotFound, "%s not found", convertKeyToPath(k))
|
return nil, false, errors.Wrapf(errNotFound, "%q not found", convertKeyToPath(origk))
|
||||||
}
|
}
|
||||||
if cr.Digest != "" {
|
if cr.Digest != "" {
|
||||||
return cr, false, nil
|
return cr, false, nil
|
||||||
|
|
|
@ -672,8 +672,12 @@ func testFileOpMkdirMkfile(t *testing.T, sb integration.Sandbox) {
|
||||||
defer os.RemoveAll(destDir)
|
defer os.RemoveAll(destDir)
|
||||||
|
|
||||||
_, err = c.Solve(context.TODO(), def, SolveOpt{
|
_, err = c.Solve(context.TODO(), def, SolveOpt{
|
||||||
Exporter: ExporterLocal,
|
Exports: []ExportEntry{
|
||||||
ExporterOutputDir: destDir,
|
{
|
||||||
|
Type: ExporterLocal,
|
||||||
|
OutputDir: destDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -722,8 +726,12 @@ func testFileOpCopyRm(t *testing.T, sb integration.Sandbox) {
|
||||||
defer os.RemoveAll(destDir)
|
defer os.RemoveAll(destDir)
|
||||||
|
|
||||||
_, err = c.Solve(context.TODO(), def, SolveOpt{
|
_, err = c.Solve(context.TODO(), def, SolveOpt{
|
||||||
Exporter: ExporterLocal,
|
Exports: []ExportEntry{
|
||||||
ExporterOutputDir: destDir,
|
{
|
||||||
|
Type: ExporterLocal,
|
||||||
|
OutputDir: destDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
LocalDirs: map[string]string{
|
LocalDirs: map[string]string{
|
||||||
"mylocal": dir,
|
"mylocal": dir,
|
||||||
"mylocal2": dir2,
|
"mylocal2": dir2,
|
||||||
|
|
|
@ -149,7 +149,7 @@ type fileActionMkdir struct {
|
||||||
func (a *fileActionMkdir) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
func (a *fileActionMkdir) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
||||||
return &pb.FileAction_Mkdir{
|
return &pb.FileAction_Mkdir{
|
||||||
Mkdir: &pb.FileActionMkDir{
|
Mkdir: &pb.FileActionMkDir{
|
||||||
Path: normalizePath(parent, a.file),
|
Path: normalizePath(parent, a.file, false),
|
||||||
Mode: int32(a.mode & 0777),
|
Mode: int32(a.mode & 0777),
|
||||||
MakeParents: a.info.MakeParents,
|
MakeParents: a.info.MakeParents,
|
||||||
Owner: a.info.ChownOpt.marshal(base),
|
Owner: a.info.ChownOpt.marshal(base),
|
||||||
|
@ -318,7 +318,7 @@ type fileActionMkfile struct {
|
||||||
func (a *fileActionMkfile) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
func (a *fileActionMkfile) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
||||||
return &pb.FileAction_Mkfile{
|
return &pb.FileAction_Mkfile{
|
||||||
Mkfile: &pb.FileActionMkFile{
|
Mkfile: &pb.FileActionMkFile{
|
||||||
Path: normalizePath(parent, a.file),
|
Path: normalizePath(parent, a.file, false),
|
||||||
Mode: int32(a.mode & 0777),
|
Mode: int32(a.mode & 0777),
|
||||||
Data: a.dt,
|
Data: a.dt,
|
||||||
Owner: a.info.ChownOpt.marshal(base),
|
Owner: a.info.ChownOpt.marshal(base),
|
||||||
|
@ -382,7 +382,7 @@ type fileActionRm struct {
|
||||||
func (a *fileActionRm) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
func (a *fileActionRm) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
||||||
return &pb.FileAction_Rm{
|
return &pb.FileAction_Rm{
|
||||||
Rm: &pb.FileActionRm{
|
Rm: &pb.FileActionRm{
|
||||||
Path: normalizePath(parent, a.file),
|
Path: normalizePath(parent, a.file, false),
|
||||||
AllowNotFound: a.info.AllowNotFound,
|
AllowNotFound: a.info.AllowNotFound,
|
||||||
AllowWildcard: a.info.AllowWildcard,
|
AllowWildcard: a.info.AllowWildcard,
|
||||||
},
|
},
|
||||||
|
@ -451,7 +451,7 @@ type fileActionCopy struct {
|
||||||
func (a *fileActionCopy) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
func (a *fileActionCopy) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
|
||||||
c := &pb.FileActionCopy{
|
c := &pb.FileActionCopy{
|
||||||
Src: a.sourcePath(),
|
Src: a.sourcePath(),
|
||||||
Dest: normalizePath(parent, a.dest),
|
Dest: normalizePath(parent, a.dest, true),
|
||||||
Owner: a.info.ChownOpt.marshal(base),
|
Owner: a.info.ChownOpt.marshal(base),
|
||||||
AllowWildcard: a.info.AllowWildcard,
|
AllowWildcard: a.info.AllowWildcard,
|
||||||
AllowEmptyWildcard: a.info.AllowEmptyWildcard,
|
AllowEmptyWildcard: a.info.AllowEmptyWildcard,
|
||||||
|
@ -683,11 +683,22 @@ func (f *FileOp) Marshal(c *Constraints) (digest.Digest, []byte, *pb.OpMetadata,
|
||||||
return f.Load()
|
return f.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizePath(parent, p string) string {
|
func normalizePath(parent, p string, keepSlash bool) string {
|
||||||
|
origPath := p
|
||||||
p = path.Clean(p)
|
p = path.Clean(p)
|
||||||
if !path.IsAbs(p) {
|
if !path.IsAbs(p) {
|
||||||
p = path.Join("/", parent, p)
|
p = path.Join("/", parent, p)
|
||||||
}
|
}
|
||||||
|
if keepSlash {
|
||||||
|
if strings.HasSuffix(origPath, "/") && !strings.HasSuffix(p, "/") {
|
||||||
|
p += "/"
|
||||||
|
} else if strings.HasSuffix(origPath, "/.") {
|
||||||
|
if p != "/" {
|
||||||
|
p += "/"
|
||||||
|
}
|
||||||
|
p += "."
|
||||||
|
}
|
||||||
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/containerd/containerd/pkg/seed"
|
"github.com/containerd/containerd/pkg/seed"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/containerd/containerd/sys"
|
"github.com/containerd/containerd/sys"
|
||||||
|
"github.com/docker/docker/pkg/reexec"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
|
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
|
||||||
"github.com/moby/buildkit/cache/remotecache"
|
"github.com/moby/buildkit/cache/remotecache"
|
||||||
|
@ -55,6 +56,7 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
apicaps.ExportedProduct = "buildkit"
|
apicaps.ExportedProduct = "buildkit"
|
||||||
seed.WithTimeAndRand()
|
seed.WithTimeAndRand()
|
||||||
|
reexec.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
type workerInitializerOpt struct {
|
type workerInitializerOpt struct {
|
||||||
|
|
|
@ -474,7 +474,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
|
||||||
case *instructions.WorkdirCommand:
|
case *instructions.WorkdirCommand:
|
||||||
err = dispatchWorkdir(d, c, true, &opt)
|
err = dispatchWorkdir(d, c, true, &opt)
|
||||||
case *instructions.AddCommand:
|
case *instructions.AddCommand:
|
||||||
err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, "", opt)
|
err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, c.Chown, opt)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, src := range c.Sources() {
|
for _, src := range c.Sources() {
|
||||||
if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
|
if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
|
||||||
|
@ -660,7 +660,8 @@ func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bo
|
||||||
}
|
}
|
||||||
d.image.Config.WorkingDir = wd
|
d.image.Config.WorkingDir = wd
|
||||||
if commit {
|
if commit {
|
||||||
if opt != nil && useFileOp(opt.buildArgValues, opt.llbCaps) {
|
withLayer := false
|
||||||
|
if wd != "/" && opt != nil && useFileOp(opt.buildArgValues, opt.llbCaps) {
|
||||||
mkdirOpt := []llb.MkdirOption{llb.WithParents(true)}
|
mkdirOpt := []llb.MkdirOption{llb.WithParents(true)}
|
||||||
if user := d.image.Config.User; user != "" {
|
if user := d.image.Config.User; user != "" {
|
||||||
mkdirOpt = append(mkdirOpt, llb.WithUser(user))
|
mkdirOpt = append(mkdirOpt, llb.WithUser(user))
|
||||||
|
@ -670,9 +671,9 @@ func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bo
|
||||||
platform = *d.platform
|
platform = *d.platform
|
||||||
}
|
}
|
||||||
d.state = d.state.File(llb.Mkdir(wd, 0755, mkdirOpt...), llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(opt.shlex, c.String(), d.state.Env())), d.prefixPlatform, &platform)))
|
d.state = d.state.File(llb.Mkdir(wd, 0755, mkdirOpt...), llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(opt.shlex, c.String(), d.state.Env())), d.prefixPlatform, &platform)))
|
||||||
|
withLayer = true
|
||||||
}
|
}
|
||||||
|
return commitToHistory(&d.image, "WORKDIR "+wd, withLayer, nil)
|
||||||
return commitToHistory(&d.image, "WORKDIR "+wd, false, nil)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1285,7 +1286,7 @@ func prefixCommand(ds *dispatchState, str string, prefixPlatform bool, platform
|
||||||
func useFileOp(args map[string]string, caps *apicaps.CapSet) bool {
|
func useFileOp(args map[string]string, caps *apicaps.CapSet) bool {
|
||||||
enabled := fileOpEnabled
|
enabled := fileOpEnabled
|
||||||
if v, ok := args["BUILDKIT_USE_FILEOP"]; ok {
|
if v, ok := args["BUILDKIT_USE_FILEOP"]; ok {
|
||||||
if b, err := strconv.ParseBool(v); err != nil {
|
if b, err := strconv.ParseBool(v); err == nil {
|
||||||
enabled = b
|
enabled = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ var allTests = []integration.Test{
|
||||||
testCopyChownCreateDest,
|
testCopyChownCreateDest,
|
||||||
testEmptyDestDir,
|
testEmptyDestDir,
|
||||||
testSymlinkedDockerfile,
|
testSymlinkedDockerfile,
|
||||||
|
testDockerfileAddArchiveWildcard,
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts []integration.TestOpt
|
var opts []integration.TestOpt
|
||||||
|
@ -1309,6 +1310,79 @@ ADD %s /newname.tar.gz
|
||||||
require.Equal(t, buf2.Bytes(), dt)
|
require.Equal(t, buf2.Bytes(), dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testDockerfileAddArchiveWildcard(t *testing.T, sb integration.Sandbox) {
|
||||||
|
f := getFrontend(t, sb)
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
tw := tar.NewWriter(buf)
|
||||||
|
expectedContent := []byte("content0")
|
||||||
|
err := tw.WriteHeader(&tar.Header{
|
||||||
|
Name: "foo",
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
Size: int64(len(expectedContent)),
|
||||||
|
Mode: 0644,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = tw.Write(expectedContent)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = tw.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
buf2 := bytes.NewBuffer(nil)
|
||||||
|
tw = tar.NewWriter(buf2)
|
||||||
|
expectedContent = []byte("content1")
|
||||||
|
err = tw.WriteHeader(&tar.Header{
|
||||||
|
Name: "bar",
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
Size: int64(len(expectedContent)),
|
||||||
|
Mode: 0644,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = tw.Write(expectedContent)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = tw.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dockerfile := []byte(`
|
||||||
|
FROM scratch
|
||||||
|
ADD *.tar /dest
|
||||||
|
`)
|
||||||
|
|
||||||
|
dir, err := tmpdir(
|
||||||
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
|
fstest.CreateFile("t.tar", buf.Bytes(), 0600),
|
||||||
|
fstest.CreateFile("b.tar", buf2.Bytes(), 0600),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
destDir, err := ioutil.TempDir("", "buildkit")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(destDir)
|
||||||
|
|
||||||
|
c, err := client.New(context.TODO(), sb.Address())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
_, err = f.Solve(context.TODO(), c, client.SolveOpt{
|
||||||
|
Exporter: client.ExporterLocal,
|
||||||
|
ExporterOutputDir: destDir,
|
||||||
|
LocalDirs: map[string]string{
|
||||||
|
builder.DefaultLocalNameDockerfile: dir,
|
||||||
|
builder.DefaultLocalNameContext: dir,
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dt, err := ioutil.ReadFile(filepath.Join(destDir, "dest/foo"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "content0", string(dt))
|
||||||
|
|
||||||
|
dt, err = ioutil.ReadFile(filepath.Join(destDir, "dest/bar"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "content1", string(dt))
|
||||||
|
}
|
||||||
|
|
||||||
func testSymlinkDestination(t *testing.T, sb integration.Sandbox) {
|
func testSymlinkDestination(t *testing.T, sb integration.Sandbox) {
|
||||||
f := getFrontend(t, sb)
|
f := getFrontend(t, sb)
|
||||||
f.RequiresBuildctl(t)
|
f.RequiresBuildctl(t)
|
||||||
|
@ -2061,7 +2135,7 @@ COPY sub/dir1 subdest6
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "foo-contents", string(dt))
|
require.Equal(t, "foo-contents", string(dt))
|
||||||
|
|
||||||
dt, err = ioutil.ReadFile(filepath.Join(destDir, "subdest/dir1/dir2/foo"))
|
dt, err = ioutil.ReadFile(filepath.Join(destDir, "subdest/dir2/foo"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "foo-contents", string(dt))
|
require.Equal(t, "foo-contents", string(dt))
|
||||||
|
|
||||||
|
|
|
@ -6,25 +6,33 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/continuity/fs"
|
"github.com/containerd/continuity/fs"
|
||||||
"github.com/moby/buildkit/snapshot"
|
"github.com/moby/buildkit/snapshot"
|
||||||
"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
|
"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
copy "github.com/tonistiigi/fsutil/copy"
|
copy "github.com/tonistiigi/fsutil/copy"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func mkdir(ctx context.Context, d string, action pb.FileActionMkDir, user *uidgid) error {
|
func timestampToTime(ts int64) *time.Time {
|
||||||
|
if ts == -1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tm := time.Unix(ts/1e9, ts%1e9)
|
||||||
|
return &tm
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkdir(ctx context.Context, d string, action pb.FileActionMkDir, user *copy.ChownOpt) error {
|
||||||
p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
|
p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if action.MakeParents {
|
if action.MakeParents {
|
||||||
if err := mkdirAll(p, os.FileMode(action.Mode)&0777, user); err != nil {
|
if err := copy.MkdirAll(p, os.FileMode(action.Mode)&0777, user, timestampToTime(action.Timestamp)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -34,26 +42,18 @@ func mkdir(ctx context.Context, d string, action pb.FileActionMkDir, user *uidgi
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if user != nil {
|
if err := copy.Chown(p, user); err != nil {
|
||||||
if err := os.Chown(p, user.uid, user.gid); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if err := copy.Utimes(p, timestampToTime(action.Timestamp)); err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
if action.Timestamp != -1 {
|
|
||||||
st := unix.Timespec{Sec: action.Timestamp / 1e9, Nsec: action.Timestamp % 1e9}
|
|
||||||
timespec := []unix.Timespec{st, st}
|
|
||||||
if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to utime %s", p)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkfile(ctx context.Context, d string, action pb.FileActionMkFile, user *uidgid) error {
|
func mkfile(ctx context.Context, d string, action pb.FileActionMkFile, user *copy.ChownOpt) error {
|
||||||
p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
|
p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -63,18 +63,12 @@ func mkfile(ctx context.Context, d string, action pb.FileActionMkFile, user *uid
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil {
|
if err := copy.Chown(p, user); err != nil {
|
||||||
if err := os.Chown(p, user.uid, user.gid); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if action.Timestamp != -1 {
|
if err := copy.Utimes(p, timestampToTime(action.Timestamp)); err != nil {
|
||||||
st := unix.Timespec{Sec: action.Timestamp / 1e9, Nsec: action.Timestamp % 1e9}
|
return err
|
||||||
timespec := []unix.Timespec{st, st}
|
|
||||||
if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to utime %s", p)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -96,50 +90,18 @@ func rm(ctx context.Context, d string, action pb.FileActionRm) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy, u *uidgid) error {
|
func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy, u *copy.ChownOpt) error {
|
||||||
// // src is the source path
|
srcPath := cleanPath(action.Src)
|
||||||
// Src string `protobuf:"bytes,1,opt,name=src,proto3" json:"src,omitempty"`
|
destPath := cleanPath(action.Dest)
|
||||||
// // dest path
|
|
||||||
// Dest string `protobuf:"bytes,2,opt,name=dest,proto3" json:"dest,omitempty"`
|
|
||||||
// // optional owner override
|
|
||||||
// Owner *ChownOpt `protobuf:"bytes,4,opt,name=owner" json:"owner,omitempty"`
|
|
||||||
// // optional permission bits override
|
|
||||||
// Mode int32 `protobuf:"varint,5,opt,name=mode,proto3" json:"mode,omitempty"`
|
|
||||||
// // followSymlink resolves symlinks in src
|
|
||||||
// FollowSymlink bool `protobuf:"varint,6,opt,name=followSymlink,proto3" json:"followSymlink,omitempty"`
|
|
||||||
// // dirCopyContents only copies contents if src is a directory
|
|
||||||
// DirCopyContents bool `protobuf:"varint,7,opt,name=dirCopyContents,proto3" json:"dirCopyContents,omitempty"`
|
|
||||||
// // attemptUnpackDockerCompatibility detects if src is an archive to unpack it instead
|
|
||||||
// AttemptUnpackDockerCompatibility bool `protobuf:"varint,8,opt,name=attemptUnpackDockerCompatibility,proto3" json:"attemptUnpackDockerCompatibility,omitempty"`
|
|
||||||
// // createDestPath creates dest path directories if needed
|
|
||||||
// CreateDestPath bool `protobuf:"varint,9,opt,name=createDestPath,proto3" json:"createDestPath,omitempty"`
|
|
||||||
// // allowWildcard allows filepath.Match wildcards in src path
|
|
||||||
// AllowWildcard bool `protobuf:"varint,10,opt,name=allowWildcard,proto3" json:"allowWildcard,omitempty"`
|
|
||||||
// // allowEmptyWildcard doesn't fail the whole copy if wildcard doesn't resolve to files
|
|
||||||
// AllowEmptyWildcard bool `protobuf:"varint,11,opt,name=allowEmptyWildcard,proto3" json:"allowEmptyWildcard,omitempty"`
|
|
||||||
// // optional created time override
|
|
||||||
// Timestamp int64 `protobuf:"varint,12,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
|
||||||
|
|
||||||
srcp, err := fs.RootPath(src, filepath.Join(filepath.Join("/", action.Src)))
|
if !action.CreateDestPath {
|
||||||
if err != nil {
|
p, err := fs.RootPath(dest, filepath.Join(filepath.Join("/", action.Dest)))
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
destp, err := fs.RootPath(dest, filepath.Join(filepath.Join("/", action.Dest)))
|
if _, err := os.Lstat(filepath.Dir(p)); err != nil {
|
||||||
if err != nil {
|
return errors.Wrapf(err, "failed to stat %s", action.Dest)
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var opt []copy.Opt
|
|
||||||
|
|
||||||
if action.AllowWildcard {
|
|
||||||
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 {
|
xattrErrorHandler := func(dst, src, key string, err error) error {
|
||||||
|
@ -147,15 +109,71 @@ func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy, u *
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
opt = append(opt, copy.WithXAttrErrorHandler(xattrErrorHandler))
|
opt := []copy.Opt{
|
||||||
|
func(ci *copy.CopyInfo) {
|
||||||
|
ci.Chown = u
|
||||||
|
ci.Utime = timestampToTime(action.Timestamp)
|
||||||
|
if m := int(action.Mode); m != -1 {
|
||||||
|
ci.Mode = &m
|
||||||
|
}
|
||||||
|
ci.CopyDirContents = action.DirCopyContents
|
||||||
|
ci.FollowLinks = action.FollowSymlink
|
||||||
|
},
|
||||||
|
copy.WithXAttrErrorHandler(xattrErrorHandler),
|
||||||
|
}
|
||||||
|
|
||||||
if err := copy.Copy(ctx, srcp, destp, opt...); err != nil {
|
if !action.AllowWildcard {
|
||||||
|
if action.AttemptUnpackDockerCompatibility {
|
||||||
|
if ok, err := unpack(ctx, src, srcPath, dest, destPath, u, timestampToTime(action.Timestamp)); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copy.Copy(ctx, src, srcPath, dest, destPath, opt...)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := copy.ResolveWildcards(src, srcPath, action.FollowSymlink)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(m) == 0 {
|
||||||
|
if action.AllowEmptyWildcard {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Errorf("%s not found", srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range m {
|
||||||
|
if action.AttemptUnpackDockerCompatibility {
|
||||||
|
if ok, err := unpack(ctx, src, s, dest, destPath, u, timestampToTime(action.Timestamp)); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := copy.Copy(ctx, src, s, dest, destPath, opt...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanPath(s string) string {
|
||||||
|
s2 := filepath.Join("/", s)
|
||||||
|
if strings.HasSuffix(s, "/.") {
|
||||||
|
if s2 != "/" {
|
||||||
|
s2 += "/"
|
||||||
|
}
|
||||||
|
s2 += "."
|
||||||
|
} else if strings.HasSuffix(s, "/") && s2 != "/" {
|
||||||
|
s2 += "/"
|
||||||
|
}
|
||||||
|
return s2
|
||||||
|
}
|
||||||
|
|
||||||
type Backend struct {
|
type Backend struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +262,5 @@ func (fb *Backend) Copy(ctx context.Context, m1, m2, user, group fileoptypes.Mou
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("copy %+v %+v %+v", action.Owner, user, group)
|
|
||||||
|
|
||||||
return docopy(ctx, src, dest, action, u)
|
return docopy(ctx, src, dest, action, u)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package file
|
|
||||||
|
|
||||||
func fixRootDirectory(p string) string {
|
|
||||||
return p
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
// +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
|
|
||||||
}
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/fs"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
|
copy "github.com/tonistiigi/fsutil/copy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unpack(ctx context.Context, srcRoot string, src string, destRoot string, dest string, user *copy.ChownOpt, tm *time.Time) (bool, error) {
|
||||||
|
src, err := fs.RootPath(srcRoot, src)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !isArchivePath(src) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dest, err = fs.RootPath(destRoot, dest)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if err := copy.MkdirAll(dest, 0755, user, tm); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return true, chrootarchive.Untar(file, dest, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isArchivePath(path string) bool {
|
||||||
|
fi, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeType != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
rdr, err := archive.DecompressStream(file)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r := tar.NewReader(rdr)
|
||||||
|
_, err = r.Next()
|
||||||
|
return err == nil
|
||||||
|
}
|
|
@ -9,17 +9,14 @@ import (
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
"github.com/opencontainers/runc/libcontainer/user"
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
copy "github.com/tonistiigi/fsutil/copy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type uidgid struct {
|
func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*copy.ChownOpt, error) {
|
||||||
uid, gid int
|
|
||||||
}
|
|
||||||
|
|
||||||
func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*uidgid, error) {
|
|
||||||
if chopt == nil {
|
if chopt == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var us uidgid
|
var us copy.ChownOpt
|
||||||
if chopt.User != nil {
|
if chopt.User != nil {
|
||||||
switch u := chopt.User.User.(type) {
|
switch u := chopt.User.User.(type) {
|
||||||
case *pb.UserOpt_ByName:
|
case *pb.UserOpt_ByName:
|
||||||
|
@ -61,12 +58,12 @@ func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*uidgid, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(users) > 0 {
|
if len(users) > 0 {
|
||||||
us.uid = users[0].Uid
|
us.Uid = users[0].Uid
|
||||||
us.gid = users[0].Gid
|
us.Gid = users[0].Gid
|
||||||
}
|
}
|
||||||
case *pb.UserOpt_ByID:
|
case *pb.UserOpt_ByID:
|
||||||
us.uid = int(u.ByID)
|
us.Uid = int(u.ByID)
|
||||||
us.gid = int(u.ByID)
|
us.Gid = int(u.ByID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,10 +108,10 @@ func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*uidgid, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(groups) > 0 {
|
if len(groups) > 0 {
|
||||||
us.gid = groups[0].Gid
|
us.Gid = groups[0].Gid
|
||||||
}
|
}
|
||||||
case *pb.UserOpt_ByID:
|
case *pb.UserOpt_ByID:
|
||||||
us.gid = int(u.ByID)
|
us.Gid = int(u.ByID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,8 +88,8 @@ func (f *fileOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, boo
|
||||||
markInvalid(action.Input)
|
markInvalid(action.Input)
|
||||||
processOwner(p.Owner, selectors)
|
processOwner(p.Owner, selectors)
|
||||||
if action.SecondaryInput != -1 && int(action.SecondaryInput) < f.numInputs {
|
if action.SecondaryInput != -1 && int(action.SecondaryInput) < f.numInputs {
|
||||||
p.Src = path.Base(p.Src)
|
|
||||||
addSelector(selectors, int(action.SecondaryInput), p.Src, p.AllowWildcard, p.FollowSymlink)
|
addSelector(selectors, int(action.SecondaryInput), p.Src, p.AllowWildcard, p.FollowSymlink)
|
||||||
|
p.Src = path.Base(p.Src)
|
||||||
}
|
}
|
||||||
dt, err = json.Marshal(p)
|
dt, err = json.Marshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -375,7 +375,7 @@ func (s *FileOpSolver) validate(idx int, inputs []fileoptypes.Ref, actions []*pb
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptypes.Ref, actions []*pb.FileAction) (input, error) {
|
func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptypes.Ref, actions []*pb.FileAction) (input, error) {
|
||||||
inp, err := s.g.Do(ctx, fmt.Sprintf("inp-%d", idx), func(ctx context.Context) (interface{}, error) {
|
inp, err := s.g.Do(ctx, fmt.Sprintf("inp-%d", idx), func(ctx context.Context) (_ interface{}, err error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
inp := s.ins[idx]
|
inp := s.ins[idx]
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
@ -391,14 +391,18 @@ func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptyp
|
||||||
return inp, nil
|
return inp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var inpMount, inpMountSecondary fileoptypes.Mount
|
||||||
var toRelease []fileoptypes.Mount
|
var toRelease []fileoptypes.Mount
|
||||||
|
var inpMountPrepared bool
|
||||||
defer func() {
|
defer func() {
|
||||||
for _, m := range toRelease {
|
for _, m := range toRelease {
|
||||||
m.Release(context.TODO())
|
m.Release(context.TODO())
|
||||||
}
|
}
|
||||||
|
if err != nil && inpMount != nil && inpMountPrepared {
|
||||||
|
inpMount.Release(context.TODO())
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var inpMount, inpMountSecondary fileoptypes.Mount
|
|
||||||
action := actions[idx-len(inputs)]
|
action := actions[idx-len(inputs)]
|
||||||
|
|
||||||
loadInput := func(ctx context.Context) func() error {
|
loadInput := func(ctx context.Context) func() error {
|
||||||
|
@ -413,6 +417,7 @@ func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptyp
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
inpMount = m
|
inpMount = m
|
||||||
|
inpMountPrepared = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
inpMount = inp.mount
|
inpMount = inp.mount
|
||||||
|
@ -511,6 +516,7 @@ func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptyp
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
inpMount = m
|
inpMount = m
|
||||||
|
inpMountPrepared = true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch a := action.Action.(type) {
|
switch a := action.Action.(type) {
|
||||||
|
|
Loading…
Reference in New Issue