Skip to content
This repository was archived by the owner on Jun 3, 2025. It is now read-only.

Commit b9b61e2

Browse files
authored
Merge pull request #943 from shortstories/bugfix/copy-with-symlink
Fix #942 COPY or ADD to symlink destination breaks image
2 parents 00a01f9 + df767bb commit b9b61e2

4 files changed

Lines changed: 76 additions & 36 deletions

File tree

pkg/commands/copy.go

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ import (
2020
"fmt"
2121
"os"
2222
"path/filepath"
23+
"strings"
2324

25+
"github.com/GoogleContainerTools/kaniko/pkg/constants"
2426
"github.com/moby/buildkit/frontend/dockerfile/instructions"
2527
"github.com/pkg/errors"
2628
"github.com/sirupsen/logrus"
2729

28-
"github.com/GoogleContainerTools/kaniko/pkg/constants"
29-
3030
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
3131
"github.com/GoogleContainerTools/kaniko/pkg/util"
3232
v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -59,11 +59,15 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
5959
if err != nil {
6060
return err
6161
}
62+
if fi.IsDir() && !strings.HasSuffix(fullPath, string(os.PathSeparator)) {
63+
fullPath += "/"
64+
}
6265
cwd := config.WorkingDir
6366
if cwd == "" {
6467
cwd = constants.RootDir
6568
}
66-
destPath, err := util.DestinationFilepath(src, dest, cwd)
69+
70+
destPath, err := util.DestinationFilepath(fullPath, dest, cwd)
6771
if err != nil {
6872
return err
6973
}
@@ -76,11 +80,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
7680
}
7781

7882
if fi.IsDir() {
79-
if !filepath.IsAbs(dest) {
80-
// we need to add '/' to the end to indicate the destination is a directory
81-
dest = filepath.Join(cwd, dest) + "/"
82-
}
83-
copiedFiles, err := util.CopyDir(fullPath, dest, c.buildcontext)
83+
copiedFiles, err := util.CopyDir(fullPath, destPath, c.buildcontext)
8484
if err != nil {
8585
return err
8686
}
@@ -197,21 +197,42 @@ func (cr *CachingCopyCommand) From() string {
197197
}
198198

199199
func resolveIfSymlink(destPath string) (string, error) {
200-
baseDir := filepath.Dir(destPath)
201-
if info, err := os.Lstat(baseDir); err == nil {
202-
switch mode := info.Mode(); {
203-
case mode&os.ModeSymlink != 0:
204-
linkPath, err := os.Readlink(baseDir)
205-
if err != nil {
206-
return "", errors.Wrap(err, "error reading symlink")
200+
if !filepath.IsAbs(destPath) {
201+
return "", errors.New("dest path must be abs")
202+
}
203+
204+
var nonexistentPaths []string
205+
206+
newPath := destPath
207+
for newPath != "/" {
208+
_, err := os.Lstat(newPath)
209+
if err != nil {
210+
if os.IsNotExist(err) {
211+
dir, file := filepath.Split(newPath)
212+
newPath = filepath.Clean(dir)
213+
nonexistentPaths = append(nonexistentPaths, file)
214+
continue
215+
} else {
216+
return "", errors.Wrap(err, "failed to lstat")
207217
}
208-
absLinkPath := filepath.Join(filepath.Dir(baseDir), linkPath)
209-
newPath := filepath.Join(absLinkPath, filepath.Base(destPath))
210-
logrus.Tracef("Updating destination path from %v to %v due to symlink", destPath, newPath)
211-
return newPath, nil
212218
}
219+
220+
newPath, err = filepath.EvalSymlinks(newPath)
221+
if err != nil {
222+
return "", errors.Wrap(err, "failed to eval symlinks")
223+
}
224+
break
213225
}
214-
return destPath, nil
226+
227+
for i := len(nonexistentPaths) - 1; i >= 0; i-- {
228+
newPath = filepath.Join(newPath, nonexistentPaths[i])
229+
}
230+
231+
if destPath != newPath {
232+
logrus.Tracef("Updating destination path from %v to %v due to symlink", destPath, newPath)
233+
}
234+
235+
return filepath.Clean(newPath), nil
215236
}
216237

217238
func copyCmdFilesUsedFromContext(

pkg/commands/copy_test.go

100644100755
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,28 @@ func Test_resolveIfSymlink(t *testing.T) {
194194
if err != nil {
195195
t.Error(err)
196196
}
197-
cases := []testCase{{destPath: thepath, expectedPath: thepath, err: nil}}
198-
197+
cases := []testCase{
198+
{destPath: thepath, expectedPath: thepath, err: nil},
199+
{destPath: "/", expectedPath: "/", err: nil},
200+
}
199201
baseDir = tmpDir
200202
symLink := filepath.Join(baseDir, "symlink")
201203
if err := os.Symlink(filepath.Base(thepath), symLink); err != nil {
202204
t.Error(err)
203205
}
204-
cases = append(cases, testCase{filepath.Join(symLink, "foo.txt"), filepath.Join(thepath, "foo.txt"), nil})
206+
cases = append(cases,
207+
testCase{filepath.Join(symLink, "foo.txt"), filepath.Join(thepath, "foo.txt"), nil},
208+
testCase{filepath.Join(symLink, "inner", "foo.txt"), filepath.Join(thepath, "inner", "foo.txt"), nil},
209+
)
210+
211+
absSymlink := filepath.Join(tmpDir, "abs-symlink")
212+
if err := os.Symlink(thepath, absSymlink); err != nil {
213+
t.Error(err)
214+
}
215+
cases = append(cases,
216+
testCase{filepath.Join(absSymlink, "foo.txt"), filepath.Join(thepath, "foo.txt"), nil},
217+
testCase{filepath.Join(absSymlink, "inner", "foo.txt"), filepath.Join(thepath, "inner", "foo.txt"), nil},
218+
)
205219

206220
for i, c := range cases {
207221
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {

pkg/util/command_util.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,20 +164,25 @@ func IsDestDir(path string) bool {
164164
// If dest is a dir, copy it to /dest/relpath
165165
// If dest is a file, copy directly to dest
166166
// If source is a dir:
167-
// Assume dest is also a dir, and copy to dest/relpath
167+
// Assume dest is also a dir, and copy to dest/
168168
// If dest is not an absolute filepath, add /cwd to the beginning
169169
func DestinationFilepath(src, dest, cwd string) (string, error) {
170-
if IsDestDir(dest) {
171-
destPath := filepath.Join(dest, filepath.Base(src))
172-
if filepath.IsAbs(dest) {
173-
return destPath, nil
174-
}
175-
return filepath.Join(cwd, destPath), nil
170+
_, srcFileName := filepath.Split(src)
171+
newDest := dest
172+
173+
if IsDestDir(newDest) {
174+
newDest = filepath.Join(newDest, srcFileName)
176175
}
177-
if filepath.IsAbs(dest) {
178-
return dest, nil
176+
177+
if !filepath.IsAbs(newDest) {
178+
newDest = filepath.Join(cwd, newDest)
179179
}
180-
return filepath.Join(cwd, dest), nil
180+
181+
if len(srcFileName) <= 0 && !strings.HasSuffix(newDest, "/") {
182+
newDest += "/"
183+
}
184+
185+
return newDest, nil
181186
}
182187

183188
// URLDestinationFilepath gives the destination a file from a remote URL should be saved to

pkg/util/command_util_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,13 @@ var destinationFilepathTests = []struct {
153153
src: "context/bar/",
154154
cwd: "/",
155155
dest: "pkg/",
156-
expectedFilepath: "/pkg/bar",
156+
expectedFilepath: "/pkg/",
157157
},
158158
{
159159
src: "context/bar/",
160160
cwd: "/newdir",
161161
dest: "pkg/",
162-
expectedFilepath: "/newdir/pkg/bar",
162+
expectedFilepath: "/newdir/pkg/",
163163
},
164164
{
165165
src: "./context/empty",
@@ -177,7 +177,7 @@ var destinationFilepathTests = []struct {
177177
src: "./",
178178
cwd: "/",
179179
dest: "/dir",
180-
expectedFilepath: "/dir",
180+
expectedFilepath: "/dir/",
181181
},
182182
{
183183
src: "context/foo",

0 commit comments

Comments
 (0)