From da7e9928e44a4d6b82ef686e03e82b2f1d23bc06 Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Thu, 16 Jan 2020 15:51:02 -0800 Subject: [PATCH] Fix Symlinks not being copies across stages --- .../dockerfiles/Dockerfile_test_copy_symlink | 5 ++ pkg/commands/copy.go | 2 +- pkg/executor/build.go | 25 +++++-- pkg/snapshot/snapshot.go | 22 ++++-- pkg/util/fs_util.go | 70 +++++++++++++++---- 5 files changed, 97 insertions(+), 27 deletions(-) create mode 100644 integration/dockerfiles/Dockerfile_test_copy_symlink diff --git a/integration/dockerfiles/Dockerfile_test_copy_symlink b/integration/dockerfiles/Dockerfile_test_copy_symlink new file mode 100644 index 000000000..efcff4412 --- /dev/null +++ b/integration/dockerfiles/Dockerfile_test_copy_symlink @@ -0,0 +1,5 @@ +FROM alpine:3.11 as t +RUN apk add gcc + +FROM scratch +COPY --from=t /usr/lib/libstdc++.so.6 /usr/lib/ \ No newline at end of file diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go index bac74584f..2056b106c 100644 --- a/pkg/commands/copy.go +++ b/pkg/commands/copy.go @@ -85,7 +85,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu return err } c.snapshotFiles = append(c.snapshotFiles, copiedFiles...) - } else if fi.Mode()&os.ModeSymlink != 0 { + } else if util.IsSymlink(fi) { // If file is a symlink, we want to create the same relative symlink exclude, err := util.CopySymlink(fullPath, destPath, c.buildcontext) if err != nil { diff --git a/pkg/executor/build.go b/pkg/executor/build.go index d1c17cf1d..d55c0a875 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -23,8 +23,6 @@ import ( "strconv" "time" - otiai10Cpy "github.com/otiai10/copy" - "github.com/google/go-containerregistry/pkg/v1/partial" "github.com/moby/buildkit/frontend/dockerfile/instructions" @@ -565,6 +563,7 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) { } } + // filesToSave, err := filesToSave(crossStageDependencies[index]) if err != nil { return nil, err @@ -574,8 +573,8 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) { return nil, err } for _, p := range filesToSave { - logrus.Infof("Saving file %s for later use.", p) - otiai10Cpy.Copy(p, filepath.Join(dstDir, p)) + logrus.Infof("Saving file %s for later use", p) + util.CopyFileOrSymlink(p, dstDir) } // Delete the filesystem @@ -587,16 +586,28 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) { return nil, err } +// fileToSave returns all the files matching the given pattern in deps. +// If a file is a symlink, it also returns the target file. +// It first returns all the target files and then symlinks so they can copied +// in that order to the kaniko workspace. func filesToSave(deps []string) ([]string, error) { - allFiles := []string{} + srcFiles := []string{} + symLinks := []string{} for _, src := range deps { srcs, err := filepath.Glob(src) if err != nil { return nil, err } - allFiles = append(allFiles, srcs...) + for _, f := range srcs { + if link, err := util.CanonicalizeLink(f); err == nil { + symLinks = append(symLinks, f) + srcFiles = append(srcFiles, link) + } else { + srcFiles = append(srcFiles, f) + } + } } - return allFiles, nil + return append(srcFiles, symLinks...), nil } func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) error { diff --git a/pkg/snapshot/snapshot.go b/pkg/snapshot/snapshot.go index 067e347f0..8029f9ae2 100644 --- a/pkg/snapshot/snapshot.go +++ b/pkg/snapshot/snapshot.go @@ -112,7 +112,6 @@ func (s *Snapshotter) TakeSnapshotFS() (string, error) { if err := writeToTar(t, filesToAdd, filesToWhiteOut); err != nil { return "", err } - return f.Name(), nil } @@ -170,7 +169,7 @@ func (s *Snapshotter) scanFullFilesystem() ([]string, []string, error) { filesToAdd := []string{} for path := range memFs { if util.CheckWhitelist(path) { - logrus.Tracef("Not adding %s to layer, as it's whitelisted", path) + logrus.Infof("Not adding %s to layer, as it's whitelisted", path) continue } // Only add changed files. @@ -179,8 +178,12 @@ func (s *Snapshotter) scanFullFilesystem() ([]string, []string, error) { return nil, nil, fmt.Errorf("could not check if file has changed %s %s", path, err) } if fileChanged { - logrus.Tracef("Adding %s to layer, because it was changed.", path) - filesToAdd = append(filesToAdd, path) + files, err := filesWithLinks(path) + if err != nil { + return nil, nil, err + } + logrus.Debug("Adding files %s to layer, because it was changed.", files) + filesToAdd = append(filesToAdd, files...) } } @@ -188,7 +191,6 @@ func (s *Snapshotter) scanFullFilesystem() ([]string, []string, error) { filesToAdd = filesWithParentDirs(filesToAdd) sort.Strings(filesToAdd) - // Add files to the layered map for _, file := range filesToAdd { if err := s.l.Add(file); err != nil { @@ -236,3 +238,13 @@ func filesWithParentDirs(files []string) []string { return newFiles } + +func filesWithLinks(path string) ([]string, error) { + link, err := util.CanonicalizeLink(path) + if err == util.NotSymLink { + return []string{path}, nil + } else if err != nil { + return nil, err + } + return []string{path, link}, nil +} diff --git a/pkg/util/fs_util.go b/pkg/util/fs_util.go index a5decd530..45bf61150 100644 --- a/pkg/util/fs_util.go +++ b/pkg/util/fs_util.go @@ -30,6 +30,8 @@ import ( "syscall" "time" + otiai10Cpy "github.com/otiai10/copy" + "github.com/GoogleContainerTools/kaniko/pkg/constants" "github.com/docker/docker/builder/dockerignore" "github.com/docker/docker/pkg/fileutils" @@ -443,18 +445,8 @@ func FilepathExists(path string) bool { // CreateFile creates a file at path and copies over contents from the reader func CreateFile(path string, reader io.Reader, perm os.FileMode, uid uint32, gid uint32) error { // Create directory path if it doesn't exist - baseDir := filepath.Dir(path) - if info, err := os.Lstat(baseDir); os.IsNotExist(err) { - logrus.Tracef("baseDir %s for file %s does not exist. Creating.", baseDir, path) - if err := os.MkdirAll(baseDir, 0755); err != nil { - return err - } - } else { - switch mode := info.Mode(); { - case mode&os.ModeSymlink != 0: - logrus.Infof("destination cannot be a symlink %v", baseDir) - return errors.New("destination cannot be a symlink") - } + if err := createParentDirectory(path); err != nil { + return err } dest, err := os.Create(path) if err != nil { @@ -531,7 +523,7 @@ func CopyDir(src, dest, buildcontext string) ([]string, error) { if err := mkdirAllWithPermissions(destPath, mode, uid, gid); err != nil { return nil, err } - } else if fi.Mode()&os.ModeSymlink != 0 { + } else if IsSymlink(fi) { // If file is a symlink, we want to create the same relative symlink if _, err := CopySymlink(fullPath, destPath, buildcontext); err != nil { return nil, err @@ -553,7 +545,7 @@ func CopySymlink(src, dest, buildcontext string) (bool, error) { logrus.Debugf("%s found in .dockerignore, ignoring", src) return true, nil } - link, err := os.Readlink(src) + link, err := filepath.EvalSymlinks(src) if err != nil { return false, err } @@ -562,6 +554,9 @@ func CopySymlink(src, dest, buildcontext string) (bool, error) { return false, err } } + if err := createParentDirectory(dest); err != nil { + return false, err + } return false, os.Symlink(link, dest) } @@ -690,3 +685,50 @@ func CreateTargetTarfile(tarpath string) (*os.File, error) { return os.Create(tarpath) } + +// Returns true if a file is a symlink +func IsSymlink(fi os.FileInfo) bool { + return fi.Mode()&os.ModeSymlink != 0 +} + +var NotSymLink = fmt.Errorf("not a symlink") + +func CanonicalizeLink(path string) (string, error) { + fi, err := os.Lstat(path) + if err != nil { + return "", err + } + if !IsSymlink(fi) { + return "", NotSymLink + } + return filepath.EvalSymlinks(path) +} + +// otiai10Cpy.Copy in case the src file is a symlink, will copy the target +// file at destination instead of creating a symlink. See #915 for more details. +func CopyFileOrSymlink(src string, destDir string) error { + destFile := filepath.Join(destDir, src) + if fi, _ := os.Lstat(src); IsSymlink(fi) { + if link, err := os.Readlink(src); err != nil { + return err + } else { + return os.Symlink(link, destFile) + } + } else { + return otiai10Cpy.Copy(src, destFile) + } +} + +func createParentDirectory(path string) error { + baseDir := filepath.Dir(path) + if info, err := os.Lstat(baseDir); os.IsNotExist(err) { + logrus.Tracef("baseDir %s for file %s does not exist. Creating.", baseDir, path) + if err := os.MkdirAll(baseDir, 0755); err != nil { + return err + } + } else if IsSymlink(info) { + logrus.Infof("destination cannot be a symlink %v", baseDir) + return errors.New("destination cannot be a symlink") + } + return nil +}