From 21a92074289fbe5dc2dc09180a515e329b536a9d Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Wed, 14 Mar 2018 17:06:46 -0700 Subject: [PATCH 1/7] Copy command and unit tests --- executor/cmd/root.go | 2 +- integration_tests/context/bar/bam/bat | 1 + integration_tests/context/bar/bat | 1 + integration_tests/context/bar/baz | 1 + integration_tests/context/foo | 1 + pkg/commands/commands.go | 4 +- pkg/commands/copy.go | 94 +++++++++ pkg/util/command_util.go | 165 +++++++++++++++ pkg/util/command_util_test.go | 286 ++++++++++++++++++++++++++ pkg/util/fs_util.go | 45 ++++ pkg/util/fs_util_test.go | 79 +++++++ testutil/util.go | 6 + 12 files changed, 683 insertions(+), 2 deletions(-) create mode 100644 integration_tests/context/bar/bam/bat create mode 100644 integration_tests/context/bar/bat create mode 100644 integration_tests/context/bar/baz create mode 100644 integration_tests/context/foo create mode 100644 pkg/commands/copy.go create mode 100644 pkg/util/command_util.go create mode 100644 pkg/util/command_util_test.go diff --git a/executor/cmd/root.go b/executor/cmd/root.go index a34a5f3cd..80b2c088f 100644 --- a/executor/cmd/root.go +++ b/executor/cmd/root.go @@ -98,7 +98,7 @@ func execute() error { // Currently only supports single stage builds for _, stage := range stages { for _, cmd := range stage.Commands { - dockerCommand, err := commands.GetCommand(cmd) + dockerCommand, err := commands.GetCommand(cmd, srcContext) if err != nil { return err } diff --git a/integration_tests/context/bar/bam/bat b/integration_tests/context/bar/bam/bat new file mode 100644 index 000000000..1054901d8 --- /dev/null +++ b/integration_tests/context/bar/bam/bat @@ -0,0 +1 @@ +bat diff --git a/integration_tests/context/bar/bat b/integration_tests/context/bar/bat new file mode 100644 index 000000000..1054901d8 --- /dev/null +++ b/integration_tests/context/bar/bat @@ -0,0 +1 @@ +bat diff --git a/integration_tests/context/bar/baz b/integration_tests/context/bar/baz new file mode 100644 index 000000000..76018072e --- /dev/null +++ b/integration_tests/context/bar/baz @@ -0,0 +1 @@ +baz diff --git a/integration_tests/context/foo b/integration_tests/context/foo new file mode 100644 index 000000000..257cc5642 --- /dev/null +++ b/integration_tests/context/foo @@ -0,0 +1 @@ +foo diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index eb2cab5bc..1837f803f 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -34,10 +34,12 @@ type DockerCommand interface { FilesToSnapshot() []string } -func GetCommand(cmd instructions.Command) (DockerCommand, error) { +func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, error) { switch c := cmd.(type) { case *instructions.RunCommand: return &RunCommand{cmd: c}, nil + case *instructions.CopyCommand: + return &CopyCommand{cmd: c, buildcontext: buildcontext}, nil } return nil, errors.Errorf("%s is not a supported command", cmd.Name()) } diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go new file mode 100644 index 000000000..9b0ffaf56 --- /dev/null +++ b/pkg/commands/copy.go @@ -0,0 +1,94 @@ +/* +Copyright 2018 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package commands + +import ( + "github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util" + "github.com/containers/image/manifest" + "github.com/docker/docker/builder/dockerfile/instructions" + "github.com/sirupsen/logrus" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +type CopyCommand struct { + cmd *instructions.CopyCommand + buildcontext string + snapshotFiles []string +} + +func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error { + srcs := c.cmd.SourcesAndDest[:len(c.cmd.SourcesAndDest)-1] + dest := c.cmd.SourcesAndDest[len(c.cmd.SourcesAndDest)-1] + + logrus.Infof("cmd: copy %s", srcs) + logrus.Infof("dest: %s", dest) + + // Get a map of [src]:[files rooted at src] + srcMap, err := util.ResolveSources(c.cmd.SourcesAndDest, c.buildcontext, config.WorkingDir) + if err != nil { + return err + } + // For each source, iterate through each file within and copy it over + for src, files := range srcMap { + for _, file := range files { + fi, err := os.Stat(filepath.Join(c.buildcontext, file)) + if err != nil { + return err + } + destPath, err := util.RelativeFilepath(file, src, dest, config.WorkingDir, c.buildcontext) + if err != nil { + return err + } + // If source file is a directory, we want to create a directory ... + if fi.IsDir() { + logrus.Infof("Creating directory %s", destPath) + if err := os.MkdirAll(destPath, fi.Mode()); err != nil { + return err + } + } else { + // ... Else, we want to copy over a file + logrus.Infof("Copying file %s to %s", file, destPath) + contents, err := ioutil.ReadFile(filepath.Join(c.buildcontext, file)) + if err != nil { + return err + } + if err := util.CreateFile(destPath, contents, fi.Mode()); err != nil { + return err + } + } + // Append the destination file to the list of files that should be snapshotted later + c.snapshotFiles = append(c.snapshotFiles, destPath) + } + } + return nil +} + +// FilesToSnapshot should return an empty array if still nil; no files were changed +func (c *CopyCommand) FilesToSnapshot() []string { + if c.snapshotFiles == nil { + return []string{} + } + return c.snapshotFiles +} + +// Author returns some information about the command for the image config +func (c *CopyCommand) CreatedBy() string { + return strings.Join(c.cmd.SourcesAndDest, " ") +} diff --git a/pkg/util/command_util.go b/pkg/util/command_util.go new file mode 100644 index 000000000..86321c5bd --- /dev/null +++ b/pkg/util/command_util.go @@ -0,0 +1,165 @@ +/* +Copyright 2018 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "github.com/docker/docker/builder/dockerfile/instructions" + "github.com/pkg/errors" + "os" + "path/filepath" + "strings" +) + +// ContainsWildcards returns true if any entry in paths contains wildcards +func ContainsWildcards(paths []string) bool { + for _, path := range paths { + for i := 0; i < len(path); i++ { + ch := path[i] + // These are the wildcards that correspond to filepath.Match + if ch == '*' || ch == '?' || ch == '[' { + return true + } + } + } + return false +} + +// ResolveSources resolves the given sources if the sources contains wildcard +// It returns a map of [src]:[files rooted at src] +func ResolveSources(srcsAndDest instructions.SourcesAndDest, root, cwd string) (map[string][]string, error) { + srcs := srcsAndDest[:len(srcsAndDest)-1] + // If sources contain wildcards, we first need to resolve them to actual paths + wildcard := ContainsWildcards(srcs) + if wildcard { + files, err := Files("", root) + if err != nil { + return nil, err + } + srcs, err = matchSources(srcs, files, cwd) + if err != nil { + return nil, err + } + } + // Now, get a map of [src]:[files rooted at src] + srcMap, err := SourcesToFilesMap(srcs, root) + if err != nil { + return nil, err + } + // Check to make sure the sources are valid + return srcMap, IsSrcsValid(srcsAndDest, srcMap) +} + +// matchSources returns a map of [src]:[matching filepaths], used to resolve wildcards +func matchSources(srcs, files []string, cwd string) ([]string, error) { + var matchedSources []string + for _, src := range srcs { + src = filepath.Clean(src) + for _, file := range files { + matched, err := filepath.Match(src, file) + if err != nil { + return nil, err + } + // Check cwd + matchedRoot, err := filepath.Match(filepath.Join(cwd, src), file) + if err != nil { + return nil, err + } + if !(matched || matchedRoot) { + continue + } + matchedSources = append(matchedSources, file) + } + } + return matchedSources, nil +} + +func IsDestDir(path string) bool { + return strings.HasSuffix(path, "/") +} + +// RelativeFilepath returns the relative filepath +// If source is a file: +// If dest is a dir, copy it to /cwd/dest/relpath +// If dest is a file, copy directly to /cwd/dest + +// If source is a dir: +// Assume dest is also a dir, and copy to /cwd/dest/relpath +func RelativeFilepath(filename, srcName, dest, cwd, buildcontext string) (string, error) { + fi, err := os.Stat(filepath.Join(buildcontext, filename)) + if err != nil { + return "", err + } + src, err := os.Stat(filepath.Join(buildcontext, srcName)) + if err != nil { + return "", err + } + if src.IsDir() || IsDestDir(dest) { + relPath, err := filepath.Rel(srcName, filename) + if err != nil { + return "", err + } + if relPath == "." && !fi.IsDir() { + relPath = filepath.Base(filename) + } + destPath := filepath.Join(cwd, dest, relPath) + return destPath, nil + } + return filepath.Join(cwd, dest), nil +} + +// SourcesToFilesMap returns a map of [src]:[files rooted at source] +func SourcesToFilesMap(srcs []string, root string) (map[string][]string, error) { + srcMap := make(map[string][]string) + for _, src := range srcs { + src = filepath.Clean(src) + files, err := Files(src, root) + if err != nil { + return nil, err + } + srcMap[src] = files + } + return srcMap, nil +} + +// IsSrcsValid returns an error if the sources provided are invalid, or nil otherwise +func IsSrcsValid(srcsAndDest instructions.SourcesAndDest, srcMap map[string][]string) error { + srcs := srcsAndDest[:len(srcsAndDest)-1] + dest := srcsAndDest[len(srcsAndDest)-1] + // If destination is a directory, return nil + if IsDestDir(dest) { + return nil + } + // If no wildcards and multiple sources, return error + if !ContainsWildcards(srcs) { + if len(srcs) > 1 { + return errors.New("when specifying multiple sources in a COPY command, destination must be a directory and end in '/'") + } + return nil + } + // If there are wildcards, and the destination is a file, there must be exactly one file to copy over + totalFiles := 0 + for _, files := range srcMap { + totalFiles += len(files) + } + if totalFiles == 0 { + return errors.New("copy failed: no source files specified") + } + if totalFiles > 1 { + return errors.New("when specifying multiple sources in a COPY command, destination must be a directory and end in '/'") + } + return nil +} diff --git a/pkg/util/command_util_test.go b/pkg/util/command_util_test.go new file mode 100644 index 000000000..ed89d182f --- /dev/null +++ b/pkg/util/command_util_test.go @@ -0,0 +1,286 @@ +/* +Copyright 2018 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "github.com/GoogleCloudPlatform/k8s-container-builder/testutil" + "sort" + "testing" +) + +var buildContextPath = "../../integration_tests/" + +var relativeFilepathTests = []struct { + srcName string + filename string + dest string + cwd string + buildcontext string + expectedFilepath string +}{ + { + srcName: "context/foo", + filename: "context/foo", + dest: "/foo", + cwd: "/", + expectedFilepath: "/foo", + }, + { + srcName: "context/foo", + filename: "context/foo", + dest: "/foodir/", + cwd: "/", + expectedFilepath: "/foodir/foo", + }, + { + srcName: "context/foo", + filename: "./context/foo", + cwd: "/", + dest: "foo", + expectedFilepath: "/foo", + }, + { + srcName: "context/bar/", + filename: "context/bar/bam/bat", + cwd: "/", + dest: "pkg/", + expectedFilepath: "/pkg/bam/bat", + }, + { + srcName: "context/bar/", + filename: "context/bar/bam/bat", + cwd: "/newdir", + dest: "pkg/", + expectedFilepath: "/newdir/pkg/bam/bat", + }, + { + srcName: "./context/empty", + filename: "context/empty", + cwd: "/", + dest: "/empty", + expectedFilepath: "/empty", + }, + { + srcName: "./context/empty", + filename: "context/empty", + cwd: "/dir", + dest: "/empty", + expectedFilepath: "/dir/empty", + }, + { + srcName: "./", + filename: "./", + cwd: "/", + dest: "/dir", + expectedFilepath: "/dir", + }, + { + srcName: "./", + filename: "context/foo", + cwd: "/", + dest: "/dir", + expectedFilepath: "/dir/context/foo", + }, + { + srcName: ".", + filename: "context/bar", + cwd: "/", + dest: "/dir", + expectedFilepath: "/dir/context/bar", + }, + { + srcName: ".", + filename: "context/bar", + cwd: "/", + dest: "/dir", + expectedFilepath: "/dir/context/bar", + }, +} + +func Test_RelativeFilepath(t *testing.T) { + for _, test := range relativeFilepathTests { + actualFilepath, err := RelativeFilepath(test.filename, test.srcName, test.dest, test.cwd, buildContextPath) + testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFilepath, actualFilepath) + } +} + +var matchSourcesTests = []struct { + srcs []string + files []string + cwd string + expectedFiles []string +}{ + { + srcs: []string{ + "pkg/*", + }, + files: []string{ + "pkg/a", + "pkg/b", + "/pkg/d", + "pkg/b/d/", + "dir/", + }, + cwd: "/", + expectedFiles: []string{ + "pkg/a", + "pkg/b", + "/pkg/d", + }, + }, +} + +func Test_MatchSources(t *testing.T) { + for _, test := range matchSourcesTests { + actualFiles, err := matchSources(test.srcs, test.files, test.cwd) + sort.Strings(actualFiles) + sort.Strings(test.expectedFiles) + testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles) + } +} + +var isSrcValidTests = []struct { + srcsAndDest []string + files map[string][]string + shouldErr bool +}{ + { + srcsAndDest: []string{ + "src1", + "src2", + "dest", + }, + files: nil, + shouldErr: true, + }, + { + srcsAndDest: []string{ + "src1", + "src2", + "dest/", + }, + files: nil, + shouldErr: false, + }, + { + srcsAndDest: []string{ + "src2/", + "dest", + }, + files: nil, + shouldErr: false, + }, + { + srcsAndDest: []string{ + "src2", + "dest", + }, + files: nil, + shouldErr: false, + }, + { + srcsAndDest: []string{ + "src2", + "src*", + "dest/", + }, + files: nil, + shouldErr: false, + }, + { + srcsAndDest: []string{ + "src2", + "src*", + "dest", + }, + files: map[string][]string{ + "src2": { + "src2/a", + "src2/b", + }, + "src*": {}, + }, + shouldErr: true, + }, + { + srcsAndDest: []string{ + "src2", + "src*", + "dest", + }, + files: map[string][]string{ + "src2": { + "src2/a", + }, + "src*": {}, + }, + shouldErr: false, + }, + { + srcsAndDest: []string{ + "src2", + "src*", + "dest", + }, + files: map[string][]string{ + "src2": {}, + "src*": {}, + }, + shouldErr: true, + }, +} + +func Test_IsSrcsValid(t *testing.T) { + for _, test := range isSrcValidTests { + err := IsSrcsValid(test.srcsAndDest, test.files) + testutil.CheckError(t, test.shouldErr, err) + } +} + +var testResolveSources = []struct { + srcsAndDest []string + cwd string + expectedMap map[string][]string +}{ + { + srcsAndDest: []string{ + "context/foo", + "context/b*", + "dest/", + }, + cwd: "/", + expectedMap: map[string][]string{ + "context/foo": { + "context/foo", + }, + "context/bar": { + "context/bar", + "context/bar/bam", + "context/bar/bam/bat", + "context/bar/bat", + "context/bar/baz", + }, + }, + }, +} + +func Test_ResolveSources(t *testing.T) { + for _, test := range testResolveSources { + actualMap, err := ResolveSources(test.srcsAndDest, buildContextPath, test.cwd) + testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedMap, actualMap) + } +} diff --git a/pkg/util/fs_util.go b/pkg/util/fs_util.go index eafd18916..3b56c6cce 100644 --- a/pkg/util/fs_util.go +++ b/pkg/util/fs_util.go @@ -97,3 +97,48 @@ func fileSystemWhitelist(path string) ([]string, error) { } return whitelist, nil } + +// Files returns a list of all files at the filepath relative to root +func Files(fp string, root string) ([]string, error) { + var files []string + fullPath := filepath.Join(root, fp) + logrus.Debugf("Getting files and contents at root %s", fullPath) + err := filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + relPath, err := filepath.Rel(root, path) + if err != nil { + return err + } + files = append(files, relPath) + return err + }) + return files, err +} + +// FilepathExists returns true if the path exists +func FilepathExists(path string) bool { + _, err := os.Stat(path) + return (err == nil) +} + +// CreateFile creates a file at path with contents specified +func CreateFile(path string, contents []byte, perm os.FileMode) error { + // Create directory path if it doesn't exist + baseDir := filepath.Dir(path) + if _, err := os.Stat(baseDir); os.IsNotExist(err) { + logrus.Debugf("baseDir %s for file %s does not exist. Creating.", baseDir, path) + if err := os.MkdirAll(baseDir, perm); err != nil { + return err + } + } + + f, err := os.Create(path) + defer f.Close() + if err != nil { + return err + } + _, err = f.Write(contents) + return err +} diff --git a/pkg/util/fs_util_test.go b/pkg/util/fs_util_test.go index ccb7b3658..f21c6f788 100644 --- a/pkg/util/fs_util_test.go +++ b/pkg/util/fs_util_test.go @@ -51,3 +51,82 @@ func Test_fileSystemWhitelist(t *testing.T) { sort.Strings(expectedWhitelist) testutil.CheckErrorAndDeepEqual(t, false, err, expectedWhitelist, actualWhitelist) } + +var tests = []struct { + files map[string]string + directory string + expectedFiles []string +}{ + { + files: map[string]string{ + "/workspace/foo/a": "baz1", + "/workspace/foo/b": "baz2", + "/kbuild/file": "file", + }, + directory: "/workspace/foo/", + expectedFiles: []string{ + "workspace/foo/a", + "workspace/foo/b", + "workspace/foo", + }, + }, + { + files: map[string]string{ + "/workspace/foo/a": "baz1", + }, + directory: "/workspace/foo/a", + expectedFiles: []string{ + "workspace/foo/a", + }, + }, + { + files: map[string]string{ + "/workspace/foo/a": "baz1", + "/workspace/foo/b": "baz2", + "/workspace/baz": "hey", + "/kbuild/file": "file", + }, + directory: "/workspace", + expectedFiles: []string{ + "workspace/foo/a", + "workspace/foo/b", + "workspace/baz", + "workspace", + "workspace/foo", + }, + }, + { + files: map[string]string{ + "/workspace/foo/a": "baz1", + "/workspace/foo/b": "baz2", + "/kbuild/file": "file", + }, + directory: "", + expectedFiles: []string{ + "workspace/foo/a", + "workspace/foo/b", + "kbuild/file", + "workspace", + "workspace/foo", + "kbuild", + ".", + }, + }, +} + +func Test_Files(t *testing.T) { + for _, test := range tests { + testDir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("err setting up temp dir: %v", err) + } + defer os.RemoveAll(testDir) + if err := testutil.SetupFiles(testDir, test.files); err != nil { + t.Fatalf("err setting up files: %v", err) + } + actualFiles, err := Files(test.directory, testDir) + sort.Strings(actualFiles) + sort.Strings(test.expectedFiles) + testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles) + } +} diff --git a/testutil/util.go b/testutil/util.go index 45607a772..64819c3ef 100644 --- a/testutil/util.go +++ b/testutil/util.go @@ -50,6 +50,12 @@ func CheckErrorAndDeepEqual(t *testing.T, shouldErr bool, err error, expected, a } } +func CheckError(t *testing.T, shouldErr bool, err error) { + if err := checkErr(shouldErr, err); err != nil { + t.Error(err) + } +} + func checkErr(shouldErr bool, err error) error { if err == nil && shouldErr { return fmt.Errorf("Expected error, but returned none") From 140d49d5064fb1fa2aaa46cae7c687842f196dfc Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Wed, 14 Mar 2018 17:07:07 -0700 Subject: [PATCH 2/7] Integration test copy command --- integration_tests/dockerfiles/Dockerfile_test_copy | 12 ++++++++++++ integration_tests/dockerfiles/config_test_copy.json | 12 ++++++++++++ integration_tests/dockerfiles/config_test_run.json | 12 ++++++------ integration_tests/integration_test_yaml.go | 9 ++++++++- 4 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 integration_tests/dockerfiles/Dockerfile_test_copy create mode 100644 integration_tests/dockerfiles/config_test_copy.json diff --git a/integration_tests/dockerfiles/Dockerfile_test_copy b/integration_tests/dockerfiles/Dockerfile_test_copy new file mode 100644 index 000000000..517f655a2 --- /dev/null +++ b/integration_tests/dockerfiles/Dockerfile_test_copy @@ -0,0 +1,12 @@ +FROM gcr.io/distroless/base +COPY context/foo foo +COPY context/foo /foodir/ +COPY context/bar/b* bar/ +COPY context/fo? /foo2 +COPY context/bar/doesnotexist* context/foo hello +COPY ./context/empty /empty +COPY ./ dir/ +COPY . newdir +COPY context/bar /baz/ +COPY ["context/foo", "/tmp/foo" ] +COPY context/b* /baz/ diff --git a/integration_tests/dockerfiles/config_test_copy.json b/integration_tests/dockerfiles/config_test_copy.json new file mode 100644 index 000000000..6d222de51 --- /dev/null +++ b/integration_tests/dockerfiles/config_test_copy.json @@ -0,0 +1,12 @@ +[ + { + "Image1": "gcr.io/kbuild-test/docker-test-copy:latest", + "Image2": "gcr.io/kbuild-test/kbuild-test-copy:latest", + "DiffType": "File", + "Diff": { + "Adds": null, + "Dels": null, + "Mods": null + } + } +] \ No newline at end of file diff --git a/integration_tests/dockerfiles/config_test_run.json b/integration_tests/dockerfiles/config_test_run.json index 0544a3d22..19cab3219 100644 --- a/integration_tests/dockerfiles/config_test_run.json +++ b/integration_tests/dockerfiles/config_test_run.json @@ -9,13 +9,13 @@ "Mods": [ { "Name": "/var/log/dpkg.log", - "Size1": 57425, - "Size2": 57425 + "Size1": 57481, + "Size2": 57481 }, { "Name": "/var/log/apt/term.log", - "Size1": 24400, - "Size2": 24400 + "Size1": 24421, + "Size2": 24421 }, { "Name": "/var/cache/ldconfig/aux-cache", @@ -24,8 +24,8 @@ }, { "Name": "/var/log/apt/history.log", - "Size1": 5089, - "Size2": 5089 + "Size1": 5415, + "Size2": 5415 }, { "Name": "/var/log/alternatives.log", diff --git a/integration_tests/integration_test_yaml.go b/integration_tests/integration_test_yaml.go index d13be52e9..2928d307a 100644 --- a/integration_tests/integration_test_yaml.go +++ b/integration_tests/integration_test_yaml.go @@ -49,6 +49,13 @@ var tests = []struct { context: "integration_tests/dockerfiles/", repo: "test-run-2", }, + { + description: "test copy", + dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_copy", + configPath: "/workspace/integration_tests/dockerfiles/config_test_copy.json", + context: "/workspace/integration_tests/", + repo: "test-copy", + }, } type step struct { @@ -102,7 +109,7 @@ func main() { kbuildImage := testRepo + kbuildPrefix + test.repo kbuild := step{ Name: executorImage, - Args: []string{executorCommand, "--destination", kbuildImage, "--dockerfile", test.dockerfilePath}, + Args: []string{executorCommand, "--destination", kbuildImage, "--dockerfile", test.dockerfilePath, "--context", test.context}, } // Pull the kbuild image From 5ebf156d94afc6b156bb38513e4e3043bf3e6b17 Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Thu, 15 Mar 2018 11:11:39 -0700 Subject: [PATCH 3/7] Fixed relative filepath and unit test --- integration_tests/context/arr[0].txt | 1 + integration_tests/context/empty/.gitignore | 0 .../dockerfiles/Dockerfile_test_copy | 2 + pkg/commands/copy.go | 4 +- pkg/util/command_util.go | 54 ++++++++++--------- pkg/util/command_util_test.go | 11 ++-- pkg/util/fs_util.go | 6 +-- pkg/util/fs_util_test.go | 4 +- 8 files changed, 43 insertions(+), 39 deletions(-) create mode 100644 integration_tests/context/arr[0].txt create mode 100644 integration_tests/context/empty/.gitignore diff --git a/integration_tests/context/arr[0].txt b/integration_tests/context/arr[0].txt new file mode 100644 index 000000000..b6fc4c620 --- /dev/null +++ b/integration_tests/context/arr[0].txt @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/integration_tests/context/empty/.gitignore b/integration_tests/context/empty/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/integration_tests/dockerfiles/Dockerfile_test_copy b/integration_tests/dockerfiles/Dockerfile_test_copy index 517f655a2..af4dc77d3 100644 --- a/integration_tests/dockerfiles/Dockerfile_test_copy +++ b/integration_tests/dockerfiles/Dockerfile_test_copy @@ -10,3 +10,5 @@ COPY . newdir COPY context/bar /baz/ COPY ["context/foo", "/tmp/foo" ] COPY context/b* /baz/ +COPY context/foo context/bar/ba? /test/ +COPY context/arr[[]0].txt /mydir/ diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go index 9b0ffaf56..afacf57eb 100644 --- a/pkg/commands/copy.go +++ b/pkg/commands/copy.go @@ -41,7 +41,7 @@ func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error { logrus.Infof("dest: %s", dest) // Get a map of [src]:[files rooted at src] - srcMap, err := util.ResolveSources(c.cmd.SourcesAndDest, c.buildcontext, config.WorkingDir) + srcMap, err := util.ResolveSources(c.cmd.SourcesAndDest, c.buildcontext) if err != nil { return err } @@ -88,7 +88,7 @@ func (c *CopyCommand) FilesToSnapshot() []string { return c.snapshotFiles } -// Author returns some information about the command for the image config +// CreatedBy returns some information about the command for the image config func (c *CopyCommand) CreatedBy() string { return strings.Join(c.cmd.SourcesAndDest, " ") } diff --git a/pkg/util/command_util.go b/pkg/util/command_util.go index 86321c5bd..7d34fca94 100644 --- a/pkg/util/command_util.go +++ b/pkg/util/command_util.go @@ -19,6 +19,7 @@ package util import ( "github.com/docker/docker/builder/dockerfile/instructions" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "os" "path/filepath" "strings" @@ -38,21 +39,22 @@ func ContainsWildcards(paths []string) bool { return false } -// ResolveSources resolves the given sources if the sources contains wildcard +// ResolveSources resolves the given sources if the sources contains wildcards // It returns a map of [src]:[files rooted at src] -func ResolveSources(srcsAndDest instructions.SourcesAndDest, root, cwd string) (map[string][]string, error) { +func ResolveSources(srcsAndDest instructions.SourcesAndDest, root string) (map[string][]string, error) { srcs := srcsAndDest[:len(srcsAndDest)-1] // If sources contain wildcards, we first need to resolve them to actual paths - wildcard := ContainsWildcards(srcs) - if wildcard { - files, err := Files("", root) + if ContainsWildcards(srcs) { + logrus.Debugf("Resolving srcs %v...", srcs) + files, err := RelativeFiles("", root) if err != nil { return nil, err } - srcs, err = matchSources(srcs, files, cwd) + srcs, err = matchSources(srcs, files) if err != nil { return nil, err } + logrus.Debugf("Resolved sources to %v", srcs) } // Now, get a map of [src]:[files rooted at src] srcMap, err := SourcesToFilesMap(srcs, root) @@ -63,8 +65,8 @@ func ResolveSources(srcsAndDest instructions.SourcesAndDest, root, cwd string) ( return srcMap, IsSrcsValid(srcsAndDest, srcMap) } -// matchSources returns a map of [src]:[matching filepaths], used to resolve wildcards -func matchSources(srcs, files []string, cwd string) ([]string, error) { +// matchSources returns a list of sources that match wildcards +func matchSources(srcs, files []string) ([]string, error) { var matchedSources []string for _, src := range srcs { src = filepath.Clean(src) @@ -73,15 +75,9 @@ func matchSources(srcs, files []string, cwd string) ([]string, error) { if err != nil { return nil, err } - // Check cwd - matchedRoot, err := filepath.Match(filepath.Join(cwd, src), file) - if err != nil { - return nil, err + if matched { + matchedSources = append(matchedSources, file) } - if !(matched || matchedRoot) { - continue - } - matchedSources = append(matchedSources, file) } } return matchedSources, nil @@ -91,13 +87,17 @@ func IsDestDir(path string) bool { return strings.HasSuffix(path, "/") } -// RelativeFilepath returns the relative filepath -// If source is a file: -// If dest is a dir, copy it to /cwd/dest/relpath -// If dest is a file, copy directly to /cwd/dest +func IsAbsoluteFilepath(path string) bool { + return strings.HasPrefix(path, "/") +} +// RelativeFilepath returns the relative filepath from the build context to the image filesystem +// If source is a file: +// If dest is a dir, copy it to /dest/relpath +// If dest is a file, copy directly to dest // If source is a dir: -// Assume dest is also a dir, and copy to /cwd/dest/relpath +// Assume dest is also a dir, and copy to dest/relpath +// If dest is not an absolute filepath, add /cwd to the beginning func RelativeFilepath(filename, srcName, dest, cwd, buildcontext string) (string, error) { fi, err := os.Stat(filepath.Join(buildcontext, filename)) if err != nil { @@ -115,8 +115,14 @@ func RelativeFilepath(filename, srcName, dest, cwd, buildcontext string) (string if relPath == "." && !fi.IsDir() { relPath = filepath.Base(filename) } - destPath := filepath.Join(cwd, dest, relPath) - return destPath, nil + destPath := filepath.Join(dest, relPath) + if IsAbsoluteFilepath(dest) { + return destPath, nil + } + return filepath.Join(cwd, destPath), nil + } + if IsAbsoluteFilepath(dest) { + return dest, nil } return filepath.Join(cwd, dest), nil } @@ -126,7 +132,7 @@ func SourcesToFilesMap(srcs []string, root string) (map[string][]string, error) srcMap := make(map[string][]string) for _, src := range srcs { src = filepath.Clean(src) - files, err := Files(src, root) + files, err := RelativeFiles(src, root) if err != nil { return nil, err } diff --git a/pkg/util/command_util_test.go b/pkg/util/command_util_test.go index ed89d182f..4585ef8c9 100644 --- a/pkg/util/command_util_test.go +++ b/pkg/util/command_util_test.go @@ -79,7 +79,7 @@ var relativeFilepathTests = []struct { filename: "context/empty", cwd: "/dir", dest: "/empty", - expectedFilepath: "/dir/empty", + expectedFilepath: "/empty", }, { srcName: "./", @@ -121,7 +121,6 @@ func Test_RelativeFilepath(t *testing.T) { var matchSourcesTests = []struct { srcs []string files []string - cwd string expectedFiles []string }{ { @@ -135,18 +134,16 @@ var matchSourcesTests = []struct { "pkg/b/d/", "dir/", }, - cwd: "/", expectedFiles: []string{ "pkg/a", "pkg/b", - "/pkg/d", }, }, } func Test_MatchSources(t *testing.T) { for _, test := range matchSourcesTests { - actualFiles, err := matchSources(test.srcs, test.files, test.cwd) + actualFiles, err := matchSources(test.srcs, test.files) sort.Strings(actualFiles) sort.Strings(test.expectedFiles) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles) @@ -253,7 +250,6 @@ func Test_IsSrcsValid(t *testing.T) { var testResolveSources = []struct { srcsAndDest []string - cwd string expectedMap map[string][]string }{ { @@ -262,7 +258,6 @@ var testResolveSources = []struct { "context/b*", "dest/", }, - cwd: "/", expectedMap: map[string][]string{ "context/foo": { "context/foo", @@ -280,7 +275,7 @@ var testResolveSources = []struct { func Test_ResolveSources(t *testing.T) { for _, test := range testResolveSources { - actualMap, err := ResolveSources(test.srcsAndDest, buildContextPath, test.cwd) + actualMap, err := ResolveSources(test.srcsAndDest, buildContextPath) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedMap, actualMap) } } diff --git a/pkg/util/fs_util.go b/pkg/util/fs_util.go index 3b56c6cce..995e7c003 100644 --- a/pkg/util/fs_util.go +++ b/pkg/util/fs_util.go @@ -98,8 +98,8 @@ func fileSystemWhitelist(path string) ([]string, error) { return whitelist, nil } -// Files returns a list of all files at the filepath relative to root -func Files(fp string, root string) ([]string, error) { +// RelativeFiles returns a list of all files at the filepath relative to root +func RelativeFiles(fp string, root string) ([]string, error) { var files []string fullPath := filepath.Join(root, fp) logrus.Debugf("Getting files and contents at root %s", fullPath) @@ -112,7 +112,7 @@ func Files(fp string, root string) ([]string, error) { return err } files = append(files, relPath) - return err + return nil }) return files, err } diff --git a/pkg/util/fs_util_test.go b/pkg/util/fs_util_test.go index f21c6f788..c8ebc88cf 100644 --- a/pkg/util/fs_util_test.go +++ b/pkg/util/fs_util_test.go @@ -114,7 +114,7 @@ var tests = []struct { }, } -func Test_Files(t *testing.T) { +func Test_RelativeFiles(t *testing.T) { for _, test := range tests { testDir, err := ioutil.TempDir("", "") if err != nil { @@ -124,7 +124,7 @@ func Test_Files(t *testing.T) { if err := testutil.SetupFiles(testDir, test.files); err != nil { t.Fatalf("err setting up files: %v", err) } - actualFiles, err := Files(test.directory, testDir) + actualFiles, err := RelativeFiles(test.directory, testDir) sort.Strings(actualFiles) sort.Strings(test.expectedFiles) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles) From de8cc1a28585390cba410f0c5c4090f4a07e7f56 Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Fri, 16 Mar 2018 14:11:06 -0700 Subject: [PATCH 4/7] Fixed check srcs function and create file with correct permissions --- pkg/commands/copy.go | 2 +- pkg/util/command_util.go | 46 +++++++++++++----------------- pkg/util/command_util_test.go | 53 +++++++++++++++++++++++++++++------ pkg/util/fs_util.go | 7 +++-- 4 files changed, 69 insertions(+), 39 deletions(-) diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go index afacf57eb..cc6a21721 100644 --- a/pkg/commands/copy.go +++ b/pkg/commands/copy.go @@ -52,7 +52,7 @@ func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error { if err != nil { return err } - destPath, err := util.RelativeFilepath(file, src, dest, config.WorkingDir, c.buildcontext) + destPath, err := util.DestinationFilepath(file, src, dest, config.WorkingDir, c.buildcontext) if err != nil { return err } diff --git a/pkg/util/command_util.go b/pkg/util/command_util.go index 7d34fca94..4e5da9626 100644 --- a/pkg/util/command_util.go +++ b/pkg/util/command_util.go @@ -28,12 +28,8 @@ import ( // ContainsWildcards returns true if any entry in paths contains wildcards func ContainsWildcards(paths []string) bool { for _, path := range paths { - for i := 0; i < len(path); i++ { - ch := path[i] - // These are the wildcards that correspond to filepath.Match - if ch == '*' || ch == '?' || ch == '[' { - return true - } + if strings.ContainsAny(path, "*?[") { + return true } } return false @@ -87,18 +83,14 @@ func IsDestDir(path string) bool { return strings.HasSuffix(path, "/") } -func IsAbsoluteFilepath(path string) bool { - return strings.HasPrefix(path, "/") -} - -// RelativeFilepath returns the relative filepath from the build context to the image filesystem +// DestinationFilepath returns the destination filepath from the build context to the image filesystem // If source is a file: // If dest is a dir, copy it to /dest/relpath // If dest is a file, copy directly to dest // If source is a dir: // Assume dest is also a dir, and copy to dest/relpath // If dest is not an absolute filepath, add /cwd to the beginning -func RelativeFilepath(filename, srcName, dest, cwd, buildcontext string) (string, error) { +func DestinationFilepath(filename, srcName, dest, cwd, buildcontext string) (string, error) { fi, err := os.Stat(filepath.Join(buildcontext, filename)) if err != nil { return "", err @@ -116,12 +108,12 @@ func RelativeFilepath(filename, srcName, dest, cwd, buildcontext string) (string relPath = filepath.Base(filename) } destPath := filepath.Join(dest, relPath) - if IsAbsoluteFilepath(dest) { + if filepath.IsAbs(dest) { return destPath, nil } return filepath.Join(cwd, destPath), nil } - if IsAbsoluteFilepath(dest) { + if filepath.IsAbs(dest) { return dest, nil } return filepath.Join(cwd, dest), nil @@ -145,18 +137,7 @@ func SourcesToFilesMap(srcs []string, root string) (map[string][]string, error) func IsSrcsValid(srcsAndDest instructions.SourcesAndDest, srcMap map[string][]string) error { srcs := srcsAndDest[:len(srcsAndDest)-1] dest := srcsAndDest[len(srcsAndDest)-1] - // If destination is a directory, return nil - if IsDestDir(dest) { - return nil - } - // If no wildcards and multiple sources, return error - if !ContainsWildcards(srcs) { - if len(srcs) > 1 { - return errors.New("when specifying multiple sources in a COPY command, destination must be a directory and end in '/'") - } - return nil - } - // If there are wildcards, and the destination is a file, there must be exactly one file to copy over + totalFiles := 0 for _, files := range srcMap { totalFiles += len(files) @@ -164,7 +145,18 @@ func IsSrcsValid(srcsAndDest instructions.SourcesAndDest, srcMap map[string][]st if totalFiles == 0 { return errors.New("copy failed: no source files specified") } - if totalFiles > 1 { + + if !ContainsWildcards(srcs) { + // If multiple sources and destination isn't a directory, return an error + if len(srcs) > 1 && !IsDestDir(dest) { + return errors.New("when specifying multiple sources in a COPY command, destination must be a directory and end in '/'") + } + return nil + } + + // If there are wildcards, and the destination is a file, there must be exactly one file to copy over, + // Otherwise, return an error + if !IsDestDir(dest) && totalFiles > 1 { return errors.New("when specifying multiple sources in a COPY command, destination must be a directory and end in '/'") } return nil diff --git a/pkg/util/command_util_test.go b/pkg/util/command_util_test.go index 4585ef8c9..7680dbcfd 100644 --- a/pkg/util/command_util_test.go +++ b/pkg/util/command_util_test.go @@ -24,7 +24,7 @@ import ( var buildContextPath = "../../integration_tests/" -var relativeFilepathTests = []struct { +var destinationFilepathTests = []struct { srcName string filename string dest string @@ -111,9 +111,9 @@ var relativeFilepathTests = []struct { }, } -func Test_RelativeFilepath(t *testing.T) { - for _, test := range relativeFilepathTests { - actualFilepath, err := RelativeFilepath(test.filename, test.srcName, test.dest, test.cwd, buildContextPath) +func Test_DestinationFilepath(t *testing.T) { + for _, test := range destinationFilepathTests { + actualFilepath, err := DestinationFilepath(test.filename, test.srcName, test.dest, test.cwd, buildContextPath) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFilepath, actualFilepath) } } @@ -161,7 +161,14 @@ var isSrcValidTests = []struct { "src2", "dest", }, - files: nil, + files: map[string][]string{ + "src1": { + "file1", + }, + "src2:": { + "file2", + }, + }, shouldErr: true, }, { @@ -170,7 +177,14 @@ var isSrcValidTests = []struct { "src2", "dest/", }, - files: nil, + files: map[string][]string{ + "src1": { + "file1", + }, + "src2:": { + "file2", + }, + }, shouldErr: false, }, { @@ -178,7 +192,14 @@ var isSrcValidTests = []struct { "src2/", "dest", }, - files: nil, + files: map[string][]string{ + "src1": { + "file1", + }, + "src2:": { + "file2", + }, + }, shouldErr: false, }, { @@ -186,7 +207,14 @@ var isSrcValidTests = []struct { "src2", "dest", }, - files: nil, + files: map[string][]string{ + "src1": { + "file1", + }, + "src2:": { + "file2", + }, + }, shouldErr: false, }, { @@ -195,7 +223,14 @@ var isSrcValidTests = []struct { "src*", "dest/", }, - files: nil, + files: map[string][]string{ + "src1": { + "file1", + }, + "src2:": { + "file2", + }, + }, shouldErr: false, }, { diff --git a/pkg/util/fs_util.go b/pkg/util/fs_util.go index 995e7c003..036414f9e 100644 --- a/pkg/util/fs_util.go +++ b/pkg/util/fs_util.go @@ -129,7 +129,7 @@ func CreateFile(path string, contents []byte, perm os.FileMode) error { baseDir := filepath.Dir(path) if _, err := os.Stat(baseDir); os.IsNotExist(err) { logrus.Debugf("baseDir %s for file %s does not exist. Creating.", baseDir, path) - if err := os.MkdirAll(baseDir, perm); err != nil { + if err := os.MkdirAll(baseDir, 0755); err != nil { return err } } @@ -140,5 +140,8 @@ func CreateFile(path string, contents []byte, perm os.FileMode) error { return err } _, err = f.Write(contents) - return err + if err != nil { + return err + } + return f.Chmod(perm) } From f4e9eeb15ab84db1e29cfffd6124a18833ae8e28 Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Mon, 19 Mar 2018 09:46:31 -0700 Subject: [PATCH 5/7] Modified fs util functions --- pkg/commands/copy.go | 3 --- pkg/util/fs_util.go | 15 +++------------ 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go index cc6a21721..cbdba436b 100644 --- a/pkg/commands/copy.go +++ b/pkg/commands/copy.go @@ -82,9 +82,6 @@ func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error { // FilesToSnapshot should return an empty array if still nil; no files were changed func (c *CopyCommand) FilesToSnapshot() []string { - if c.snapshotFiles == nil { - return []string{} - } return c.snapshotFiles } diff --git a/pkg/util/fs_util.go b/pkg/util/fs_util.go index 036414f9e..386ebe19c 100644 --- a/pkg/util/fs_util.go +++ b/pkg/util/fs_util.go @@ -23,6 +23,7 @@ import ( "github.com/containers/image/docker" "github.com/sirupsen/logrus" "io" + "io/ioutil" "os" "path/filepath" "strings" @@ -120,7 +121,7 @@ func RelativeFiles(fp string, root string) ([]string, error) { // FilepathExists returns true if the path exists func FilepathExists(path string) bool { _, err := os.Stat(path) - return (err == nil) + return !os.IsNotExist(err) } // CreateFile creates a file at path with contents specified @@ -133,15 +134,5 @@ func CreateFile(path string, contents []byte, perm os.FileMode) error { return err } } - - f, err := os.Create(path) - defer f.Close() - if err != nil { - return err - } - _, err = f.Write(contents) - if err != nil { - return err - } - return f.Chmod(perm) + return ioutil.WriteFile(path, contents, perm) } From b3ec877b60b2e93f8e5dff3184e19f717b025835 Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Mon, 19 Mar 2018 16:37:12 -0700 Subject: [PATCH 6/7] Use io.Copy when creating files --- integration_tests/context/foo | 0 pkg/commands/copy.go | 6 +++--- pkg/util/fs_util.go | 14 ++++++++++---- 3 files changed, 13 insertions(+), 7 deletions(-) mode change 100644 => 100755 integration_tests/context/foo diff --git a/integration_tests/context/foo b/integration_tests/context/foo old mode 100644 new mode 100755 diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go index cbdba436b..96fded65d 100644 --- a/pkg/commands/copy.go +++ b/pkg/commands/copy.go @@ -21,7 +21,6 @@ import ( "github.com/containers/image/manifest" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/sirupsen/logrus" - "io/ioutil" "os" "path/filepath" "strings" @@ -65,11 +64,12 @@ func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error { } else { // ... Else, we want to copy over a file logrus.Infof("Copying file %s to %s", file, destPath) - contents, err := ioutil.ReadFile(filepath.Join(c.buildcontext, file)) + srcFile, err := os.Open(filepath.Join(c.buildcontext, file)) if err != nil { return err } - if err := util.CreateFile(destPath, contents, fi.Mode()); err != nil { + defer srcFile.Close() + if err := util.CreateFile(destPath, srcFile, fi.Mode()); err != nil { return err } } diff --git a/pkg/util/fs_util.go b/pkg/util/fs_util.go index 386ebe19c..c0ba9008e 100644 --- a/pkg/util/fs_util.go +++ b/pkg/util/fs_util.go @@ -23,7 +23,6 @@ import ( "github.com/containers/image/docker" "github.com/sirupsen/logrus" "io" - "io/ioutil" "os" "path/filepath" "strings" @@ -124,8 +123,8 @@ func FilepathExists(path string) bool { return !os.IsNotExist(err) } -// CreateFile creates a file at path with contents specified -func CreateFile(path string, contents []byte, perm os.FileMode) error { +// CreateFile creates a file at path and copies over contents from the reader +func CreateFile(path string, reader io.Reader, perm os.FileMode) error { // Create directory path if it doesn't exist baseDir := filepath.Dir(path) if _, err := os.Stat(baseDir); os.IsNotExist(err) { @@ -134,5 +133,12 @@ func CreateFile(path string, contents []byte, perm os.FileMode) error { return err } } - return ioutil.WriteFile(path, contents, perm) + dest, err := os.Create(path) + if err != nil { + return err + } + if _, err := io.Copy(dest, reader); err != nil { + return err + } + return dest.Chmod(perm) } From cc0c6726974965eb556c62a60db496095253fb82 Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Tue, 20 Mar 2018 14:10:13 -0700 Subject: [PATCH 7/7] Add support for dest = '.' and additional DestinationFilepath test --- pkg/util/command_util.go | 2 +- pkg/util/command_util_test.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/util/command_util.go b/pkg/util/command_util.go index 4e5da9626..38521231a 100644 --- a/pkg/util/command_util.go +++ b/pkg/util/command_util.go @@ -80,7 +80,7 @@ func matchSources(srcs, files []string) ([]string, error) { } func IsDestDir(path string) bool { - return strings.HasSuffix(path, "/") + return strings.HasSuffix(path, "/") || path == "." } // DestinationFilepath returns the destination filepath from the build context to the image filesystem diff --git a/pkg/util/command_util_test.go b/pkg/util/command_util_test.go index 7680dbcfd..2d96f8d2a 100644 --- a/pkg/util/command_util_test.go +++ b/pkg/util/command_util_test.go @@ -109,6 +109,13 @@ var destinationFilepathTests = []struct { dest: "/dir", expectedFilepath: "/dir/context/bar", }, + { + srcName: "context/foo", + filename: "context/foo", + cwd: "/test", + dest: ".", + expectedFilepath: "/test/foo", + }, } func Test_DestinationFilepath(t *testing.T) {