Merge pull request #1114 from tejal29/fix_copy
Add more tests for Copy and some fixes.
This commit is contained in:
commit
c718dc61a6
|
|
@ -1,14 +1,10 @@
|
||||||
FROM busybox as t
|
FROM busybox as t
|
||||||
RUN echo "hello" > /tmp/target
|
RUN mkdir temp
|
||||||
RUN ln -s /tmp/target /tmp/link
|
RUN echo "hello" > temp/target
|
||||||
|
RUN ln -s target temp/link
|
||||||
## Relative link
|
|
||||||
RUN cd tmp && ln -s target relative_link
|
|
||||||
|
|
||||||
## Relative link with paths
|
## Relative link with paths
|
||||||
RUN mkdir workdir && cd workdir && ln -s ../tmp/target relative_dir_link
|
RUN mkdir workdir && cd workdir && ln -s ../temp/target relative_link
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
COPY --from=t /tmp/link /tmp/
|
COPY --from=t temp/ dest/
|
||||||
COPY --from=t /tmp/relative_link /tmp/
|
COPY --from=t /workdir/relative_link /workdirAnother/
|
||||||
COPY --from=t /workdir/relative_dir_link /workdir/
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
|
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||||
|
|
||||||
|
|
@ -66,18 +67,18 @@ func (a *AddCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui
|
||||||
}
|
}
|
||||||
logrus.Infof("Adding remote URL %s to %s", src, urlDest)
|
logrus.Infof("Adding remote URL %s to %s", src, urlDest)
|
||||||
if err := util.DownloadFileToDest(src, urlDest); err != nil {
|
if err := util.DownloadFileToDest(src, urlDest); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "downloading remote source file")
|
||||||
}
|
}
|
||||||
a.snapshotFiles = append(a.snapshotFiles, urlDest)
|
a.snapshotFiles = append(a.snapshotFiles, urlDest)
|
||||||
} else if util.IsFileLocalTarArchive(fullPath) {
|
} else if util.IsFileLocalTarArchive(fullPath) {
|
||||||
tarDest, err := util.DestinationFilepath("", dest, config.WorkingDir)
|
tarDest, err := util.DestinationFilepath("", dest, config.WorkingDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "determining dest for tar")
|
||||||
}
|
}
|
||||||
logrus.Infof("Unpacking local tar archive %s to %s", src, tarDest)
|
logrus.Infof("Unpacking local tar archive %s to %s", src, tarDest)
|
||||||
extractedFiles, err := util.UnpackLocalTarArchive(fullPath, tarDest)
|
extractedFiles, err := util.UnpackLocalTarArchive(fullPath, tarDest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "unpacking local tar")
|
||||||
}
|
}
|
||||||
logrus.Debugf("Added %v from local tar archive %s", extractedFiles, src)
|
logrus.Debugf("Added %v from local tar archive %s", extractedFiles, src)
|
||||||
a.snapshotFiles = append(a.snapshotFiles, extractedFiles...)
|
a.snapshotFiles = append(a.snapshotFiles, extractedFiles...)
|
||||||
|
|
@ -98,7 +99,7 @@ func (a *AddCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := copyCmd.ExecuteCommand(config, buildArgs); err != nil {
|
if err := copyCmd.ExecuteCommand(config, buildArgs); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "executing copy command")
|
||||||
}
|
}
|
||||||
a.snapshotFiles = append(a.snapshotFiles, copyCmd.snapshotFiles...)
|
a.snapshotFiles = append(a.snapshotFiles, copyCmd.snapshotFiles...)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -54,17 +54,18 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
|
||||||
|
|
||||||
uid, gid, err := getUserGroup(c.cmd.Chown, replacementEnvs)
|
uid, gid, err := getUserGroup(c.cmd.Chown, replacementEnvs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "getting user group from chowm")
|
||||||
}
|
}
|
||||||
|
|
||||||
srcs, dest, err := util.ResolveEnvAndWildcards(c.cmd.SourcesAndDest, c.buildcontext, replacementEnvs)
|
srcs, dest, err := util.ResolveEnvAndWildcards(c.cmd.SourcesAndDest, c.buildcontext, replacementEnvs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "resolving src")
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each source, iterate through and copy it over
|
// For each source, iterate through and copy it over
|
||||||
for _, src := range srcs {
|
for _, src := range srcs {
|
||||||
fullPath := filepath.Join(c.buildcontext, src)
|
fullPath := filepath.Join(c.buildcontext, src)
|
||||||
|
|
||||||
fi, err := os.Lstat(fullPath)
|
fi, err := os.Lstat(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not copy source")
|
return errors.Wrap(err, "could not copy source")
|
||||||
|
|
@ -79,27 +80,27 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
|
||||||
|
|
||||||
destPath, err := util.DestinationFilepath(fullPath, dest, cwd)
|
destPath, err := util.DestinationFilepath(fullPath, dest, cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "find destination path")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the destination dir is a symlink we need to resolve the path and use
|
// If the destination dir is a symlink we need to resolve the path and use
|
||||||
// that instead of the symlink path
|
// that instead of the symlink path
|
||||||
destPath, err = resolveIfSymlink(destPath)
|
destPath, err = resolveIfSymlink(destPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "resolving dest symlink")
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
copiedFiles, err := util.CopyDir(fullPath, destPath, c.buildcontext, uid, gid)
|
copiedFiles, err := util.CopyDir(fullPath, destPath, c.buildcontext, uid, gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "copying dir")
|
||||||
}
|
}
|
||||||
c.snapshotFiles = append(c.snapshotFiles, copiedFiles...)
|
c.snapshotFiles = append(c.snapshotFiles, copiedFiles...)
|
||||||
} else if util.IsSymlink(fi) {
|
} else if util.IsSymlink(fi) {
|
||||||
// If file is a symlink, we want to copy the target file to destPath
|
// If file is a symlink, we want to copy the target file to destPath
|
||||||
exclude, err := util.CopySymlink(fullPath, destPath, c.buildcontext, uid, gid)
|
exclude, err := util.CopySymlink(fullPath, destPath, c.buildcontext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "copying symlink")
|
||||||
}
|
}
|
||||||
if exclude {
|
if exclude {
|
||||||
continue
|
continue
|
||||||
|
|
@ -109,7 +110,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
|
||||||
// ... Else, we want to copy over a file
|
// ... Else, we want to copy over a file
|
||||||
exclude, err := util.CopyFile(fullPath, destPath, c.buildcontext, uid, gid)
|
exclude, err := util.CopyFile(fullPath, destPath, c.buildcontext, uid, gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "copying file")
|
||||||
}
|
}
|
||||||
if exclude {
|
if exclude {
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -442,3 +442,449 @@ func TestGetUserGroup(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCopyCommand_ExecuteCommand_Extended(t *testing.T) {
|
||||||
|
setupDirs := func(t *testing.T) (string, string) {
|
||||||
|
testDir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Join(testDir, "bar")
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dir, 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file := filepath.Join(dir, "bam.txt")
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(file, []byte("meow"), 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
targetPath := filepath.Join(dir, "dam.txt")
|
||||||
|
if err := ioutil.WriteFile(targetPath, []byte("woof"), 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Symlink("dam.txt", filepath.Join(dir, "sym.link")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return testDir, filepath.Base(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("copy dir to another dir", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
expected, err := ioutil.ReadDir(filepath.Join(testDir, srcDir))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{srcDir, "dest"},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "dest" dir exists with contents of srcDir
|
||||||
|
actual, err := ioutil.ReadDir(filepath.Join(testDir, "dest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i, f := range actual {
|
||||||
|
testutil.CheckDeepEqual(t, expected[i].Name(), f.Name())
|
||||||
|
testutil.CheckDeepEqual(t, expected[i].Mode(), f.Mode())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("copy file to a dir", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{filepath.Join(srcDir, "bam.txt"), "dest/"},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "dest" dir exists with file bam.txt
|
||||||
|
files, err := ioutil.ReadDir(filepath.Join(testDir, "dest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, 1, len(files))
|
||||||
|
testutil.CheckDeepEqual(t, files[0].Name(), "bam.txt")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("copy file to a filepath", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{filepath.Join(srcDir, "bam.txt"), "dest"},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if bam.txt is copied to dest file
|
||||||
|
if _, err := os.Lstat(filepath.Join(testDir, "dest")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("copy file to a dir without trailing /", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
destDir := filepath.Join(testDir, "dest")
|
||||||
|
if err := os.MkdirAll(destDir, 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{filepath.Join(srcDir, "bam.txt"), "dest"},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "dest" dir exists with file bam.txt
|
||||||
|
files, err := ioutil.ReadDir(filepath.Join(testDir, "dest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, 1, len(files))
|
||||||
|
testutil.CheckDeepEqual(t, files[0].Name(), "bam.txt")
|
||||||
|
|
||||||
|
})
|
||||||
|
t.Run("copy symlink file to a dir", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{filepath.Join(srcDir, "sym.link"), "dest/"},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "dest" dir exists with link sym.link
|
||||||
|
files, err := ioutil.ReadDir(filepath.Join(testDir, "dest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// bam.txt and sym.link should be present
|
||||||
|
testutil.CheckDeepEqual(t, 1, len(files))
|
||||||
|
testutil.CheckDeepEqual(t, files[0].Name(), "sym.link")
|
||||||
|
testutil.CheckDeepEqual(t, true, files[0].Mode()&os.ModeSymlink != 0)
|
||||||
|
linkName, err := os.Readlink(filepath.Join(testDir, "dest", "sym.link"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, linkName, "dam.txt")
|
||||||
|
})
|
||||||
|
t.Run("copy deadlink symlink file to a dir", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
doesNotExists := filepath.Join(testDir, "dead.txt")
|
||||||
|
if err := ioutil.WriteFile(doesNotExists, []byte("remove me"), 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Symlink("../dead.txt", filepath.Join(testDir, srcDir, "dead.link")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Remove(doesNotExists); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{filepath.Join(srcDir, "dead.link"), "dest/"},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "dest" dir exists with link dead.link
|
||||||
|
files, err := ioutil.ReadDir(filepath.Join(testDir, "dest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, 1, len(files))
|
||||||
|
testutil.CheckDeepEqual(t, files[0].Name(), "dead.link")
|
||||||
|
testutil.CheckDeepEqual(t, true, files[0].Mode()&os.ModeSymlink != 0)
|
||||||
|
linkName, err := os.Readlink(filepath.Join(testDir, "dest", "dead.link"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, linkName, "../dead.txt")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("copy src symlink dir to a dir", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
expected, err := ioutil.ReadDir(filepath.Join(testDir, srcDir))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
another := filepath.Join(testDir, "another")
|
||||||
|
os.Symlink(filepath.Join(testDir, srcDir), another)
|
||||||
|
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{"another", "dest"},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "dest" dir exists with contents of srcDir
|
||||||
|
actual, err := ioutil.ReadDir(filepath.Join(testDir, "dest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i, f := range actual {
|
||||||
|
testutil.CheckDeepEqual(t, expected[i].Name(), f.Name())
|
||||||
|
testutil.CheckDeepEqual(t, expected[i].Mode(), f.Mode())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("copy dir with a symlink to a file outside of current src dir", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
expected, err := ioutil.ReadDir(filepath.Join(testDir, srcDir))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
anotherSrc := filepath.Join(testDir, "anotherSrc")
|
||||||
|
if err := os.MkdirAll(anotherSrc, 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
targetPath := filepath.Join(anotherSrc, "target.txt")
|
||||||
|
if err := ioutil.WriteFile(targetPath, []byte("woof"), 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Symlink(targetPath, filepath.Join(testDir, srcDir, "zSym.link")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{srcDir, "dest"},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "dest" dir exists contents of srcDir and an extra zSym.link created
|
||||||
|
// in this test
|
||||||
|
actual, err := ioutil.ReadDir(filepath.Join(testDir, "dest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, 4, len(actual))
|
||||||
|
for i, f := range expected {
|
||||||
|
testutil.CheckDeepEqual(t, f.Name(), actual[i].Name())
|
||||||
|
testutil.CheckDeepEqual(t, f.Mode(), actual[i].Mode())
|
||||||
|
}
|
||||||
|
linkName, err := os.Readlink(filepath.Join(testDir, "dest", "zSym.link"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, linkName, targetPath)
|
||||||
|
})
|
||||||
|
t.Run("copy src symlink dir to a dir", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
expected, err := ioutil.ReadDir(filepath.Join(testDir, srcDir))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
another := filepath.Join(testDir, "another")
|
||||||
|
os.Symlink(filepath.Join(testDir, srcDir), another)
|
||||||
|
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{"another", "dest"},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "dest" dir exists with bam.txt and "dest" dir is a symlink
|
||||||
|
actual, err := ioutil.ReadDir(filepath.Join(testDir, "dest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i, f := range actual {
|
||||||
|
testutil.CheckDeepEqual(t, expected[i].Name(), f.Name())
|
||||||
|
testutil.CheckDeepEqual(t, expected[i].Mode(), f.Mode())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("copy src dir to a dest dir which is a symlink", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
expected, err := ioutil.ReadDir(filepath.Join(testDir, srcDir))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := filepath.Join(testDir, "dest")
|
||||||
|
if err := os.MkdirAll(dest, 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
linkedDest := filepath.Join(testDir, "linkDest")
|
||||||
|
if err := os.Symlink(dest, linkedDest); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{srcDir, linkedDest},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "linkdest" dir exists with contents of srcDir
|
||||||
|
actual, err := ioutil.ReadDir(filepath.Join(testDir, "linkDest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i, f := range expected {
|
||||||
|
testutil.CheckDeepEqual(t, f.Name(), actual[i].Name())
|
||||||
|
testutil.CheckDeepEqual(t, f.Mode(), actual[i].Mode())
|
||||||
|
}
|
||||||
|
// Check if linkDest -> dest
|
||||||
|
linkName, err := os.Readlink(filepath.Join(testDir, "linkDest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, linkName, dest)
|
||||||
|
})
|
||||||
|
t.Run("copy src file to a dest dir which is a symlink", func(t *testing.T) {
|
||||||
|
testDir, srcDir := setupDirs(t)
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
dest := filepath.Join(testDir, "dest")
|
||||||
|
if err := os.MkdirAll(dest, 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
linkedDest := filepath.Join(testDir, "linkDest")
|
||||||
|
if err := os.Symlink(dest, linkedDest); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := CopyCommand{
|
||||||
|
cmd: &instructions.CopyCommand{
|
||||||
|
SourcesAndDest: []string{fmt.Sprintf("%s/bam.txt", srcDir), linkedDest},
|
||||||
|
},
|
||||||
|
buildcontext: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &v1.Config{
|
||||||
|
Cmd: nil,
|
||||||
|
Env: []string{},
|
||||||
|
WorkingDir: testDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||||
|
testutil.CheckNoError(t, err)
|
||||||
|
// Check if "linkDest" link is same.
|
||||||
|
actual, err := ioutil.ReadDir(filepath.Join(testDir, "dest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, "bam.txt", actual[0].Name())
|
||||||
|
c, err := ioutil.ReadFile(filepath.Join(testDir, "dest", "bam.txt"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, "meow", string(c))
|
||||||
|
// Check if linkDest -> dest
|
||||||
|
linkName, err := os.Readlink(filepath.Join(testDir, "linkDest"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testutil.CheckDeepEqual(t, linkName, dest)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -339,7 +339,7 @@ func (s *stageBuilder) build() error {
|
||||||
v := command.(commands.Cached)
|
v := command.(commands.Cached)
|
||||||
layer := v.Layer()
|
layer := v.Layer()
|
||||||
if err := s.saveLayerToImage(layer, command.String()); err != nil {
|
if err := s.saveLayerToImage(layer, command.String()); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failed to save layer")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tarPath, err := s.takeSnapshot(files)
|
tarPath, err := s.takeSnapshot(files)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import (
|
||||||
// * Add all ancestors of each path to the output set.
|
// * Add all ancestors of each path to the output set.
|
||||||
func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string, err error) {
|
func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string, err error) {
|
||||||
logrus.Info("Resolving paths")
|
logrus.Info("Resolving paths")
|
||||||
|
logrus.Debugf("Resolving paths %s", paths)
|
||||||
|
|
||||||
fileSet := make(map[string]bool)
|
fileSet := make(map[string]bool)
|
||||||
|
|
||||||
|
|
@ -47,8 +48,7 @@ func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string
|
||||||
|
|
||||||
link, e := resolveSymlinkAncestor(f)
|
link, e := resolveSymlinkAncestor(f)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
err = e
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if f != link {
|
if f != link {
|
||||||
|
|
@ -64,9 +64,9 @@ func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string
|
||||||
|
|
||||||
// If the path is a symlink we need to also consider the target of that
|
// If the path is a symlink we need to also consider the target of that
|
||||||
// link
|
// link
|
||||||
evaled, err = filepath.EvalSymlinks(f)
|
evaled, e = filepath.EvalSymlinks(f)
|
||||||
if err != nil {
|
if e != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(e) {
|
||||||
logrus.Errorf("couldn't eval %s with link %s", f, link)
|
logrus.Errorf("couldn't eval %s with link %s", f, link)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -133,7 +133,7 @@ loop:
|
||||||
for newPath != "/" {
|
for newPath != "/" {
|
||||||
fi, err := os.Lstat(newPath)
|
fi, err := os.Lstat(newPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to lstat")
|
return "", errors.Wrap(err, "resolvePaths: failed to lstat")
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.IsSymlink(fi) {
|
if util.IsSymlink(fi) {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import (
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/filesystem"
|
"github.com/GoogleContainerTools/kaniko/pkg/filesystem"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
||||||
|
|
||||||
"github.com/karrick/godirwalk"
|
"github.com/karrick/godirwalk"
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
|
@ -35,6 +35,10 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pathSeparator = "/"
|
||||||
|
)
|
||||||
|
|
||||||
// ResolveEnvironmentReplacementList resolves a list of values by calling resolveEnvironmentReplacement
|
// ResolveEnvironmentReplacementList resolves a list of values by calling resolveEnvironmentReplacement
|
||||||
func ResolveEnvironmentReplacementList(values, envs []string, isFilepath bool) ([]string, error) {
|
func ResolveEnvironmentReplacementList(values, envs []string, isFilepath bool) ([]string, error) {
|
||||||
var resolvedValues []string
|
var resolvedValues []string
|
||||||
|
|
@ -114,13 +118,14 @@ func ResolveSources(srcs []string, root string) ([]string, error) {
|
||||||
logrus.Infof("Resolving srcs %v...", srcs)
|
logrus.Infof("Resolving srcs %v...", srcs)
|
||||||
files, err := RelativeFiles("", root)
|
files, err := RelativeFiles("", root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errors.Wrap(err, "resolving sources")
|
||||||
}
|
}
|
||||||
resolved, err := matchSources(srcs, files)
|
resolved, err := matchSources(srcs, files)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errors.Wrap(err, "matching sources")
|
||||||
}
|
}
|
||||||
logrus.Debugf("Resolved sources to %v", resolved)
|
logrus.Debugf("Resolved sources to %v", resolved)
|
||||||
|
fmt.Println("end of resolve sources")
|
||||||
return resolved, nil
|
return resolved, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,7 +159,7 @@ func IsDestDir(path string) bool {
|
||||||
fileInfo, err := os.Stat(path)
|
fileInfo, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// fall back to string-based determination
|
// fall back to string-based determination
|
||||||
return strings.HasSuffix(path, "/") || path == "."
|
return strings.HasSuffix(path, pathSeparator) || path == "."
|
||||||
}
|
}
|
||||||
// if it's a real path, check the fs response
|
// if it's a real path, check the fs response
|
||||||
return fileInfo.IsDir()
|
return fileInfo.IsDir()
|
||||||
|
|
@ -171,16 +176,19 @@ func DestinationFilepath(src, dest, cwd string) (string, error) {
|
||||||
_, srcFileName := filepath.Split(src)
|
_, srcFileName := filepath.Split(src)
|
||||||
newDest := dest
|
newDest := dest
|
||||||
|
|
||||||
|
if !filepath.IsAbs(newDest) {
|
||||||
|
newDest = filepath.Join(cwd, newDest)
|
||||||
|
// join call clean on all results.
|
||||||
|
if strings.HasSuffix(dest, pathSeparator) || strings.HasSuffix(dest, ".") {
|
||||||
|
newDest += pathSeparator
|
||||||
|
}
|
||||||
|
}
|
||||||
if IsDestDir(newDest) {
|
if IsDestDir(newDest) {
|
||||||
newDest = filepath.Join(newDest, srcFileName)
|
newDest = filepath.Join(newDest, srcFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filepath.IsAbs(newDest) {
|
if len(srcFileName) <= 0 && !strings.HasSuffix(newDest, pathSeparator) {
|
||||||
newDest = filepath.Join(cwd, newDest)
|
newDest += pathSeparator
|
||||||
}
|
|
||||||
|
|
||||||
if len(srcFileName) <= 0 && !strings.HasSuffix(newDest, "/") {
|
|
||||||
newDest += "/"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newDest, nil
|
return newDest, nil
|
||||||
|
|
|
||||||
|
|
@ -442,7 +442,7 @@ func DetectFilesystemWhitelist(path string) error {
|
||||||
func RelativeFiles(fp string, root string) ([]string, error) {
|
func RelativeFiles(fp string, root string) ([]string, error) {
|
||||||
var files []string
|
var files []string
|
||||||
fullPath := filepath.Join(root, fp)
|
fullPath := filepath.Join(root, fp)
|
||||||
logrus.Debugf("Getting files and contents at root %s", fullPath)
|
logrus.Debugf("Getting files and contents at root %s for %s", root, fullPath)
|
||||||
err := filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -505,15 +505,16 @@ func FilepathExists(path string) bool {
|
||||||
func CreateFile(path string, reader io.Reader, perm os.FileMode, uid uint32, gid uint32) error {
|
func CreateFile(path string, reader io.Reader, perm os.FileMode, uid uint32, gid uint32) error {
|
||||||
// Create directory path if it doesn't exist
|
// Create directory path if it doesn't exist
|
||||||
if err := createParentDirectory(path); err != nil {
|
if err := createParentDirectory(path); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "creating parent dir")
|
||||||
}
|
}
|
||||||
|
|
||||||
dest, err := os.Create(path)
|
dest, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "creating file")
|
||||||
}
|
}
|
||||||
defer dest.Close()
|
defer dest.Close()
|
||||||
if _, err := io.Copy(dest, reader); err != nil {
|
if _, err := io.Copy(dest, reader); err != nil {
|
||||||
return err
|
return errors.Wrap(err, "copying file")
|
||||||
}
|
}
|
||||||
return setFilePermissions(path, perm, int(uid), int(gid))
|
return setFilePermissions(path, perm, int(uid), int(gid))
|
||||||
}
|
}
|
||||||
|
|
@ -570,7 +571,7 @@ func DetermineTargetFileOwnership(fi os.FileInfo, uid, gid int64) (int64, int64)
|
||||||
func CopyDir(src, dest, buildcontext string, uid, gid int64) ([]string, error) {
|
func CopyDir(src, dest, buildcontext string, uid, gid int64) ([]string, error) {
|
||||||
files, err := RelativeFiles("", src)
|
files, err := RelativeFiles("", src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errors.Wrap(err, "copying dir")
|
||||||
}
|
}
|
||||||
var copiedFiles []string
|
var copiedFiles []string
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
|
@ -594,7 +595,7 @@ func CopyDir(src, dest, buildcontext string, uid, gid int64) ([]string, error) {
|
||||||
}
|
}
|
||||||
} else if IsSymlink(fi) {
|
} else if IsSymlink(fi) {
|
||||||
// If file is a symlink, we want to create the same relative symlink
|
// If file is a symlink, we want to create the same relative symlink
|
||||||
if _, err := CopySymlink(fullPath, destPath, buildcontext, uid, gid); err != nil {
|
if _, err := CopySymlink(fullPath, destPath, buildcontext); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -608,16 +609,12 @@ func CopyDir(src, dest, buildcontext string, uid, gid int64) ([]string, error) {
|
||||||
return copiedFiles, nil
|
return copiedFiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopySymlink copies the symlink at src to dest
|
// CopySymlink copies the symlink at src to dest.
|
||||||
func CopySymlink(src, dest, buildcontext string, uid int64, gid int64) (bool, error) {
|
func CopySymlink(src, dest, buildcontext string) (bool, error) {
|
||||||
if ExcludeFile(src, buildcontext) {
|
if ExcludeFile(src, buildcontext) {
|
||||||
logrus.Debugf("%s found in .dockerignore, ignoring", src)
|
logrus.Debugf("%s found in .dockerignore, ignoring", src)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
link, err := filepath.EvalSymlinks(src)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if FilepathExists(dest) {
|
if FilepathExists(dest) {
|
||||||
if err := os.RemoveAll(dest); err != nil {
|
if err := os.RemoveAll(dest); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
@ -626,7 +623,11 @@ func CopySymlink(src, dest, buildcontext string, uid int64, gid int64) (bool, er
|
||||||
if err := createParentDirectory(dest); err != nil {
|
if err := createParentDirectory(dest); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return CopyFile(link, dest, buildcontext, uid, gid)
|
link, err := os.Readlink(src)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("could not read link for %s", src)
|
||||||
|
}
|
||||||
|
return false, os.Symlink(link, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyFile copies the file at src to dest
|
// CopyFile copies the file at src to dest
|
||||||
|
|
@ -827,15 +828,14 @@ func getSymlink(path string) error {
|
||||||
func CopyFileOrSymlink(src string, destDir string) error {
|
func CopyFileOrSymlink(src string, destDir string) error {
|
||||||
destFile := filepath.Join(destDir, src)
|
destFile := filepath.Join(destDir, src)
|
||||||
if fi, _ := os.Lstat(src); IsSymlink(fi) {
|
if fi, _ := os.Lstat(src); IsSymlink(fi) {
|
||||||
link, err := EvalSymLink(src)
|
link, err := os.Readlink(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
linkPath := filepath.Join(destDir, link)
|
|
||||||
if err := createParentDirectory(destFile); err != nil {
|
if err := createParentDirectory(destFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.Symlink(linkPath, destFile)
|
return os.Symlink(link, destFile)
|
||||||
}
|
}
|
||||||
return otiai10Cpy.Copy(src, destFile)
|
return otiai10Cpy.Copy(src, destFile)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -835,7 +835,7 @@ func TestCopySymlink(t *testing.T) {
|
||||||
if err := os.Symlink(tc.linkTarget, link); err != nil {
|
if err := os.Symlink(tc.linkTarget, link); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err := CopySymlink(link, dest, "", DoNotChangeUID, DoNotChangeGID); err != nil {
|
if _, err := CopySymlink(link, dest, ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err := os.Lstat(dest); err != nil {
|
if _, err := os.Lstat(dest); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,12 @@ func CheckError(t *testing.T, shouldErr bool, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckNoError(t *testing.T, err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkErr(shouldErr bool, err error) error {
|
func checkErr(shouldErr bool, err error) error {
|
||||||
if err == nil && shouldErr {
|
if err == nil && shouldErr {
|
||||||
return fmt.Errorf("Expected error, but returned none")
|
return fmt.Errorf("Expected error, but returned none")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue