commit
						08251159cd
					
				|  | @ -98,7 +98,7 @@ func execute() error { | ||||||
| 	// Currently only supports single stage builds
 | 	// Currently only supports single stage builds
 | ||||||
| 	for _, stage := range stages { | 	for _, stage := range stages { | ||||||
| 		for _, cmd := range stage.Commands { | 		for _, cmd := range stage.Commands { | ||||||
| 			dockerCommand, err := commands.GetCommand(cmd) | 			dockerCommand, err := commands.GetCommand(cmd, srcContext) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | hello | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | bat | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | bat | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | baz | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | foo | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | 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/ | ||||||
|  | COPY context/foo context/bar/ba? /test/ | ||||||
|  | COPY context/arr[[]0].txt /mydir/ | ||||||
|  | @ -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 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | ] | ||||||
|  | @ -49,6 +49,13 @@ var fileTests = []struct { | ||||||
| 		context:        "integration_tests/dockerfiles/", | 		context:        "integration_tests/dockerfiles/", | ||||||
| 		repo:           "test-run-2", | 		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", | ||||||
|  | 	}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var structureTests = []struct { | var structureTests = []struct { | ||||||
|  | @ -126,7 +133,7 @@ func main() { | ||||||
| 		kbuildImage := testRepo + kbuildPrefix + test.repo | 		kbuildImage := testRepo + kbuildPrefix + test.repo | ||||||
| 		kbuild := step{ | 		kbuild := step{ | ||||||
| 			Name: executorImage, | 			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
 | 		// Pull the kbuild image
 | ||||||
|  |  | ||||||
|  | @ -34,10 +34,12 @@ type DockerCommand interface { | ||||||
| 	FilesToSnapshot() []string | 	FilesToSnapshot() []string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func GetCommand(cmd instructions.Command) (DockerCommand, error) { | func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, error) { | ||||||
| 	switch c := cmd.(type) { | 	switch c := cmd.(type) { | ||||||
| 	case *instructions.RunCommand: | 	case *instructions.RunCommand: | ||||||
| 		return &RunCommand{cmd: c}, nil | 		return &RunCommand{cmd: c}, nil | ||||||
|  | 	case *instructions.CopyCommand: | ||||||
|  | 		return &CopyCommand{cmd: c, buildcontext: buildcontext}, nil | ||||||
| 	case *instructions.ExposeCommand: | 	case *instructions.ExposeCommand: | ||||||
| 		return &ExposeCommand{cmd: c}, nil | 		return &ExposeCommand{cmd: c}, nil | ||||||
| 	case *instructions.EnvCommand: | 	case *instructions.EnvCommand: | ||||||
|  |  | ||||||
|  | @ -0,0 +1,91 @@ | ||||||
|  | /* | ||||||
|  | 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" | ||||||
|  | 	"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) | ||||||
|  | 	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.DestinationFilepath(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) | ||||||
|  | 				srcFile, err := os.Open(filepath.Join(c.buildcontext, file)) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				defer srcFile.Close() | ||||||
|  | 				if err := util.CreateFile(destPath, srcFile, 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 { | ||||||
|  | 	return c.snapshotFiles | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreatedBy returns some information about the command for the image config
 | ||||||
|  | func (c *CopyCommand) CreatedBy() string { | ||||||
|  | 	return strings.Join(c.cmd.SourcesAndDest, " ") | ||||||
|  | } | ||||||
|  | @ -0,0 +1,163 @@ | ||||||
|  | /* | ||||||
|  | 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" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ContainsWildcards returns true if any entry in paths contains wildcards
 | ||||||
|  | func ContainsWildcards(paths []string) bool { | ||||||
|  | 	for _, path := range paths { | ||||||
|  | 		if strings.ContainsAny(path, "*?[") { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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 string) (map[string][]string, error) { | ||||||
|  | 	srcs := srcsAndDest[:len(srcsAndDest)-1] | ||||||
|  | 	// If sources contain wildcards, we first need to resolve them to actual paths
 | ||||||
|  | 	if ContainsWildcards(srcs) { | ||||||
|  | 		logrus.Debugf("Resolving srcs %v...", srcs) | ||||||
|  | 		files, err := RelativeFiles("", root) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		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) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	// Check to make sure the sources are valid
 | ||||||
|  | 	return srcMap, IsSrcsValid(srcsAndDest, srcMap) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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) | ||||||
|  | 		for _, file := range files { | ||||||
|  | 			matched, err := filepath.Match(src, file) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			if matched { | ||||||
|  | 				matchedSources = append(matchedSources, file) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return matchedSources, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func IsDestDir(path string) bool { | ||||||
|  | 	return strings.HasSuffix(path, "/") || path == "." | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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 DestinationFilepath(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(dest, relPath) | ||||||
|  | 		if filepath.IsAbs(dest) { | ||||||
|  | 			return destPath, nil | ||||||
|  | 		} | ||||||
|  | 		return filepath.Join(cwd, destPath), nil | ||||||
|  | 	} | ||||||
|  | 	if filepath.IsAbs(dest) { | ||||||
|  | 		return dest, 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 := RelativeFiles(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] | ||||||
|  | 
 | ||||||
|  | 	totalFiles := 0 | ||||||
|  | 	for _, files := range srcMap { | ||||||
|  | 		totalFiles += len(files) | ||||||
|  | 	} | ||||||
|  | 	if totalFiles == 0 { | ||||||
|  | 		return errors.New("copy failed: no source files specified") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,323 @@ | ||||||
|  | /* | ||||||
|  | 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 destinationFilepathTests = []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: "/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", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		srcName:          "context/foo", | ||||||
|  | 		filename:         "context/foo", | ||||||
|  | 		cwd:              "/test", | ||||||
|  | 		dest:             ".", | ||||||
|  | 		expectedFilepath: "/test/foo", | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var matchSourcesTests = []struct { | ||||||
|  | 	srcs          []string | ||||||
|  | 	files         []string | ||||||
|  | 	expectedFiles []string | ||||||
|  | }{ | ||||||
|  | 	{ | ||||||
|  | 		srcs: []string{ | ||||||
|  | 			"pkg/*", | ||||||
|  | 		}, | ||||||
|  | 		files: []string{ | ||||||
|  | 			"pkg/a", | ||||||
|  | 			"pkg/b", | ||||||
|  | 			"/pkg/d", | ||||||
|  | 			"pkg/b/d/", | ||||||
|  | 			"dir/", | ||||||
|  | 		}, | ||||||
|  | 		expectedFiles: []string{ | ||||||
|  | 			"pkg/a", | ||||||
|  | 			"pkg/b", | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Test_MatchSources(t *testing.T) { | ||||||
|  | 	for _, test := range matchSourcesTests { | ||||||
|  | 		actualFiles, err := matchSources(test.srcs, test.files) | ||||||
|  | 		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: map[string][]string{ | ||||||
|  | 			"src1": { | ||||||
|  | 				"file1", | ||||||
|  | 			}, | ||||||
|  | 			"src2:": { | ||||||
|  | 				"file2", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		shouldErr: true, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		srcsAndDest: []string{ | ||||||
|  | 			"src1", | ||||||
|  | 			"src2", | ||||||
|  | 			"dest/", | ||||||
|  | 		}, | ||||||
|  | 		files: map[string][]string{ | ||||||
|  | 			"src1": { | ||||||
|  | 				"file1", | ||||||
|  | 			}, | ||||||
|  | 			"src2:": { | ||||||
|  | 				"file2", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		shouldErr: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		srcsAndDest: []string{ | ||||||
|  | 			"src2/", | ||||||
|  | 			"dest", | ||||||
|  | 		}, | ||||||
|  | 		files: map[string][]string{ | ||||||
|  | 			"src1": { | ||||||
|  | 				"file1", | ||||||
|  | 			}, | ||||||
|  | 			"src2:": { | ||||||
|  | 				"file2", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		shouldErr: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		srcsAndDest: []string{ | ||||||
|  | 			"src2", | ||||||
|  | 			"dest", | ||||||
|  | 		}, | ||||||
|  | 		files: map[string][]string{ | ||||||
|  | 			"src1": { | ||||||
|  | 				"file1", | ||||||
|  | 			}, | ||||||
|  | 			"src2:": { | ||||||
|  | 				"file2", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		shouldErr: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		srcsAndDest: []string{ | ||||||
|  | 			"src2", | ||||||
|  | 			"src*", | ||||||
|  | 			"dest/", | ||||||
|  | 		}, | ||||||
|  | 		files: map[string][]string{ | ||||||
|  | 			"src1": { | ||||||
|  | 				"file1", | ||||||
|  | 			}, | ||||||
|  | 			"src2:": { | ||||||
|  | 				"file2", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		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 | ||||||
|  | 	expectedMap map[string][]string | ||||||
|  | }{ | ||||||
|  | 	{ | ||||||
|  | 		srcsAndDest: []string{ | ||||||
|  | 			"context/foo", | ||||||
|  | 			"context/b*", | ||||||
|  | 			"dest/", | ||||||
|  | 		}, | ||||||
|  | 		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) | ||||||
|  | 		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedMap, actualMap) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -97,3 +97,48 @@ func fileSystemWhitelist(path string) ([]string, error) { | ||||||
| 	} | 	} | ||||||
| 	return whitelist, nil | 	return whitelist, nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // 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) | ||||||
|  | 	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 nil | ||||||
|  | 	}) | ||||||
|  | 	return files, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FilepathExists returns true if the path exists
 | ||||||
|  | func FilepathExists(path string) bool { | ||||||
|  | 	_, err := os.Stat(path) | ||||||
|  | 	return !os.IsNotExist(err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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) { | ||||||
|  | 		logrus.Debugf("baseDir %s for file %s does not exist. Creating.", baseDir, path) | ||||||
|  | 		if err := os.MkdirAll(baseDir, 0755); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	dest, err := os.Create(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if _, err := io.Copy(dest, reader); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return dest.Chmod(perm) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -51,3 +51,82 @@ func Test_fileSystemWhitelist(t *testing.T) { | ||||||
| 	sort.Strings(expectedWhitelist) | 	sort.Strings(expectedWhitelist) | ||||||
| 	testutil.CheckErrorAndDeepEqual(t, false, err, expectedWhitelist, actualWhitelist) | 	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_RelativeFiles(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 := RelativeFiles(test.directory, testDir) | ||||||
|  | 		sort.Strings(actualFiles) | ||||||
|  | 		sort.Strings(test.expectedFiles) | ||||||
|  | 		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -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 { | 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