Copy command and unit tests
This commit is contained in:
parent
c2a69c0e24
commit
21a9207428
|
|
@ -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 @@
|
||||||
|
bat
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
bat
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
baz
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
foo
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
return nil, errors.Errorf("%s is not a supported command", cmd.Name())
|
return nil, errors.Errorf("%s is not a supported command", cmd.Name())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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, " ")
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -97,3 +97,48 @@ func fileSystemWhitelist(path string) ([]string, error) {
|
||||||
}
|
}
|
||||||
return whitelist, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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_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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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