Environment replacement
This commit is contained in:
commit
dbb0774778
|
|
@ -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,20 @@
|
||||||
|
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/
|
||||||
|
COPY context/bar/bat .
|
||||||
|
|
||||||
|
ENV contextenv ./context
|
||||||
|
COPY ${contextenv}/foo /tmp/foo2
|
||||||
|
COPY $contextenv/foo /tmp/foo3
|
||||||
|
COPY $contextenv/* /tmp/${contextenv}/
|
||||||
|
|
@ -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,102 @@
|
||||||
|
/*
|
||||||
|
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)
|
||||||
|
|
||||||
|
// First, resolve any environment replacement
|
||||||
|
resolvedEnvs, err := util.ResolveEnvironmentReplacementList(c.copyToString(), c.cmd.SourcesAndDest, config.Env, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dest = resolvedEnvs[len(c.cmd.SourcesAndDest)-1]
|
||||||
|
// Get a map of [src]:[files rooted at src]
|
||||||
|
srcMap, err := util.ResolveSources(resolvedEnvs, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CopyCommand) copyToString() string {
|
||||||
|
copy := []string{"COPY"}
|
||||||
|
return strings.Join(append(copy, c.cmd.SourcesAndDest...), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, " ")
|
||||||
|
}
|
||||||
|
|
@ -35,11 +35,11 @@ func (e *EnvCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||||
envString := envToString(e.cmd)
|
envString := envToString(e.cmd)
|
||||||
newEnvs := e.cmd.Env
|
newEnvs := e.cmd.Env
|
||||||
for index, pair := range newEnvs {
|
for index, pair := range newEnvs {
|
||||||
expandedKey, err := util.ResolveEnvironmentReplacement(envString, pair.Key, config.Env)
|
expandedKey, err := util.ResolveEnvironmentReplacement(envString, pair.Key, config.Env, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
expandedValue, err := util.ResolveEnvironmentReplacement(envString, pair.Value, config.Env)
|
expandedValue, err := util.ResolveEnvironmentReplacement(envString, pair.Value, config.Env, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func (r *ExposeCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||||
// Add any new ones in
|
// Add any new ones in
|
||||||
for _, p := range r.cmd.Ports {
|
for _, p := range r.cmd.Ports {
|
||||||
// Resolve any environment variables
|
// Resolve any environment variables
|
||||||
p, err := util.ResolveEnvironmentReplacement(exposeString, p, config.Env)
|
p, err := util.ResolveEnvironmentReplacement(exposeString, p, config.Env, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,31 @@ package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||||
"github.com/docker/docker/builder/dockerfile/parser"
|
"github.com/docker/docker/builder/dockerfile/parser"
|
||||||
"github.com/docker/docker/builder/dockerfile/shell"
|
"github.com/docker/docker/builder/dockerfile/shell"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResolveEnvironmentReplacement resolves replacing env variables in some text from envs
|
// ResolveEnvironmentReplacement resolves a list of values by calling resolveEnvironmentReplacement
|
||||||
|
func ResolveEnvironmentReplacementList(command string, values, envs []string, isFilepath bool) ([]string, error) {
|
||||||
|
var resolvedValues []string
|
||||||
|
for _, value := range values {
|
||||||
|
resolved, err := ResolveEnvironmentReplacement(command, value, envs, isFilepath)
|
||||||
|
logrus.Infof("Resolved %s to %s", value, resolved)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resolvedValues = append(resolvedValues, resolved)
|
||||||
|
}
|
||||||
|
return resolvedValues, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveEnvironmentReplacement resolves replacing env variables in some text from envs
|
||||||
// It takes in a string representation of the command, the value to be resolved, and a list of envs (config.Env)
|
// It takes in a string representation of the command, the value to be resolved, and a list of envs (config.Env)
|
||||||
// Ex: fp = $foo/newdir, envs = [foo=/foodir], then this should return /foodir/newdir
|
// Ex: fp = $foo/newdir, envs = [foo=/foodir], then this should return /foodir/newdir
|
||||||
// The dockerfile/shell package handles processing env values
|
// The dockerfile/shell package handles processing env values
|
||||||
|
|
@ -32,30 +51,159 @@ import (
|
||||||
// ""a'b'c"" -> "a'b'c"
|
// ""a'b'c"" -> "a'b'c"
|
||||||
// "Rex\ The\ Dog \" -> "Rex The Dog"
|
// "Rex\ The\ Dog \" -> "Rex The Dog"
|
||||||
// "a\"b" -> "a"b"
|
// "a\"b" -> "a"b"
|
||||||
func ResolveEnvironmentReplacement(command, value string, envs []string) (string, error) {
|
func ResolveEnvironmentReplacement(command, value string, envs []string, isFilepath bool) (string, error) {
|
||||||
p, err := parser.Parse(bytes.NewReader([]byte(command)))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
shlex := shell.NewLex(p.EscapeToken)
|
|
||||||
return shlex.ProcessWord(value, envs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveFilepathEnvironmentReplacement replaces env variables in filepaths
|
|
||||||
// and returns a cleaned version of the path
|
|
||||||
func ResolveFilepathEnvironmentReplacement(command, value string, envs []string) (string, error) {
|
|
||||||
p, err := parser.Parse(bytes.NewReader([]byte(command)))
|
p, err := parser.Parse(bytes.NewReader([]byte(command)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
shlex := shell.NewLex(p.EscapeToken)
|
shlex := shell.NewLex(p.EscapeToken)
|
||||||
fp, err := shlex.ProcessWord(value, envs)
|
fp, err := shlex.ProcessWord(value, envs)
|
||||||
|
if !isFilepath {
|
||||||
|
return fp, err
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fp = filepath.Clean(fp)
|
fp = filepath.Clean(fp)
|
||||||
if filepath.IsAbs(value) {
|
if IsDestDir(value) {
|
||||||
fp = filepath.Join(fp, "/")
|
fp = fp + "/"
|
||||||
}
|
}
|
||||||
return fp, nil
|
return fp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/GoogleCloudPlatform/k8s-container-builder/testutil"
|
"github.com/GoogleCloudPlatform/k8s-container-builder/testutil"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,6 +26,7 @@ var testEnvReplacement = []struct {
|
||||||
path string
|
path string
|
||||||
command string
|
command string
|
||||||
envs []string
|
envs []string
|
||||||
|
isFilepath bool
|
||||||
expectedPath string
|
expectedPath string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
@ -33,8 +35,18 @@ var testEnvReplacement = []struct {
|
||||||
envs: []string{
|
envs: []string{
|
||||||
"simple=/path/",
|
"simple=/path/",
|
||||||
},
|
},
|
||||||
|
isFilepath: true,
|
||||||
expectedPath: "/simple/path",
|
expectedPath: "/simple/path",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/simple/path/",
|
||||||
|
command: "WORKDIR /simple/path/",
|
||||||
|
envs: []string{
|
||||||
|
"simple=/path/",
|
||||||
|
},
|
||||||
|
isFilepath: true,
|
||||||
|
expectedPath: "/simple/path/",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "${a}/b",
|
path: "${a}/b",
|
||||||
command: "WORKDIR ${a}/b",
|
command: "WORKDIR ${a}/b",
|
||||||
|
|
@ -42,6 +54,7 @@ var testEnvReplacement = []struct {
|
||||||
"a=/path/",
|
"a=/path/",
|
||||||
"b=/path2/",
|
"b=/path2/",
|
||||||
},
|
},
|
||||||
|
isFilepath: true,
|
||||||
expectedPath: "/path/b",
|
expectedPath: "/path/b",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -51,14 +64,26 @@ var testEnvReplacement = []struct {
|
||||||
"a=/path/",
|
"a=/path/",
|
||||||
"b=/path2/",
|
"b=/path2/",
|
||||||
},
|
},
|
||||||
|
isFilepath: true,
|
||||||
expectedPath: "/path/b",
|
expectedPath: "/path/b",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/$a/b/",
|
||||||
|
command: "COPY /${a}/b /c/",
|
||||||
|
envs: []string{
|
||||||
|
"a=/path/",
|
||||||
|
"b=/path2/",
|
||||||
|
},
|
||||||
|
isFilepath: true,
|
||||||
|
expectedPath: "/path/b/",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "\\$foo",
|
path: "\\$foo",
|
||||||
command: "COPY \\$foo /quux",
|
command: "COPY \\$foo /quux",
|
||||||
envs: []string{
|
envs: []string{
|
||||||
"foo=/path/",
|
"foo=/path/",
|
||||||
},
|
},
|
||||||
|
isFilepath: true,
|
||||||
expectedPath: "$foo",
|
expectedPath: "$foo",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -73,7 +98,308 @@ var testEnvReplacement = []struct {
|
||||||
|
|
||||||
func Test_EnvReplacement(t *testing.T) {
|
func Test_EnvReplacement(t *testing.T) {
|
||||||
for _, test := range testEnvReplacement {
|
for _, test := range testEnvReplacement {
|
||||||
actualPath, err := ResolveEnvironmentReplacement(test.command, test.path, test.envs)
|
actualPath, err := ResolveEnvironmentReplacement(test.command, test.path, test.envs, test.isFilepath)
|
||||||
testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedPath, actualPath)
|
testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedPath, actualPath)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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