mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-21 10:53:22 +02:00
Merge branch 'master' into log-timestamp-v0.19.0
This commit is contained in:
@@ -38,25 +38,27 @@ type BuildContext interface {
|
||||
// parser
|
||||
func GetBuildContext(srcContext string) (BuildContext, error) {
|
||||
split := strings.SplitAfter(srcContext, "://")
|
||||
prefix := split[0]
|
||||
context := split[1]
|
||||
if len(split) > 1 {
|
||||
prefix := split[0]
|
||||
context := split[1]
|
||||
|
||||
switch prefix {
|
||||
case constants.GCSBuildContextPrefix:
|
||||
return &GCS{context: context}, nil
|
||||
case constants.S3BuildContextPrefix:
|
||||
return &S3{context: context}, nil
|
||||
case constants.LocalDirBuildContextPrefix:
|
||||
return &Dir{context: context}, nil
|
||||
case constants.GitBuildContextPrefix:
|
||||
return &Git{context: context}, nil
|
||||
case constants.HTTPSBuildContextPrefix:
|
||||
if util.ValidAzureBlobStorageHost(srcContext) {
|
||||
return &AzureBlob{context: srcContext}, nil
|
||||
switch prefix {
|
||||
case constants.GCSBuildContextPrefix:
|
||||
return &GCS{context: context}, nil
|
||||
case constants.S3BuildContextPrefix:
|
||||
return &S3{context: context}, nil
|
||||
case constants.LocalDirBuildContextPrefix:
|
||||
return &Dir{context: context}, nil
|
||||
case constants.GitBuildContextPrefix:
|
||||
return &Git{context: context}, nil
|
||||
case constants.HTTPSBuildContextPrefix:
|
||||
if util.ValidAzureBlobStorageHost(srcContext) {
|
||||
return &AzureBlob{context: srcContext}, nil
|
||||
}
|
||||
return nil, errors.New("url provided for https context is not in a supported format, please use the https url for Azure Blob Storage")
|
||||
case TarBuildContextPrefix:
|
||||
return &Tar{context: context}, nil
|
||||
}
|
||||
return nil, errors.New("url provided for https context is not in a supported format, please use the https url for Azure Blob Storage")
|
||||
case TarBuildContextPrefix:
|
||||
return &Tar{context: context}, nil
|
||||
}
|
||||
return nil, errors.New("unknown build context prefix provided, please use one of the following: gs://, dir://, tar://, s3://, git://, https://")
|
||||
}
|
||||
|
||||
+19
-1
@@ -25,6 +25,16 @@ import (
|
||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||
)
|
||||
|
||||
const (
|
||||
gitPullMethodEnvKey = "GIT_PULL_METHOD"
|
||||
gitPullMethodHTTPS = "https"
|
||||
gitPullMethodHTTP = "http"
|
||||
)
|
||||
|
||||
var (
|
||||
supportedGitPullMethods = map[string]bool{gitPullMethodHTTPS: true, gitPullMethodHTTP: true}
|
||||
)
|
||||
|
||||
// Git unifies calls to download and unpack the build context.
|
||||
type Git struct {
|
||||
context string
|
||||
@@ -35,7 +45,7 @@ func (g *Git) UnpackTarFromBuildContext() (string, error) {
|
||||
directory := constants.BuildContextDir
|
||||
parts := strings.Split(g.context, "#")
|
||||
options := git.CloneOptions{
|
||||
URL: "https://" + parts[0],
|
||||
URL: getGitPullMethod() + "://" + parts[0],
|
||||
Progress: os.Stdout,
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
@@ -44,3 +54,11 @@ func (g *Git) UnpackTarFromBuildContext() (string, error) {
|
||||
_, err := git.PlainClone(directory, false, &options)
|
||||
return directory, err
|
||||
}
|
||||
|
||||
func getGitPullMethod() string {
|
||||
gitPullMethod := os.Getenv(gitPullMethodEnvKey)
|
||||
if ok := supportedGitPullMethods[gitPullMethod]; !ok {
|
||||
gitPullMethod = gitPullMethodHTTPS
|
||||
}
|
||||
return gitPullMethod
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright 2020 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 buildcontext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
)
|
||||
|
||||
func TestGetGitPullMethod(t *testing.T) {
|
||||
tests := []struct {
|
||||
testName string
|
||||
setEnv func() (expectedValue string)
|
||||
}{
|
||||
{
|
||||
testName: "noEnv",
|
||||
setEnv: func() (expectedValue string) {
|
||||
expectedValue = gitPullMethodHTTPS
|
||||
return
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "emptyEnv",
|
||||
setEnv: func() (expectedValue string) {
|
||||
_ = os.Setenv(gitPullMethodEnvKey, "")
|
||||
expectedValue = gitPullMethodHTTPS
|
||||
return
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "httpEnv",
|
||||
setEnv: func() (expectedValue string) {
|
||||
err := os.Setenv(gitPullMethodEnvKey, gitPullMethodHTTP)
|
||||
if nil != err {
|
||||
expectedValue = gitPullMethodHTTPS
|
||||
} else {
|
||||
expectedValue = gitPullMethodHTTP
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "httpsEnv",
|
||||
setEnv: func() (expectedValue string) {
|
||||
_ = os.Setenv(gitPullMethodEnvKey, gitPullMethodHTTPS)
|
||||
expectedValue = gitPullMethodHTTPS
|
||||
return
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "unknownEnv",
|
||||
setEnv: func() (expectedValue string) {
|
||||
_ = os.Setenv(gitPullMethodEnvKey, "unknown")
|
||||
expectedValue = gitPullMethodHTTPS
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.testName, func(t *testing.T) {
|
||||
expectedValue := tt.setEnv()
|
||||
testutil.CheckDeepEqual(t, expectedValue, getGitPullMethod())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,15 @@ limitations under the License.
|
||||
package buildcontext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Tar unifies calls to download and unpack the build context.
|
||||
@@ -35,6 +39,23 @@ func (t *Tar) UnpackTarFromBuildContext() (string, error) {
|
||||
if err := os.MkdirAll(directory, 0750); err != nil {
|
||||
return "", errors.Wrap(err, "unpacking tar from build context")
|
||||
}
|
||||
if t.context == "stdin" {
|
||||
fi, _ := os.Stdin.Stat()
|
||||
if (fi.Mode() & os.ModeCharDevice) != 0 {
|
||||
return "", fmt.Errorf("no data found.. don't forget to add the '--interactive, -i' flag")
|
||||
}
|
||||
logrus.Infof("To simulate EOF and exit, press 'Ctrl+D'")
|
||||
// if launched through docker in interactive mode and without piped data
|
||||
// process will be stuck here until EOF is sent
|
||||
data, err := util.GetInputFrom(os.Stdin)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "fail to get standard input")
|
||||
}
|
||||
t.context = filepath.Join(directory, constants.ContextTar)
|
||||
if err := ioutil.WriteFile(t.context, data, 0644); err != nil {
|
||||
return "", errors.Wrap(err, "fail to redirect standard input into compressed tar file")
|
||||
}
|
||||
}
|
||||
|
||||
return directory, util.UnpackCompressedTar(t.context, directory)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
@@ -25,12 +24,6 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var RootDir string
|
||||
|
||||
func init() {
|
||||
RootDir = constants.RootDir
|
||||
}
|
||||
|
||||
type CurrentCacheKey func() (string, error)
|
||||
|
||||
type DockerCommand interface {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
kConfig "github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -47,7 +47,7 @@ type CopyCommand struct {
|
||||
func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
// Resolve from
|
||||
if c.cmd.From != "" {
|
||||
c.buildcontext = filepath.Join(constants.KanikoDir, c.cmd.From)
|
||||
c.buildcontext = filepath.Join(kConfig.KanikoDir, c.cmd.From)
|
||||
}
|
||||
|
||||
replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
|
||||
@@ -74,7 +74,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
|
||||
}
|
||||
cwd := config.WorkingDir
|
||||
if cwd == "" {
|
||||
cwd = constants.RootDir
|
||||
cwd = kConfig.RootDir
|
||||
}
|
||||
|
||||
destPath, err := util.DestinationFilepath(fullPath, dest, cwd)
|
||||
@@ -191,7 +191,7 @@ func (cr *CachingCopyCommand) ExecuteCommand(config *v1.Config, buildArgs *docke
|
||||
cr.layer = layers[0]
|
||||
cr.readSuccess = true
|
||||
|
||||
cr.extractedFiles, err = util.GetFSFromLayers(RootDir, layers, util.ExtractFunc(cr.extractFn), util.IncludeWhiteout())
|
||||
cr.extractedFiles, err = util.GetFSFromLayers(kConfig.RootDir, layers, util.ExtractFunc(cr.extractFn), util.IncludeWhiteout())
|
||||
|
||||
logrus.Debugf("extractedFiles: %s", cr.extractedFiles)
|
||||
if err != nil {
|
||||
@@ -207,7 +207,8 @@ func (cr *CachingCopyCommand) FilesUsedFromContext(config *v1.Config, buildArgs
|
||||
|
||||
func (cr *CachingCopyCommand) FilesToSnapshot() []string {
|
||||
f := cr.extractedFiles
|
||||
logrus.Debugf("files extracted by caching copy command %s", f)
|
||||
logrus.Debugf("%d files extracted by caching copy command", len(f))
|
||||
logrus.Tracef("Extracted files: %s", f)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -405,7 +405,6 @@ func TestCopyCommand_ExecuteCommand_Extended(t *testing.T) {
|
||||
if err := os.MkdirAll(dir, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
file := filepath.Join(dir, "bam.txt")
|
||||
|
||||
if err := ioutil.WriteFile(file, []byte("meow"), 0777); err != nil {
|
||||
@@ -418,6 +417,7 @@ func TestCopyCommand_ExecuteCommand_Extended(t *testing.T) {
|
||||
if err := os.Symlink("dam.txt", filepath.Join(dir, "sym.link")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return testDir, filepath.Base(dir)
|
||||
}
|
||||
|
||||
@@ -922,4 +922,42 @@ func TestCopyCommand_ExecuteCommand_Extended(t *testing.T) {
|
||||
testutil.CheckNoError(t, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("copy src dir with relative symlinks in a dir", func(t *testing.T) {
|
||||
testDir, srcDir := setupDirs(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
// Make another dir inside bar with a relative symlink
|
||||
dir := filepath.Join(testDir, srcDir, "another")
|
||||
if err := os.MkdirAll(dir, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Symlink("../bam.txt", filepath.Join(dir, "bam_relative.txt"))
|
||||
|
||||
dest := filepath.Join(testDir, "copy")
|
||||
cmd := CopyCommand{
|
||||
cmd: &instructions.CopyCommand{
|
||||
SourcesAndDest: []string{srcDir, dest},
|
||||
},
|
||||
buildcontext: testDir,
|
||||
}
|
||||
|
||||
cfg := &v1.Config{
|
||||
Cmd: nil,
|
||||
Env: []string{},
|
||||
WorkingDir: testDir,
|
||||
}
|
||||
err := cmd.ExecuteCommand(cfg, dockerfile.NewBuildArgs([]string{}))
|
||||
testutil.CheckNoError(t, err)
|
||||
actual, err := ioutil.ReadDir(filepath.Join(dest, "another"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testutil.CheckDeepEqual(t, "bam_relative.txt", actual[0].Name())
|
||||
linkName, err := os.Readlink(filepath.Join(dest, "another", "bam_relative.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testutil.CheckDeepEqual(t, "../bam.txt", linkName)
|
||||
})
|
||||
}
|
||||
|
||||
+4
-2
@@ -24,6 +24,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
kConfig "github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
@@ -202,7 +203,7 @@ func (cr *CachingRunCommand) ExecuteCommand(config *v1.Config, buildArgs *docker
|
||||
cr.readSuccess = true
|
||||
|
||||
cr.extractedFiles, err = util.GetFSFromLayers(
|
||||
constants.RootDir,
|
||||
kConfig.RootDir,
|
||||
layers,
|
||||
util.ExtractFunc(cr.extractFn),
|
||||
util.IncludeWhiteout(),
|
||||
@@ -216,7 +217,8 @@ func (cr *CachingRunCommand) ExecuteCommand(config *v1.Config, buildArgs *docker
|
||||
|
||||
func (cr *CachingRunCommand) FilesToSnapshot() []string {
|
||||
f := cr.extractedFiles
|
||||
logrus.Debugf("files extracted from caching run command %s", f)
|
||||
logrus.Debugf("%d files extracted by caching run command", len(f))
|
||||
logrus.Tracef("Extracted files: %s", f)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2020 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 config
|
||||
|
||||
import (
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
)
|
||||
|
||||
var RootDir string
|
||||
var KanikoDir string
|
||||
var WhitelistPath string
|
||||
|
||||
func init() {
|
||||
RootDir = constants.RootDir
|
||||
KanikoDir = constants.KanikoDir
|
||||
WhitelistPath = constants.WhitelistPath
|
||||
}
|
||||
@@ -56,6 +56,7 @@ type KanikoOptions struct {
|
||||
Cache bool
|
||||
Cleanup bool
|
||||
WhitelistVarRun bool
|
||||
SkipUnusedStages bool
|
||||
}
|
||||
|
||||
// WarmerOptions are options that are set by command line arguments to the cache warmer.
|
||||
|
||||
+158
-56
@@ -25,6 +25,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
@@ -34,15 +35,14 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Stages parses a Dockerfile and returns an array of KanikoStage
|
||||
func Stages(opts *config.KanikoOptions) ([]config.KanikoStage, error) {
|
||||
func ParseStages(opts *config.KanikoOptions) ([]instructions.Stage, []instructions.ArgCommand, error) {
|
||||
var err error
|
||||
var d []uint8
|
||||
match, _ := regexp.MatchString("^https?://", opts.DockerfilePath)
|
||||
if match {
|
||||
response, e := http.Get(opts.DockerfilePath)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
return nil, nil, e
|
||||
}
|
||||
d, err = ioutil.ReadAll(response.Body)
|
||||
} else {
|
||||
@@ -50,41 +50,15 @@ func Stages(opts *config.KanikoOptions) ([]config.KanikoStage, error) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("reading dockerfile at path %s", opts.DockerfilePath))
|
||||
return nil, nil, errors.Wrap(err, fmt.Sprintf("reading dockerfile at path %s", opts.DockerfilePath))
|
||||
}
|
||||
|
||||
stages, metaArgs, err := Parse(d)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parsing dockerfile")
|
||||
}
|
||||
targetStage, err := targetStage(stages, opts.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolveStages(stages)
|
||||
var kanikoStages []config.KanikoStage
|
||||
for index, stage := range stages {
|
||||
resolvedBaseName, err := util.ResolveEnvironmentReplacement(stage.BaseName, opts.BuildArgs, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "resolving base name")
|
||||
}
|
||||
stage.Name = resolvedBaseName
|
||||
logrus.Infof("Resolved base name %s to %s", stage.BaseName, stage.Name)
|
||||
kanikoStages = append(kanikoStages, config.KanikoStage{
|
||||
Stage: stage,
|
||||
BaseImageIndex: baseImageIndex(index, stages),
|
||||
BaseImageStoredLocally: (baseImageIndex(index, stages) != -1),
|
||||
SaveStage: saveStage(index, stages),
|
||||
Final: index == targetStage,
|
||||
MetaArgs: metaArgs,
|
||||
Index: index,
|
||||
})
|
||||
if index == targetStage {
|
||||
break
|
||||
}
|
||||
return nil, nil, errors.Wrap(err, "parsing dockerfile")
|
||||
}
|
||||
|
||||
return kanikoStages, nil
|
||||
return stages, metaArgs, nil
|
||||
}
|
||||
|
||||
// baseImageIndex returns the index of the stage the current stage is built off
|
||||
@@ -204,30 +178,6 @@ func targetStage(stages []instructions.Stage, target string) (int, error) {
|
||||
return -1, fmt.Errorf("%s is not a valid target build stage", target)
|
||||
}
|
||||
|
||||
// resolveStages resolves any calls to previous stages with names to indices
|
||||
// Ex. --from=second_stage should be --from=1 for easier processing later on
|
||||
// As third party library lowers stage name in FROM instruction, this function resolves stage case insensitively.
|
||||
func resolveStages(stages []instructions.Stage) {
|
||||
nameToIndex := make(map[string]string)
|
||||
for i, stage := range stages {
|
||||
index := strconv.Itoa(i)
|
||||
if stage.Name != index {
|
||||
nameToIndex[stage.Name] = index
|
||||
}
|
||||
for _, cmd := range stage.Commands {
|
||||
switch c := cmd.(type) {
|
||||
case *instructions.CopyCommand:
|
||||
if c.From != "" {
|
||||
if val, ok := nameToIndex[strings.ToLower(c.From)]; ok {
|
||||
c.From = val
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ParseCommands parses an array of commands into an array of instructions.Command; used for onbuild
|
||||
func ParseCommands(cmdArray []string) ([]instructions.Command, error) {
|
||||
var cmds []instructions.Command
|
||||
@@ -264,3 +214,155 @@ func saveStage(index int, stages []instructions.Stage) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ResolveCrossStageCommands resolves any calls to previous stages with names to indices
|
||||
// Ex. --from=secondStage should be --from=1 for easier processing later on
|
||||
// As third party library lowers stage name in FROM instruction, this function resolves stage case insensitively.
|
||||
func ResolveCrossStageCommands(cmds []instructions.Command, stageNameToIdx map[string]string) {
|
||||
for _, cmd := range cmds {
|
||||
switch c := cmd.(type) {
|
||||
case *instructions.CopyCommand:
|
||||
if c.From != "" {
|
||||
if val, ok := stageNameToIdx[strings.ToLower(c.From)]; ok {
|
||||
c.From = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resolveStagesArgs resolves all the args from list of stages
|
||||
func resolveStagesArgs(stages []instructions.Stage, args []string) error {
|
||||
for i, s := range stages {
|
||||
resolvedBaseName, err := util.ResolveEnvironmentReplacement(s.BaseName, args, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("resolving base name %s", s.BaseName))
|
||||
}
|
||||
if s.BaseName != resolvedBaseName {
|
||||
stages[i].BaseName = resolvedBaseName
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MakeKanikoStages(opts *config.KanikoOptions, stages []instructions.Stage, metaArgs []instructions.ArgCommand) ([]config.KanikoStage, error) {
|
||||
targetStage, err := targetStage(stages, opts.Target)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error finding target stage")
|
||||
}
|
||||
args := unifyArgs(metaArgs, opts.BuildArgs)
|
||||
if err := resolveStagesArgs(stages, args); err != nil {
|
||||
return nil, errors.Wrap(err, "resolving args")
|
||||
}
|
||||
if opts.SkipUnusedStages {
|
||||
stages = skipUnusedStages(stages, &targetStage, opts.Target)
|
||||
}
|
||||
var kanikoStages []config.KanikoStage
|
||||
for index, stage := range stages {
|
||||
if len(stage.Name) > 0 {
|
||||
logrus.Infof("Resolved base name %s to %s", stage.BaseName, stage.Name)
|
||||
}
|
||||
baseImageIndex := baseImageIndex(index, stages)
|
||||
kanikoStages = append(kanikoStages, config.KanikoStage{
|
||||
Stage: stage,
|
||||
BaseImageIndex: baseImageIndex,
|
||||
BaseImageStoredLocally: (baseImageIndex != -1),
|
||||
SaveStage: saveStage(index, stages),
|
||||
Final: index == targetStage,
|
||||
MetaArgs: metaArgs,
|
||||
Index: index,
|
||||
})
|
||||
if index == targetStage {
|
||||
break
|
||||
}
|
||||
}
|
||||
return kanikoStages, nil
|
||||
}
|
||||
|
||||
func GetOnBuildInstructions(config *v1.Config, stageNameToIdx map[string]string) ([]instructions.Command, error) {
|
||||
if config.OnBuild == nil || len(config.OnBuild) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cmds, err := ParseCommands(config.OnBuild)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Iterate over commands and replace references to other stages with their index
|
||||
ResolveCrossStageCommands(cmds, stageNameToIdx)
|
||||
return cmds, nil
|
||||
}
|
||||
|
||||
// unifyArgs returns the unified args between metaArgs and --build-arg
|
||||
// by default --build-arg overrides metaArgs except when --build-arg is empty
|
||||
func unifyArgs(metaArgs []instructions.ArgCommand, buildArgs []string) []string {
|
||||
argsMap := make(map[string]string)
|
||||
for _, a := range metaArgs {
|
||||
if a.Value != nil {
|
||||
argsMap[a.Key] = *a.Value
|
||||
}
|
||||
}
|
||||
splitter := "="
|
||||
for _, a := range buildArgs {
|
||||
s := strings.Split(a, splitter)
|
||||
if len(s) > 1 && s[1] != "" {
|
||||
argsMap[s[0]] = s[1]
|
||||
}
|
||||
}
|
||||
var args []string
|
||||
for k, v := range argsMap {
|
||||
args = append(args, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// skipUnusedStages returns the list of used stages without the unnecessaries ones
|
||||
func skipUnusedStages(stages []instructions.Stage, lastStageIndex *int, target string) []instructions.Stage {
|
||||
stagesDependencies := make(map[string]bool)
|
||||
var onlyUsedStages []instructions.Stage
|
||||
idx := *lastStageIndex
|
||||
|
||||
lastStageBaseName := stages[idx].BaseName
|
||||
|
||||
for i := idx; i >= 0; i-- {
|
||||
s := stages[i]
|
||||
if (s.Name != "" && stagesDependencies[s.Name]) || s.Name == lastStageBaseName || i == idx {
|
||||
for _, c := range s.Commands {
|
||||
switch cmd := c.(type) {
|
||||
case *instructions.CopyCommand:
|
||||
stageName := cmd.From
|
||||
if copyFromIndex, err := strconv.Atoi(stageName); err == nil {
|
||||
stageName = stages[copyFromIndex].Name
|
||||
}
|
||||
if !stagesDependencies[stageName] {
|
||||
stagesDependencies[stageName] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if i != idx {
|
||||
stagesDependencies[s.BaseName] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
dependenciesLen := len(stagesDependencies)
|
||||
if target == "" && dependenciesLen == 0 {
|
||||
return stages
|
||||
} else if dependenciesLen > 0 {
|
||||
for i := 0; i < idx; i++ {
|
||||
if stages[i].Name == "" {
|
||||
continue
|
||||
}
|
||||
s := stages[i]
|
||||
if stagesDependencies[s.Name] || s.Name == lastStageBaseName {
|
||||
onlyUsedStages = append(onlyUsedStages, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
onlyUsedStages = append(onlyUsedStages, stages[idx])
|
||||
if idx > len(onlyUsedStages)-1 {
|
||||
*lastStageIndex = len(onlyUsedStages) - 1
|
||||
}
|
||||
|
||||
return onlyUsedStages
|
||||
}
|
||||
|
||||
@@ -17,25 +17,28 @@ limitations under the License.
|
||||
package dockerfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
)
|
||||
|
||||
func Test_Stages_ArgValueWithQuotes(t *testing.T) {
|
||||
func Test_ParseStages_ArgValueWithQuotes(t *testing.T) {
|
||||
dockerfile := `
|
||||
ARG IMAGE="ubuntu:16.04"
|
||||
ARG FOO=bar
|
||||
FROM ${IMAGE}
|
||||
RUN echo hi > /hi
|
||||
|
||||
|
||||
FROM scratch AS second
|
||||
COPY --from=0 /hi /hi2
|
||||
|
||||
|
||||
FROM scratch
|
||||
COPY --from=second /hi2 /hi3
|
||||
`
|
||||
@@ -53,25 +56,23 @@ func Test_Stages_ArgValueWithQuotes(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stages, err := Stages(&config.KanikoOptions{DockerfilePath: tmpfile.Name()})
|
||||
stages, metaArgs, err := ParseStages(&config.KanikoOptions{DockerfilePath: tmpfile.Name()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(stages) == 0 {
|
||||
t.Fatal("length of stages expected to be greater than zero, but was zero")
|
||||
|
||||
}
|
||||
|
||||
if len(stages[0].MetaArgs) == 0 {
|
||||
t.Fatal("length of stage[0] meta args expected to be greater than zero, but was zero")
|
||||
if len(metaArgs) != 2 {
|
||||
t.Fatalf("length of stage meta args expected to be 2, but was %d", len(metaArgs))
|
||||
}
|
||||
|
||||
expectedVal := "ubuntu:16.04"
|
||||
|
||||
arg := stages[0].MetaArgs[0]
|
||||
if arg.ValueString() != expectedVal {
|
||||
t.Fatalf("expected stages[0].MetaArgs[0] val to be %s but was %s", expectedVal, arg.ValueString())
|
||||
for i, expectedVal := range []string{"ubuntu:16.04", "bar"} {
|
||||
if metaArgs[i].ValueString() != expectedVal {
|
||||
t.Fatalf("expected metaArg %d val to be %s but was %s", i, expectedVal, metaArgs[i].ValueString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,40 +190,63 @@ func Test_stripEnclosingQuotes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_resolveStages(t *testing.T) {
|
||||
dockerfile := `
|
||||
FROM scratch
|
||||
RUN echo hi > /hi
|
||||
|
||||
FROM scratch AS second
|
||||
COPY --from=0 /hi /hi2
|
||||
|
||||
FROM scratch AS tHiRd
|
||||
COPY --from=second /hi2 /hi3
|
||||
COPY --from=1 /hi2 /hi3
|
||||
|
||||
FROM scratch
|
||||
COPY --from=thIrD /hi3 /hi4
|
||||
COPY --from=third /hi3 /hi4
|
||||
COPY --from=2 /hi3 /hi4
|
||||
`
|
||||
stages, _, err := Parse([]byte(dockerfile))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func Test_GetOnBuildInstructions(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
cfg *v1.Config
|
||||
stageToIdx map[string]string
|
||||
expCommands []instructions.Command
|
||||
}
|
||||
resolveStages(stages)
|
||||
for index, stage := range stages {
|
||||
if index == 0 {
|
||||
continue
|
||||
}
|
||||
expectedStage := strconv.Itoa(index - 1)
|
||||
for _, command := range stage.Commands {
|
||||
copyCmd := command.(*instructions.CopyCommand)
|
||||
if copyCmd.From != expectedStage {
|
||||
t.Fatalf("unexpected copy command: %s resolved to stage %s, expected %s", copyCmd.String(), copyCmd.From, expectedStage)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{name: "no on-build on config",
|
||||
cfg: &v1.Config{},
|
||||
stageToIdx: map[string]string{"builder": "0"},
|
||||
expCommands: nil,
|
||||
},
|
||||
{name: "onBuild on config, nothing to resolve",
|
||||
cfg: &v1.Config{OnBuild: []string{"WORKDIR /app"}},
|
||||
stageToIdx: map[string]string{"builder": "0", "temp": "1"},
|
||||
expCommands: []instructions.Command{&instructions.WorkdirCommand{Path: "/app"}},
|
||||
},
|
||||
{name: "onBuild on config, resolve multiple stages",
|
||||
cfg: &v1.Config{OnBuild: []string{"COPY --from=builder a.txt b.txt", "COPY --from=temp /app /app"}},
|
||||
stageToIdx: map[string]string{"builder": "0", "temp": "1"},
|
||||
expCommands: []instructions.Command{
|
||||
&instructions.CopyCommand{
|
||||
SourcesAndDest: []string{"a.txt b.txt"},
|
||||
From: "0",
|
||||
},
|
||||
&instructions.CopyCommand{
|
||||
SourcesAndDest: []string{"/app /app"},
|
||||
From: "1",
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cmds, err := GetOnBuildInstructions(test.cfg, test.stageToIdx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse config for on-build instructions")
|
||||
}
|
||||
if len(cmds) != len(test.expCommands) {
|
||||
t.Fatalf("Expected %d commands, got %d", len(test.expCommands), len(cmds))
|
||||
}
|
||||
|
||||
for i, cmd := range cmds {
|
||||
if reflect.TypeOf(cmd) != reflect.TypeOf(test.expCommands[i]) {
|
||||
t.Fatalf("Got command %s, expected %s", cmd, test.expCommands[i])
|
||||
}
|
||||
switch c := cmd.(type) {
|
||||
case *instructions.CopyCommand:
|
||||
{
|
||||
exp := test.expCommands[i].(*instructions.CopyCommand)
|
||||
testutil.CheckDeepEqual(t, exp.From, c.From)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,10 +254,10 @@ func Test_targetStage(t *testing.T) {
|
||||
dockerfile := `
|
||||
FROM scratch
|
||||
RUN echo hi > /hi
|
||||
|
||||
|
||||
FROM scratch AS second
|
||||
COPY --from=0 /hi /hi2
|
||||
|
||||
|
||||
FROM scratch
|
||||
COPY --from=second /hi2 /hi3
|
||||
`
|
||||
@@ -364,3 +388,261 @@ func Test_baseImageIndex(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ResolveStagesArgs(t *testing.T) {
|
||||
dockerfile := `
|
||||
ARG IMAGE="ubuntu:16.04"
|
||||
ARG LAST_STAGE_VARIANT
|
||||
FROM ${IMAGE} as base
|
||||
RUN echo hi > /hi
|
||||
FROM base AS base-dev
|
||||
RUN echo dev >> /hi
|
||||
FROM base AS base-prod
|
||||
RUN echo prod >> /hi
|
||||
FROM base-${LAST_STAGE_VARIANT}
|
||||
RUN cat /hi
|
||||
`
|
||||
|
||||
buildArgLastVariants := []string{"dev", "prod"}
|
||||
buildArgImages := []string{"alpine:3.11", ""}
|
||||
var expectedImage string
|
||||
|
||||
for _, buildArgLastVariant := range buildArgLastVariants {
|
||||
for _, buildArgImage := range buildArgImages {
|
||||
if buildArgImage != "" {
|
||||
expectedImage = buildArgImage
|
||||
} else {
|
||||
expectedImage = "ubuntu:16.04"
|
||||
}
|
||||
buildArgs := []string{fmt.Sprintf("IMAGE=%s", buildArgImage), fmt.Sprintf("LAST_STAGE_VARIANT=%s", buildArgLastVariant)}
|
||||
|
||||
stages, metaArgs, err := Parse([]byte(dockerfile))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stagesLen := len(stages)
|
||||
args := unifyArgs(metaArgs, buildArgs)
|
||||
if err := resolveStagesArgs(stages, args); err != nil {
|
||||
t.Fatalf("fail to resolves args %v: %v", buildArgs, err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
actualSourceCode string
|
||||
actualBaseName string
|
||||
expectedSourceCode string
|
||||
expectedBaseName string
|
||||
}{
|
||||
{
|
||||
name: "Test_BuildArg_From_First_Stage",
|
||||
actualSourceCode: stages[0].SourceCode,
|
||||
actualBaseName: stages[0].BaseName,
|
||||
expectedSourceCode: "FROM ${IMAGE} as base",
|
||||
expectedBaseName: expectedImage,
|
||||
},
|
||||
{
|
||||
name: "Test_BuildArg_From_Last_Stage",
|
||||
actualSourceCode: stages[stagesLen-1].SourceCode,
|
||||
actualBaseName: stages[stagesLen-1].BaseName,
|
||||
expectedSourceCode: "FROM base-${LAST_STAGE_VARIANT}",
|
||||
expectedBaseName: fmt.Sprintf("base-%s", buildArgLastVariant),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
testutil.CheckDeepEqual(t, test.expectedSourceCode, test.actualSourceCode)
|
||||
testutil.CheckDeepEqual(t, test.expectedBaseName, test.actualBaseName)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SkipingUnusedStages(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
dockerfile string
|
||||
targets []string
|
||||
expectedSourceCodes map[string][]string
|
||||
expectedTargetIndexBeforeSkip map[string]int
|
||||
expectedTargetIndexAfterSkip map[string]int
|
||||
}{
|
||||
{
|
||||
description: "dockerfile_without_copyFrom",
|
||||
dockerfile: `
|
||||
FROM alpine:3.11 AS base-dev
|
||||
RUN echo dev > /hi
|
||||
FROM alpine:3.11 AS base-prod
|
||||
RUN echo prod > /hi
|
||||
FROM base-dev as final-stage
|
||||
RUN cat /hi
|
||||
`,
|
||||
targets: []string{"base-dev", "base-prod", ""},
|
||||
expectedSourceCodes: map[string][]string{
|
||||
"base-dev": {"FROM alpine:3.11 AS base-dev"},
|
||||
"base-prod": {"FROM alpine:3.11 AS base-prod"},
|
||||
"": {"FROM alpine:3.11 AS base-dev", "FROM base-dev as final-stage"},
|
||||
},
|
||||
expectedTargetIndexBeforeSkip: map[string]int{
|
||||
"base-dev": 0,
|
||||
"base-prod": 1,
|
||||
"": 2,
|
||||
},
|
||||
expectedTargetIndexAfterSkip: map[string]int{
|
||||
"base-dev": 0,
|
||||
"base-prod": 0,
|
||||
"": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "dockerfile_with_copyFrom",
|
||||
dockerfile: `
|
||||
FROM alpine:3.11 AS base-dev
|
||||
RUN echo dev > /hi
|
||||
FROM alpine:3.11 AS base-prod
|
||||
RUN echo prod > /hi
|
||||
FROM alpine:3.11
|
||||
COPY --from=base-prod /hi /finalhi
|
||||
RUN cat /finalhi
|
||||
`,
|
||||
targets: []string{"base-dev", "base-prod", ""},
|
||||
expectedSourceCodes: map[string][]string{
|
||||
"base-dev": {"FROM alpine:3.11 AS base-dev"},
|
||||
"base-prod": {"FROM alpine:3.11 AS base-prod"},
|
||||
"": {"FROM alpine:3.11 AS base-prod", "FROM alpine:3.11"},
|
||||
},
|
||||
expectedTargetIndexBeforeSkip: map[string]int{
|
||||
"base-dev": 0,
|
||||
"base-prod": 1,
|
||||
"": 2,
|
||||
},
|
||||
expectedTargetIndexAfterSkip: map[string]int{
|
||||
"base-dev": 0,
|
||||
"base-prod": 0,
|
||||
"": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "dockerfile_with_two_copyFrom",
|
||||
dockerfile: `
|
||||
FROM alpine:3.11 AS base-dev
|
||||
RUN echo dev > /hi
|
||||
FROM alpine:3.11 AS base-prod
|
||||
RUN echo prod > /hi
|
||||
FROM alpine:3.11
|
||||
COPY --from=base-dev /hi /finalhidev
|
||||
COPY --from=base-prod /hi /finalhiprod
|
||||
RUN cat /finalhidev
|
||||
RUN cat /finalhiprod
|
||||
`,
|
||||
targets: []string{"base-dev", "base-prod", ""},
|
||||
expectedSourceCodes: map[string][]string{
|
||||
"base-dev": {"FROM alpine:3.11 AS base-dev"},
|
||||
"base-prod": {"FROM alpine:3.11 AS base-prod"},
|
||||
"": {"FROM alpine:3.11 AS base-dev", "FROM alpine:3.11 AS base-prod", "FROM alpine:3.11"},
|
||||
},
|
||||
expectedTargetIndexBeforeSkip: map[string]int{
|
||||
"base-dev": 0,
|
||||
"base-prod": 1,
|
||||
"": 2,
|
||||
},
|
||||
expectedTargetIndexAfterSkip: map[string]int{
|
||||
"base-dev": 0,
|
||||
"base-prod": 0,
|
||||
"": 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "dockerfile_with_two_copyFrom_and_arg",
|
||||
dockerfile: `
|
||||
FROM debian:9.11 as base
|
||||
COPY . .
|
||||
FROM scratch as second
|
||||
ENV foopath context/foo
|
||||
COPY --from=0 $foopath context/b* /foo/
|
||||
FROM second as third
|
||||
COPY --from=base /context/foo /new/foo
|
||||
FROM base as fourth
|
||||
# Make sure that we snapshot intermediate images correctly
|
||||
RUN date > /date
|
||||
ENV foo bar
|
||||
# This base image contains symlinks with relative paths to whitelisted directories
|
||||
# We need to test they're extracted correctly
|
||||
FROM fedora@sha256:c4cc32b09c6ae3f1353e7e33a8dda93dc41676b923d6d89afa996b421cc5aa48
|
||||
FROM fourth
|
||||
ARG file=/foo2
|
||||
COPY --from=second /foo ${file}
|
||||
COPY --from=debian:9.11 /etc/os-release /new
|
||||
`,
|
||||
targets: []string{"base", ""},
|
||||
expectedSourceCodes: map[string][]string{
|
||||
"base": {"FROM debian:9.11 as base"},
|
||||
"second": {"FROM debian:9.11 as base", "FROM scratch as second"},
|
||||
"": {"FROM debian:9.11 as base", "FROM scratch as second", "FROM base as fourth", "FROM fourth"},
|
||||
},
|
||||
expectedTargetIndexBeforeSkip: map[string]int{
|
||||
"base": 0,
|
||||
"second": 1,
|
||||
"": 5,
|
||||
},
|
||||
expectedTargetIndexAfterSkip: map[string]int{
|
||||
"base": 0,
|
||||
"second": 1,
|
||||
"": 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "dockerfile_without_final_dependencies",
|
||||
dockerfile: `
|
||||
FROM alpine:3.11
|
||||
FROM debian:9.11 as base
|
||||
RUN echo foo > /foo
|
||||
FROM debian:9.11 as fizz
|
||||
RUN echo fizz >> /fizz
|
||||
COPY --from=base /foo /fizz
|
||||
FROM alpine:3.11 as buzz
|
||||
RUN echo buzz > /buzz
|
||||
FROM alpine:3.11 as final
|
||||
RUN echo bar > /bar
|
||||
`,
|
||||
targets: []string{"final", "buzz", "fizz", ""},
|
||||
expectedSourceCodes: map[string][]string{
|
||||
"final": {"FROM alpine:3.11 as final"},
|
||||
"buzz": {"FROM alpine:3.11 as buzz"},
|
||||
"fizz": {"FROM debian:9.11 as base", "FROM debian:9.11 as fizz"},
|
||||
"": {"FROM alpine:3.11", "FROM debian:9.11 as base", "FROM debian:9.11 as fizz", "FROM alpine:3.11 as buzz", "FROM alpine:3.11 as final"},
|
||||
},
|
||||
expectedTargetIndexBeforeSkip: map[string]int{
|
||||
"final": 4,
|
||||
"buzz": 3,
|
||||
"fizz": 2,
|
||||
"": 4,
|
||||
},
|
||||
expectedTargetIndexAfterSkip: map[string]int{
|
||||
"final": 0,
|
||||
"buzz": 0,
|
||||
"fizz": 1,
|
||||
"": 4,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
stages, _, err := Parse([]byte(test.dockerfile))
|
||||
testutil.CheckError(t, false, err)
|
||||
actualSourceCodes := make(map[string][]string)
|
||||
for _, target := range test.targets {
|
||||
targetIndex, err := targetStage(stages, target)
|
||||
testutil.CheckError(t, false, err)
|
||||
targetIndexBeforeSkip := targetIndex
|
||||
onlyUsedStages := skipUnusedStages(stages, &targetIndex, target)
|
||||
for _, s := range onlyUsedStages {
|
||||
actualSourceCodes[target] = append(actualSourceCodes[target], s.SourceCode)
|
||||
}
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
testutil.CheckDeepEqual(t, test.expectedSourceCodes[target], actualSourceCodes[target])
|
||||
testutil.CheckDeepEqual(t, test.expectedTargetIndexBeforeSkip[target], targetIndexBeforeSkip)
|
||||
testutil.CheckDeepEqual(t, test.expectedTargetIndexAfterSkip[target], targetIndex)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+115
-56
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -51,6 +52,11 @@ import (
|
||||
// This is the size of an empty tar in Go
|
||||
const emptyTarSize = 1024
|
||||
|
||||
// for testing
|
||||
var (
|
||||
initializeConfig = initConfig
|
||||
)
|
||||
|
||||
type cachePusher func(*config.KanikoOptions, string, string, string) error
|
||||
type snapShotter interface {
|
||||
Init() error
|
||||
@@ -77,7 +83,7 @@ type stageBuilder struct {
|
||||
}
|
||||
|
||||
// newStageBuilder returns a new type stageBuilder which contains all the information required to build the stage
|
||||
func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, crossStageDeps map[int][]string, dcm map[string]string, sid map[string]string) (*stageBuilder, error) {
|
||||
func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, crossStageDeps map[int][]string, dcm map[string]string, sid map[string]string, stageNameToIdx map[string]string) (*stageBuilder, error) {
|
||||
sourceImage, err := util.RetrieveSourceImage(stage, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -88,7 +94,7 @@ func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, cross
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil {
|
||||
if err := resolveOnBuild(&stage, &imageConfig.Config, stageNameToIdx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -97,7 +103,7 @@ func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, cross
|
||||
return nil, err
|
||||
}
|
||||
l := snapshot.NewLayeredMap(hasher, util.CacheHasher())
|
||||
snapshotter := snapshot.NewSnapshotter(l, constants.RootDir)
|
||||
snapshotter := snapshot.NewSnapshotter(l, config.RootDir)
|
||||
|
||||
digest, err := sourceImage.Digest()
|
||||
if err != nil {
|
||||
@@ -135,7 +141,7 @@ func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, cross
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func initializeConfig(img partial.WithConfigFile, opts *config.KanikoOptions) (*v1.ConfigFile, error) {
|
||||
func initConfig(img partial.WithConfigFile, opts *config.KanikoOptions) (*v1.ConfigFile, error) {
|
||||
imageConfig, err := img.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -298,7 +304,7 @@ func (s *stageBuilder) build() error {
|
||||
if shouldUnpack {
|
||||
t := timing.Start("FS Unpacking")
|
||||
|
||||
if _, err := util.GetFSFromImage(constants.RootDir, s.image, util.ExtractFile); err != nil {
|
||||
if _, err := util.GetFSFromImage(config.RootDir, s.image, util.ExtractFile); err != nil {
|
||||
return errors.Wrap(err, "failed to get filesystem from image")
|
||||
}
|
||||
|
||||
@@ -307,7 +313,7 @@ func (s *stageBuilder) build() error {
|
||||
logrus.Info("Skipping unpacking as no commands require it.")
|
||||
}
|
||||
|
||||
if err := util.DetectFilesystemWhitelist(constants.WhitelistPath); err != nil {
|
||||
if err := util.DetectFilesystemWhitelist(config.WhitelistPath); err != nil {
|
||||
return errors.Wrap(err, "failed to check filesystem whitelist")
|
||||
}
|
||||
|
||||
@@ -333,9 +339,11 @@ func (s *stageBuilder) build() error {
|
||||
return errors.Wrap(err, "failed to get files used from context")
|
||||
}
|
||||
|
||||
*compositeKey, err = s.populateCompositeKey(command, files, *compositeKey, s.args, s.cf.Config.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
if s.opts.Cache {
|
||||
*compositeKey, err = s.populateCompositeKey(command, files, *compositeKey, s.args, s.cf.Config.Env)
|
||||
if err != nil && s.opts.Cache {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Info(command.String())
|
||||
@@ -371,19 +379,21 @@ func (s *stageBuilder) build() error {
|
||||
return errors.Wrap(err, "failed to take snapshot")
|
||||
}
|
||||
|
||||
logrus.Debugf("build: composite key for command %v %v", command.String(), compositeKey)
|
||||
ck, err := compositeKey.Hash()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to hash composite key")
|
||||
}
|
||||
if s.opts.Cache {
|
||||
logrus.Debugf("build: composite key for command %v %v", command.String(), compositeKey)
|
||||
ck, err := compositeKey.Hash()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to hash composite key")
|
||||
}
|
||||
|
||||
logrus.Debugf("build: cache key for command %v %v", command.String(), ck)
|
||||
logrus.Debugf("build: cache key for command %v %v", command.String(), ck)
|
||||
|
||||
// Push layer to cache (in parallel) now along with new config file
|
||||
if s.opts.Cache && command.ShouldCacheOutput() {
|
||||
cacheGroup.Go(func() error {
|
||||
return s.pushLayerToCache(s.opts, ck, tarPath, command.String())
|
||||
})
|
||||
// Push layer to cache (in parallel) now along with new config file
|
||||
if command.ShouldCacheOutput() {
|
||||
cacheGroup.Go(func() error {
|
||||
return s.pushLayerToCache(s.opts, ck, tarPath, command.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := s.saveSnapshotToImage(command.String(), tarPath); err != nil {
|
||||
return errors.Wrap(err, "failed to save snapshot to image")
|
||||
@@ -414,7 +424,7 @@ func (s *stageBuilder) takeSnapshot(files []string) (string, error) {
|
||||
}
|
||||
|
||||
func (s *stageBuilder) shouldTakeSnapshot(index int, files []string) bool {
|
||||
isLastCommand := index == len(s.stage.Commands)-1
|
||||
isLastCommand := index == len(s.cmds)-1
|
||||
|
||||
// We only snapshot the very end with single snapshot mode on.
|
||||
if s.opts.SingleSnapshot {
|
||||
@@ -486,11 +496,7 @@ func (s *stageBuilder) saveLayerToImage(layer v1.Layer, createdBy string) error
|
||||
return err
|
||||
}
|
||||
|
||||
func CalculateDependencies(opts *config.KanikoOptions) (map[int][]string, error) {
|
||||
stages, err := dockerfile.Stages(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func CalculateDependencies(stages []config.KanikoStage, opts *config.KanikoOptions, stageNameToIdx map[string]string) (map[int][]string, error) {
|
||||
images := []v1.Image{}
|
||||
depGraph := map[int][]string{}
|
||||
for _, s := range stages {
|
||||
@@ -512,7 +518,11 @@ func CalculateDependencies(opts *config.KanikoOptions) (map[int][]string, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range s.Commands {
|
||||
|
||||
cmds, err := dockerfile.GetOnBuildInstructions(&cfg.Config, stageNameToIdx)
|
||||
cmds = append(cmds, s.Commands...)
|
||||
|
||||
for _, c := range cmds {
|
||||
switch cmd := c.(type) {
|
||||
case *instructions.CopyCommand:
|
||||
if cmd.From != "" {
|
||||
@@ -524,7 +534,6 @@ func CalculateDependencies(opts *config.KanikoOptions) (map[int][]string, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
depGraph[i] = append(depGraph[i], resolved[0:len(resolved)-1]...)
|
||||
}
|
||||
case *instructions.EnvCommand:
|
||||
@@ -554,27 +563,33 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
||||
digestToCacheKey := make(map[string]string)
|
||||
stageIdxToDigest := make(map[string]string)
|
||||
|
||||
// Parse dockerfile
|
||||
stages, err := dockerfile.Stages(opts)
|
||||
stages, metaArgs, err := dockerfile.ParseStages(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := util.GetExcludedFiles(opts.DockerfilePath, opts.SrcContext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Some stages may refer to other random images, not previous stages
|
||||
if err := fetchExtraStages(stages, opts); err != nil {
|
||||
stageNameToIdx := ResolveCrossStageInstructions(stages)
|
||||
|
||||
kanikoStages, err := dockerfile.MakeKanikoStages(opts, stages, metaArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
crossStageDependencies, err := CalculateDependencies(opts)
|
||||
if err := util.GetExcludedFiles(opts.DockerfilePath, opts.SrcContext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Some stages may refer to other random images, not previous stages
|
||||
if err := fetchExtraStages(kanikoStages, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crossStageDependencies, err := CalculateDependencies(kanikoStages, opts, stageNameToIdx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Infof("Built cross stage deps: %v", crossStageDependencies)
|
||||
|
||||
for index, stage := range stages {
|
||||
sb, err := newStageBuilder(opts, stage, crossStageDependencies, digestToCacheKey, stageIdxToDigest)
|
||||
for index, stage := range kanikoStages {
|
||||
sb, err := newStageBuilder(opts, stage, crossStageDependencies, digestToCacheKey, stageIdxToDigest, stageNameToIdx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -589,6 +604,17 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configFile, err := sourceImage.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configFile.OS = runtime.GOOS
|
||||
configFile.Architecture = runtime.GOARCH
|
||||
sourceImage, err = mutate.ConfigFile(sourceImage, configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, err := sourceImage.Digest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -629,20 +655,23 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dstDir := filepath.Join(constants.KanikoDir, strconv.Itoa(index))
|
||||
dstDir := filepath.Join(config.KanikoDir, strconv.Itoa(index))
|
||||
if err := os.MkdirAll(dstDir, 0644); err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err,
|
||||
fmt.Sprintf("to create workspace for stage %s",
|
||||
stageIdxToDigest[strconv.Itoa(index)],
|
||||
))
|
||||
}
|
||||
for _, p := range filesToSave {
|
||||
logrus.Infof("Saving file %s for later use", p)
|
||||
if err := util.CopyFileOrSymlink(p, dstDir); err != nil {
|
||||
return nil, err
|
||||
if err := util.CopyFileOrSymlink(p, dstDir, config.RootDir); err != nil {
|
||||
return nil, errors.Wrap(err, "could not save file")
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the filesystem
|
||||
if err := util.DeleteFilesystem(); err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("deleting file system after satge %d", index))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,14 +683,22 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
||||
func filesToSave(deps []string) ([]string, error) {
|
||||
srcFiles := []string{}
|
||||
for _, src := range deps {
|
||||
srcs, err := filepath.Glob(src)
|
||||
srcs, err := filepath.Glob(filepath.Join(config.RootDir, src))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range srcs {
|
||||
if link, err := util.EvalSymLink(f); err == nil {
|
||||
link, err = filepath.Rel(config.RootDir, link)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("could not find relative path to %s", config.RootDir))
|
||||
}
|
||||
srcFiles = append(srcFiles, link)
|
||||
}
|
||||
f, err = filepath.Rel(config.RootDir, f)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("could not find relative path to %s", config.RootDir))
|
||||
}
|
||||
srcFiles = append(srcFiles, f)
|
||||
}
|
||||
}
|
||||
@@ -672,7 +709,7 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e
|
||||
t := timing.Start("Fetching Extra Stages")
|
||||
defer timing.DefaultRun.Stop(t)
|
||||
|
||||
var names = []string{}
|
||||
var names []string
|
||||
|
||||
for stageIndex, s := range stages {
|
||||
for _, cmd := range s.Commands {
|
||||
@@ -689,11 +726,10 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e
|
||||
continue
|
||||
}
|
||||
// Check if the name is the alias of a previous stage
|
||||
for _, name := range names {
|
||||
if name == c.From {
|
||||
continue
|
||||
}
|
||||
if fromPreviousStage(c, names) {
|
||||
continue
|
||||
}
|
||||
|
||||
// This must be an image name, fetch it.
|
||||
logrus.Debugf("Found extra base image stage %s", c.From)
|
||||
sourceImage, err := util.RetrieveRemoteImage(c.From, opts)
|
||||
@@ -714,10 +750,20 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fromPreviousStage(copyCommand *instructions.CopyCommand, previousStageNames []string) bool {
|
||||
for _, previousStageName := range previousStageNames {
|
||||
if previousStageName == copyCommand.From {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func extractImageToDependencyDir(name string, image v1.Image) error {
|
||||
t := timing.Start("Extracting Image to Dependency Dir")
|
||||
defer timing.DefaultRun.Stop(t)
|
||||
dependencyDir := filepath.Join(constants.KanikoDir, name)
|
||||
dependencyDir := filepath.Join(config.KanikoDir, name)
|
||||
if err := os.MkdirAll(dependencyDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -752,15 +798,12 @@ func getHasher(snapshotMode string) (func(string) (string, error), error) {
|
||||
return nil, fmt.Errorf("%s is not a valid snapshot mode", snapshotMode)
|
||||
}
|
||||
|
||||
func resolveOnBuild(stage *config.KanikoStage, config *v1.Config) error {
|
||||
if config.OnBuild == nil || len(config.OnBuild) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Otherwise, parse into commands
|
||||
cmds, err := dockerfile.ParseCommands(config.OnBuild)
|
||||
func resolveOnBuild(stage *config.KanikoStage, config *v1.Config, stageNameToIdx map[string]string) error {
|
||||
cmds, err := dockerfile.GetOnBuildInstructions(config, stageNameToIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Append to the beginning of the commands in the stage
|
||||
stage.Commands = append(cmds, stage.Commands...)
|
||||
logrus.Infof("Executing %v build triggers", len(cmds))
|
||||
@@ -789,3 +832,19 @@ func reviewConfig(stage config.KanikoStage, config *v1.Config) {
|
||||
config.Cmd = nil
|
||||
}
|
||||
}
|
||||
|
||||
// iterates over a list of stages and resolves instructions referring to earlier stages
|
||||
// returns a mapping of stage name to stage id, f.e - ["first": "0", "second": "1", "target": "2"]
|
||||
func ResolveCrossStageInstructions(stages []instructions.Stage) map[string]string {
|
||||
nameToIndex := make(map[string]string)
|
||||
for i, stage := range stages {
|
||||
index := strconv.Itoa(i)
|
||||
if stage.Name != "" {
|
||||
nameToIndex[stage.Name] = index
|
||||
}
|
||||
dockerfile.ResolveCrossStageCommands(stage.Commands, nameToIndex)
|
||||
}
|
||||
|
||||
logrus.Debugf("Built stage name to index map: %v", nameToIndex)
|
||||
return nameToIndex
|
||||
}
|
||||
|
||||
+126
-46
@@ -25,6 +25,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/commands"
|
||||
@@ -35,6 +36,7 @@ import (
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
)
|
||||
|
||||
@@ -88,28 +90,17 @@ func stage(t *testing.T, d string) config.KanikoStage {
|
||||
}
|
||||
}
|
||||
|
||||
type MockCommand struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (m *MockCommand) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||
commands := []instructions.Command{
|
||||
&MockCommand{name: "command1"},
|
||||
&MockCommand{name: "command2"},
|
||||
&MockCommand{name: "command3"},
|
||||
}
|
||||
|
||||
stage := instructions.Stage{
|
||||
Commands: commands,
|
||||
cmds := []commands.DockerCommand{
|
||||
&MockDockerCommand{command: "command1"},
|
||||
&MockDockerCommand{command: "command2"},
|
||||
&MockDockerCommand{command: "command3"},
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
stage config.KanikoStage
|
||||
opts *config.KanikoOptions
|
||||
cmds []commands.DockerCommand
|
||||
}
|
||||
type args struct {
|
||||
index int
|
||||
@@ -126,8 +117,8 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||
fields: fields{
|
||||
stage: config.KanikoStage{
|
||||
Final: true,
|
||||
Stage: stage,
|
||||
},
|
||||
cmds: cmds,
|
||||
},
|
||||
args: args{
|
||||
index: 1,
|
||||
@@ -139,11 +130,11 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||
fields: fields{
|
||||
stage: config.KanikoStage{
|
||||
Final: false,
|
||||
Stage: stage,
|
||||
},
|
||||
cmds: cmds,
|
||||
},
|
||||
args: args{
|
||||
index: len(commands) - 1,
|
||||
index: len(cmds) - 1,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
@@ -152,8 +143,8 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||
fields: fields{
|
||||
stage: config.KanikoStage{
|
||||
Final: false,
|
||||
Stage: stage,
|
||||
},
|
||||
cmds: cmds,
|
||||
},
|
||||
args: args{
|
||||
index: 0,
|
||||
@@ -165,9 +156,9 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||
fields: fields{
|
||||
stage: config.KanikoStage{
|
||||
Final: false,
|
||||
Stage: stage,
|
||||
},
|
||||
opts: &config.KanikoOptions{Cache: true},
|
||||
cmds: cmds,
|
||||
},
|
||||
args: args{
|
||||
index: 0,
|
||||
@@ -184,6 +175,7 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||
s := &stageBuilder{
|
||||
stage: tt.fields.stage,
|
||||
opts: tt.fields.opts,
|
||||
cmds: tt.fields.cmds,
|
||||
}
|
||||
if got := s.shouldTakeSnapshot(tt.args.index, tt.args.files); got != tt.want {
|
||||
t.Errorf("stageBuilder.shouldTakeSnapshot() = %v, want %v", got, tt.want)
|
||||
@@ -194,7 +186,8 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||
|
||||
func TestCalculateDependencies(t *testing.T) {
|
||||
type args struct {
|
||||
dockerfile string
|
||||
dockerfile string
|
||||
mockInitConfig func(partial.WithConfigFile, *config.KanikoOptions) (*v1.ConfigFile, error)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -314,16 +307,58 @@ COPY --from=stage2 /bar /bat
|
||||
1: {"/bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one image has onbuild config",
|
||||
args: args{
|
||||
mockInitConfig: func(img partial.WithConfigFile, opts *config.KanikoOptions) (*v1.ConfigFile, error) {
|
||||
cfg, err := img.ConfigFile()
|
||||
// if image is "alpine" then add ONBUILD to its config
|
||||
if cfg != nil && cfg.Architecture != "" {
|
||||
cfg.Config.OnBuild = []string{"COPY --from=builder /app /app"}
|
||||
}
|
||||
return cfg, err
|
||||
},
|
||||
dockerfile: `
|
||||
FROM scratch as builder
|
||||
RUN foo
|
||||
FROM alpine as second
|
||||
# This image has an ONBUILD command so it will be executed
|
||||
COPY --from=builder /foo /bar
|
||||
FROM scratch as target
|
||||
COPY --from=second /bar /bat
|
||||
`,
|
||||
},
|
||||
want: map[int][]string{
|
||||
0: {"/app", "/foo"},
|
||||
1: {"/bar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.mockInitConfig != nil {
|
||||
original := initializeConfig
|
||||
defer func() { initializeConfig = original }()
|
||||
initializeConfig = tt.args.mockInitConfig
|
||||
}
|
||||
|
||||
f, _ := ioutil.TempFile("", "")
|
||||
ioutil.WriteFile(f.Name(), []byte(tt.args.dockerfile), 0755)
|
||||
opts := &config.KanikoOptions{
|
||||
DockerfilePath: f.Name(),
|
||||
}
|
||||
testStages, metaArgs, err := dockerfile.ParseStages(opts)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse test dockerfile to stages: %s", err)
|
||||
}
|
||||
|
||||
got, err := CalculateDependencies(opts)
|
||||
stageNameToIdx := ResolveCrossStageInstructions(testStages)
|
||||
kanikoStages, err := dockerfile.MakeKanikoStages(opts, testStages, metaArgs)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse stages to Kaniko Stages: %s", err)
|
||||
}
|
||||
|
||||
got, err := CalculateDependencies(kanikoStages, opts, stageNameToIdx)
|
||||
if err != nil {
|
||||
t.Errorf("got error: %s,", err)
|
||||
}
|
||||
@@ -371,10 +406,15 @@ func Test_filesToSave(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "")
|
||||
original := config.RootDir
|
||||
config.RootDir = tmpDir
|
||||
if err != nil {
|
||||
t.Errorf("error creating tmpdir: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
defer func() {
|
||||
config.RootDir = original
|
||||
os.RemoveAll(tmpDir)
|
||||
}()
|
||||
|
||||
for _, f := range tt.files {
|
||||
p := filepath.Join(tmpDir, f)
|
||||
@@ -391,22 +431,14 @@ func Test_filesToSave(t *testing.T) {
|
||||
fp.Close()
|
||||
}
|
||||
|
||||
args := []string{}
|
||||
for _, arg := range tt.args {
|
||||
args = append(args, filepath.Join(tmpDir, arg))
|
||||
}
|
||||
got, err := filesToSave(args)
|
||||
got, err := filesToSave(tt.args)
|
||||
if err != nil {
|
||||
t.Errorf("got err: %s", err)
|
||||
}
|
||||
want := []string{}
|
||||
for _, w := range tt.want {
|
||||
want = append(want, filepath.Join(tmpDir, w))
|
||||
}
|
||||
sort.Strings(want)
|
||||
sort.Strings(tt.want)
|
||||
sort.Strings(got)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("filesToSave() = %v, want %v", got, want)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("filesToSave() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -870,12 +902,16 @@ COPY %s bar.txt
|
||||
DockerfilePath: f.Name(),
|
||||
}
|
||||
|
||||
stages, err := dockerfile.Stages(opts)
|
||||
testStages, metaArgs, err := dockerfile.ParseStages(opts)
|
||||
if err != nil {
|
||||
t.Errorf("could not parse test dockerfile")
|
||||
t.Errorf("Failed to parse test dockerfile to stages: %s", err)
|
||||
}
|
||||
|
||||
stage := stages[0]
|
||||
_ = ResolveCrossStageInstructions(testStages)
|
||||
kanikoStages, err := dockerfile.MakeKanikoStages(opts, testStages, metaArgs)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse stages to Kaniko Stages: %s", err)
|
||||
}
|
||||
stage := kanikoStages[0]
|
||||
|
||||
cmds := stage.Commands
|
||||
return testcase{
|
||||
@@ -941,12 +977,17 @@ COPY %s bar.txt
|
||||
DockerfilePath: f.Name(),
|
||||
}
|
||||
|
||||
stages, err := dockerfile.Stages(opts)
|
||||
testStages, metaArgs, err := dockerfile.ParseStages(opts)
|
||||
if err != nil {
|
||||
t.Errorf("could not parse test dockerfile")
|
||||
t.Errorf("Failed to parse test dockerfile to stages: %s", err)
|
||||
}
|
||||
_ = ResolveCrossStageInstructions(testStages)
|
||||
kanikoStages, err := dockerfile.MakeKanikoStages(opts, testStages, metaArgs)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse stages to Kaniko Stages: %s", err)
|
||||
}
|
||||
|
||||
stage := stages[0]
|
||||
stage := kanikoStages[0]
|
||||
|
||||
cmds := stage.Commands
|
||||
return testcase{
|
||||
@@ -1129,9 +1170,9 @@ COPY %s bar.txt
|
||||
for key, value := range tc.args {
|
||||
sb.args.AddArg(key, &value)
|
||||
}
|
||||
tmp := commands.RootDir
|
||||
tmp := config.RootDir
|
||||
if tc.rootDir != "" {
|
||||
commands.RootDir = tc.rootDir
|
||||
config.RootDir = tc.rootDir
|
||||
}
|
||||
err := sb.build()
|
||||
if err != nil {
|
||||
@@ -1141,7 +1182,7 @@ COPY %s bar.txt
|
||||
assertCacheKeys(t, tc.expectedCacheKeys, lc.receivedKeys, "receive")
|
||||
assertCacheKeys(t, tc.pushedCacheKeys, keys, "push")
|
||||
|
||||
commands.RootDir = tmp
|
||||
config.RootDir = tmp
|
||||
|
||||
})
|
||||
}
|
||||
@@ -1247,3 +1288,42 @@ func hashCompositeKeys(t *testing.T, ck1 CompositeCache, ck2 CompositeCache) (st
|
||||
}
|
||||
return key1, key2
|
||||
}
|
||||
|
||||
func Test_ResolveCrossStageInstructions(t *testing.T) {
|
||||
df := `
|
||||
FROM scratch
|
||||
RUN echo hi > /hi
|
||||
|
||||
FROM scratch AS second
|
||||
COPY --from=0 /hi /hi2
|
||||
|
||||
FROM scratch AS tHiRd
|
||||
COPY --from=second /hi2 /hi3
|
||||
COPY --from=1 /hi2 /hi3
|
||||
|
||||
FROM scratch
|
||||
COPY --from=thIrD /hi3 /hi4
|
||||
COPY --from=third /hi3 /hi4
|
||||
COPY --from=2 /hi3 /hi4
|
||||
`
|
||||
stages, _, err := dockerfile.Parse([]byte(df))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stageToIdx := ResolveCrossStageInstructions(stages)
|
||||
for index, stage := range stages {
|
||||
if index == 0 {
|
||||
continue
|
||||
}
|
||||
expectedStage := strconv.Itoa(index - 1)
|
||||
for _, command := range stage.Commands {
|
||||
copyCmd := command.(*instructions.CopyCommand)
|
||||
if copyCmd.From != expectedStage {
|
||||
t.Fatalf("unexpected copy command: %s resolved to stage %s, expected %s", copyCmd.String(), copyCmd.From, expectedStage)
|
||||
}
|
||||
}
|
||||
|
||||
expectedMap := map[string]string{"second": "1", "third": "2"}
|
||||
testutil.CheckDeepEqual(t, expectedMap, stageToIdx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
Copyright 2020 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 executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
)
|
||||
|
||||
func TestCopyCommand_Multistage(t *testing.T) {
|
||||
t.Run("copy a file across multistage", func(t *testing.T) {
|
||||
testDir, fn := setupMultistageTests(t)
|
||||
defer fn()
|
||||
dockerFile := fmt.Sprintf(`
|
||||
FROM scratch as first
|
||||
COPY foo/bam.txt copied/
|
||||
ENV test test
|
||||
|
||||
From scratch as second
|
||||
COPY --from=first copied/bam.txt output/bam.txt`)
|
||||
ioutil.WriteFile(filepath.Join(testDir, "workspace", "Dockerfile"), []byte(dockerFile), 0755)
|
||||
opts := &config.KanikoOptions{
|
||||
DockerfilePath: filepath.Join(testDir, "workspace", "Dockerfile"),
|
||||
SrcContext: filepath.Join(testDir, "workspace"),
|
||||
SnapshotMode: constants.SnapshotModeFull,
|
||||
}
|
||||
_, err := DoBuild(opts)
|
||||
testutil.CheckNoError(t, err)
|
||||
// Check Image has one layer bam.txt
|
||||
files, err := ioutil.ReadDir(filepath.Join(testDir, "output"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testutil.CheckDeepEqual(t, 1, len(files))
|
||||
testutil.CheckDeepEqual(t, files[0].Name(), "bam.txt")
|
||||
|
||||
})
|
||||
|
||||
t.Run("copy a file across multistage into a directory", func(t *testing.T) {
|
||||
testDir, fn := setupMultistageTests(t)
|
||||
defer fn()
|
||||
dockerFile := fmt.Sprintf(`
|
||||
FROM scratch as first
|
||||
COPY foo/bam.txt copied/
|
||||
ENV test test
|
||||
|
||||
From scratch as second
|
||||
COPY --from=first copied/bam.txt output/`)
|
||||
ioutil.WriteFile(filepath.Join(testDir, "workspace", "Dockerfile"), []byte(dockerFile), 0755)
|
||||
opts := &config.KanikoOptions{
|
||||
DockerfilePath: filepath.Join(testDir, "workspace", "Dockerfile"),
|
||||
SrcContext: filepath.Join(testDir, "workspace"),
|
||||
SnapshotMode: constants.SnapshotModeFull,
|
||||
}
|
||||
_, err := DoBuild(opts)
|
||||
testutil.CheckNoError(t, err)
|
||||
files, err := ioutil.ReadDir(filepath.Join(testDir, "output"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testutil.CheckDeepEqual(t, 1, len(files))
|
||||
testutil.CheckDeepEqual(t, files[0].Name(), "bam.txt")
|
||||
})
|
||||
t.Run("copy directory across multistage into a directory", func(t *testing.T) {
|
||||
testDir, fn := setupMultistageTests(t)
|
||||
defer fn()
|
||||
dockerFile := fmt.Sprintf(`
|
||||
FROM scratch as first
|
||||
COPY foo copied
|
||||
ENV test test
|
||||
|
||||
From scratch as second
|
||||
COPY --from=first copied another`)
|
||||
ioutil.WriteFile(filepath.Join(testDir, "workspace", "Dockerfile"), []byte(dockerFile), 0755)
|
||||
opts := &config.KanikoOptions{
|
||||
DockerfilePath: filepath.Join(testDir, "workspace", "Dockerfile"),
|
||||
SrcContext: filepath.Join(testDir, "workspace"),
|
||||
SnapshotMode: constants.SnapshotModeFull,
|
||||
}
|
||||
_, err := DoBuild(opts)
|
||||
testutil.CheckNoError(t, err)
|
||||
// Check Image has one layer bam.txt
|
||||
files, err := ioutil.ReadDir(filepath.Join(testDir, "another"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testutil.CheckDeepEqual(t, 2, len(files))
|
||||
testutil.CheckDeepEqual(t, files[0].Name(), "bam.link")
|
||||
testutil.CheckDeepEqual(t, files[1].Name(), "bam.txt")
|
||||
// TODO fix this
|
||||
// path := filepath.Join(testDir, "output/another", "bam.link")
|
||||
//linkName, err := os.Readlink(path)
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//testutil.CheckDeepEqual(t, linkName, "bam.txt")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func setupMultistageTests(t *testing.T) (string, func()) {
|
||||
testDir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create workspace with files, dirs, and symlinks
|
||||
// workspace tree:
|
||||
// /root
|
||||
// /kaniko
|
||||
// /workspace
|
||||
// - /foo
|
||||
// - bam.txt
|
||||
// - bam.link -> bam.txt
|
||||
// - /bin
|
||||
// - exec.link -> ../exec
|
||||
// exec
|
||||
|
||||
// Make directory for stage or else the executor will create with permissions 0664
|
||||
// and we will run into issue https://github.com/golang/go/issues/22323
|
||||
if err := os.MkdirAll(filepath.Join(testDir, "kaniko/0"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
workspace := filepath.Join(testDir, "workspace")
|
||||
// Make foo
|
||||
if err := os.MkdirAll(filepath.Join(workspace, "foo"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file := filepath.Join(workspace, "foo", "bam.txt")
|
||||
if err := ioutil.WriteFile(file, []byte("meow"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Symlink("bam.txt", filepath.Join(workspace, "foo", "bam.link"))
|
||||
|
||||
// Make a file with contents link
|
||||
file = filepath.Join(workspace, "exec")
|
||||
if err := ioutil.WriteFile(file, []byte("woof"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Make bin
|
||||
if err := os.MkdirAll(filepath.Join(workspace, "bin"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Symlink("../exec", filepath.Join(workspace, "bin", "exec.link"))
|
||||
|
||||
// set up config
|
||||
config.RootDir = testDir
|
||||
config.KanikoDir = fmt.Sprintf("%s/%s", testDir, "kaniko")
|
||||
// Write a whitelist path
|
||||
if err := os.MkdirAll(filepath.Join(testDir, "proc"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mFile := filepath.Join(testDir, "proc/mountinfo")
|
||||
mountInfo := fmt.Sprintf(
|
||||
`36 35 98:0 /kaniko %s/kaniko rw,noatime master:1 - ext3 /dev/root rw,errors=continue
|
||||
36 35 98:0 /proc %s/proc rw,noatime master:1 - ext3 /dev/root rw,errors=continue
|
||||
`, testDir, testDir)
|
||||
if err := ioutil.WriteFile(mFile, []byte(mountInfo), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config.WhitelistPath = mFile
|
||||
return testDir, func() {
|
||||
config.KanikoDir = constants.KanikoDir
|
||||
config.RootDir = constants.RootDir
|
||||
config.WhitelistPath = constants.WhitelistPath
|
||||
}
|
||||
}
|
||||
+22
-3
@@ -24,6 +24,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -52,6 +53,7 @@ type withUserAgent struct {
|
||||
|
||||
const (
|
||||
UpstreamClientUaKey = "UPSTREAM_CLIENT_TYPE"
|
||||
DockerConfLocation = "/kaniko/.docker/config.json"
|
||||
)
|
||||
|
||||
func (w *withUserAgent) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
@@ -98,6 +100,13 @@ var defaultX509Handler systemCertLoader = func() CertPool {
|
||||
}
|
||||
}
|
||||
|
||||
// for testing
|
||||
var (
|
||||
fs = afero.NewOsFs()
|
||||
execCommand = exec.Command
|
||||
checkRemotePushPermission = remote.CheckPushPermission
|
||||
)
|
||||
|
||||
// CheckPushPermissions checks that the configured credentials can be used to
|
||||
// push to every specified destination.
|
||||
func CheckPushPermissions(opts *config.KanikoOptions) error {
|
||||
@@ -115,6 +124,18 @@ func CheckPushPermissions(opts *config.KanikoOptions) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Historically kaniko was pre-configured by default with gcr credential helper,
|
||||
// in here we keep the backwards compatibility by enabling the GCR helper only
|
||||
// when gcr.io is in one of the destinations.
|
||||
if strings.Contains(destRef.RegistryStr(), "gcr.io") {
|
||||
// Checking for existence of docker.config as it's normally required for
|
||||
// authenticated registries and prevent overwriting user provided docker conf
|
||||
if _, err := fs.Stat(DockerConfLocation); os.IsNotExist(err) {
|
||||
if err := execCommand("docker-credential-gcr", "configure-docker").Run(); err != nil {
|
||||
return errors.Wrap(err, "error while configuring docker-credential-gcr helper")
|
||||
}
|
||||
}
|
||||
}
|
||||
registryName := destRef.Repository.Registry.Name()
|
||||
if opts.Insecure || opts.InsecureRegistries.Contains(registryName) {
|
||||
newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure)
|
||||
@@ -124,7 +145,7 @@ func CheckPushPermissions(opts *config.KanikoOptions) error {
|
||||
destRef.Repository.Registry = newReg
|
||||
}
|
||||
tr := makeTransport(opts, registryName, defaultX509Handler)
|
||||
if err := remote.CheckPushPermission(destRef, creds.GetKeychain(), tr); err != nil {
|
||||
if err := checkRemotePushPermission(destRef, creds.GetKeychain(), tr); err != nil {
|
||||
return errors.Wrapf(err, "checking push permission for %q", destRef)
|
||||
}
|
||||
checked[destRef.Context().RepositoryStr()] = true
|
||||
@@ -231,8 +252,6 @@ func DoPush(image v1.Image, opts *config.KanikoOptions) error {
|
||||
return writeImageOutputs(image, destRefs)
|
||||
}
|
||||
|
||||
var fs = afero.NewOsFs()
|
||||
|
||||
func writeImageOutputs(image v1.Image, destRefs []name.Tag) error {
|
||||
dir := os.Getenv("BUILDER_OUTPUT")
|
||||
if dir == "" {
|
||||
|
||||
@@ -24,11 +24,13 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/google/go-containerregistry/pkg/v1/random"
|
||||
@@ -307,3 +309,68 @@ func Test_makeTransport(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var calledExecCommand = false
|
||||
var calledCheckPushPermission = false
|
||||
|
||||
func setCalledFalse() {
|
||||
calledExecCommand = false
|
||||
calledCheckPushPermission = false
|
||||
}
|
||||
|
||||
func fakeExecCommand(command string, args ...string) *exec.Cmd {
|
||||
calledExecCommand = true
|
||||
cs := []string{"-test.run=TestHelperProcess", "--", command}
|
||||
cs = append(cs, args...)
|
||||
cmd := exec.Command(os.Args[0], cs...)
|
||||
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func fakeCheckPushPermission(ref name.Reference, kc authn.Keychain, t http.RoundTripper) error {
|
||||
calledCheckPushPermission = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCheckPushPermissions(t *testing.T) {
|
||||
tests := []struct {
|
||||
Destination string
|
||||
ShouldCallExecCommand bool
|
||||
ExistingConfig bool
|
||||
}{
|
||||
{"gcr.io/test-image", true, false},
|
||||
{"gcr.io/test-image", false, true},
|
||||
{"localhost:5000/test-image", false, false},
|
||||
{"localhost:5000/test-image", false, true},
|
||||
}
|
||||
|
||||
execCommand = fakeExecCommand
|
||||
checkRemotePushPermission = fakeCheckPushPermission
|
||||
for _, test := range tests {
|
||||
testName := fmt.Sprintf("%s_ExistingDockerConf_%v", test.Destination, test.ExistingConfig)
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
fs = afero.NewMemMapFs()
|
||||
opts := config.KanikoOptions{
|
||||
Destinations: []string{test.Destination},
|
||||
}
|
||||
if test.ExistingConfig {
|
||||
afero.WriteFile(fs, DockerConfLocation, []byte(""), os.FileMode(0644))
|
||||
defer fs.Remove(DockerConfLocation)
|
||||
}
|
||||
CheckPushPermissions(&opts)
|
||||
if test.ShouldCallExecCommand != calledExecCommand {
|
||||
t.Errorf("Expected calledExecCommand to be %v however it was %v",
|
||||
calledExecCommand, test.ShouldCallExecCommand)
|
||||
}
|
||||
setCalledFalse()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperProcess(t *testing.T) {
|
||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "fake result")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -34,8 +35,8 @@ import (
|
||||
// output set.
|
||||
// * Add all ancestors of each path to the output set.
|
||||
func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string, err error) {
|
||||
logrus.Info("Resolving paths")
|
||||
logrus.Debugf("Resolving paths %s", paths)
|
||||
logrus.Infof("Resolving %d paths", len(paths))
|
||||
logrus.Tracef("Resolving paths %s", paths)
|
||||
|
||||
fileSet := make(map[string]bool)
|
||||
|
||||
@@ -72,6 +73,7 @@ func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string
|
||||
}
|
||||
|
||||
logrus.Debugf("symlink path %s, target does not exist", f)
|
||||
continue
|
||||
}
|
||||
|
||||
// If the given path is a symlink and the target is part of the whitelist
|
||||
@@ -89,7 +91,6 @@ func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string
|
||||
|
||||
// Also add parent directories to keep the permission of them correctly.
|
||||
pathsToAdd = filesWithParentDirs(pathsToAdd)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -130,7 +131,7 @@ func resolveSymlinkAncestor(path string) (string, error) {
|
||||
newPath := filepath.Clean(path)
|
||||
|
||||
loop:
|
||||
for newPath != "/" {
|
||||
for newPath != config.RootDir {
|
||||
fi, err := os.Lstat(newPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "resolvePaths: failed to lstat")
|
||||
|
||||
@@ -28,14 +28,14 @@ import (
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
||||
"github.com/karrick/godirwalk"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// For testing
|
||||
var snapshotPathPrefix = constants.KanikoDir
|
||||
var snapshotPathPrefix = config.KanikoDir
|
||||
|
||||
// Snapshotter holds the root directory from which to take snapshots, and a list of snapshots taken
|
||||
type Snapshotter struct {
|
||||
@@ -63,7 +63,7 @@ func (s *Snapshotter) Key() (string, error) {
|
||||
// TakeSnapshot takes a snapshot of the specified files, avoiding directories in the whitelist, and creates
|
||||
// a tarball of the changed files. Return contents of the tarball, and whether or not any files were changed
|
||||
func (s *Snapshotter) TakeSnapshot(files []string) (string, error) {
|
||||
f, err := ioutil.TempFile(snapshotPathPrefix, "")
|
||||
f, err := ioutil.TempFile(config.KanikoDir, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -226,10 +226,28 @@ func writeToTar(t util.Tar, files, whiteouts []string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
addedPaths := make(map[string]bool)
|
||||
for _, path := range files {
|
||||
if _, fileExists := addedPaths[path]; fileExists {
|
||||
continue
|
||||
}
|
||||
for _, parentPath := range util.ParentDirectories(path) {
|
||||
if parentPath == "/" {
|
||||
continue
|
||||
}
|
||||
if _, dirExists := addedPaths[parentPath]; dirExists {
|
||||
continue
|
||||
}
|
||||
if err := t.AddFileToTar(parentPath); err != nil {
|
||||
return err
|
||||
}
|
||||
addedPaths[parentPath] = true
|
||||
}
|
||||
if err := t.AddFileToTar(path); err != nil {
|
||||
return err
|
||||
}
|
||||
addedPaths[path] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/pkg/errors"
|
||||
@@ -63,6 +64,12 @@ func TestSnapshotFSFileChange(t *testing.T) {
|
||||
fooPath: "newbaz1",
|
||||
batPath: "baz",
|
||||
}
|
||||
for _, path := range util.ParentDirectoriesWithoutLeadingSlash(batPath) {
|
||||
if path == "/" {
|
||||
continue
|
||||
}
|
||||
snapshotFiles[path+"/"] = ""
|
||||
}
|
||||
|
||||
actualFiles := []string{}
|
||||
for {
|
||||
@@ -76,6 +83,9 @@ func TestSnapshotFSFileChange(t *testing.T) {
|
||||
if _, isFile := snapshotFiles[hdr.Name]; !isFile {
|
||||
t.Fatalf("File %s unexpectedly in tar", hdr.Name)
|
||||
}
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
continue
|
||||
}
|
||||
contents, _ := ioutil.ReadAll(tr)
|
||||
if string(contents) != snapshotFiles[hdr.Name] {
|
||||
t.Fatalf("Contents of %s incorrect, expected: %s, actual: %s", hdr.Name, snapshotFiles[hdr.Name], string(contents))
|
||||
@@ -152,6 +162,12 @@ func TestSnapshotFSChangePermissions(t *testing.T) {
|
||||
snapshotFiles := map[string]string{
|
||||
batPathWithoutLeadingSlash: "baz2",
|
||||
}
|
||||
for _, path := range util.ParentDirectoriesWithoutLeadingSlash(batPathWithoutLeadingSlash) {
|
||||
if path == "/" {
|
||||
continue
|
||||
}
|
||||
snapshotFiles[path+"/"] = ""
|
||||
}
|
||||
|
||||
foundFiles := []string{}
|
||||
for {
|
||||
@@ -159,11 +175,13 @@ func TestSnapshotFSChangePermissions(t *testing.T) {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Logf("Info %s in tar", hdr.Name)
|
||||
foundFiles = append(foundFiles, hdr.Name)
|
||||
if _, isFile := snapshotFiles[hdr.Name]; !isFile {
|
||||
t.Fatalf("File %s unexpectedly in tar", hdr.Name)
|
||||
}
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
continue
|
||||
}
|
||||
contents, _ := ioutil.ReadAll(tr)
|
||||
if string(contents) != snapshotFiles[hdr.Name] {
|
||||
t.Fatalf("Contents of %s incorrect, expected: %s, actual: %s", hdr.Name, snapshotFiles[hdr.Name], string(contents))
|
||||
@@ -203,7 +221,9 @@ func TestSnapshotFiles(t *testing.T) {
|
||||
expectedFiles := []string{
|
||||
filepath.Join(testDirWithoutLeadingSlash, "foo"),
|
||||
}
|
||||
expectedFiles = append(expectedFiles, util.ParentDirectoriesWithoutLeadingSlash(filepath.Join(testDir, "foo"))...)
|
||||
for _, path := range util.ParentDirectoriesWithoutLeadingSlash(filepath.Join(testDir, "foo")) {
|
||||
expectedFiles = append(expectedFiles, strings.TrimRight(path, "/")+"/")
|
||||
}
|
||||
|
||||
f, err := os.Open(tarPath)
|
||||
if err != nil {
|
||||
@@ -458,8 +478,11 @@ func setUpTest() (string, *Snapshotter, func(), error) {
|
||||
return "", nil, nil, errors.Wrap(err, "initializing snapshotter")
|
||||
}
|
||||
|
||||
original := config.KanikoDir
|
||||
config.KanikoDir = testDir
|
||||
cleanup := func() {
|
||||
os.RemoveAll(snapshotPath)
|
||||
config.KanikoDir = original
|
||||
dirCleanUp()
|
||||
}
|
||||
|
||||
|
||||
@@ -26,13 +26,14 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/shell"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
)
|
||||
|
||||
// for testing
|
||||
@@ -130,7 +131,6 @@ func ResolveSources(srcs []string, root string) ([]string, error) {
|
||||
return nil, errors.Wrap(err, "matching sources")
|
||||
}
|
||||
logrus.Debugf("Resolved sources to %v", resolved)
|
||||
fmt.Println("end of resolve sources")
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ func matchSources(srcs, files []string) ([]string, error) {
|
||||
src = filepath.Clean(src)
|
||||
for _, file := range files {
|
||||
if filepath.IsAbs(src) {
|
||||
file = filepath.Join(constants.RootDir, file)
|
||||
file = filepath.Join(config.RootDir, file)
|
||||
}
|
||||
matched, err := filepath.Match(src, file)
|
||||
if err != nil {
|
||||
@@ -290,8 +290,9 @@ func IsSrcRemoteFileURL(rawurl string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func UpdateConfigEnv(newEnvs []instructions.KeyValuePair, config *v1.Config, replacementEnvs []string) error {
|
||||
for index, pair := range newEnvs {
|
||||
func UpdateConfigEnv(envVars []instructions.KeyValuePair, config *v1.Config, replacementEnvs []string) error {
|
||||
newEnvs := make([]instructions.KeyValuePair, len(envVars))
|
||||
for index, pair := range envVars {
|
||||
expandedKey, err := ResolveEnvironmentReplacement(pair.Key, replacementEnvs, false)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
)
|
||||
|
||||
var testURL = "https://github.com/GoogleContainerTools/runtimes-common/blob/master/LICENSE"
|
||||
@@ -275,6 +277,73 @@ func Test_MatchSources(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var updateConfigEnvTests = []struct {
|
||||
name string
|
||||
envVars []instructions.KeyValuePair
|
||||
config *v1.Config
|
||||
replacementEnvs []string
|
||||
expectedEnv []string
|
||||
}{
|
||||
{
|
||||
name: "test env config update",
|
||||
envVars: []instructions.KeyValuePair{
|
||||
{
|
||||
Key: "key",
|
||||
Value: "var",
|
||||
},
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "baz",
|
||||
}},
|
||||
config: &v1.Config{},
|
||||
replacementEnvs: []string{},
|
||||
expectedEnv: []string{"key=var", "foo=baz"},
|
||||
}, {
|
||||
name: "test env config update with replacmenets",
|
||||
envVars: []instructions.KeyValuePair{
|
||||
{
|
||||
Key: "key",
|
||||
Value: "/var/run",
|
||||
},
|
||||
{
|
||||
Key: "env",
|
||||
Value: "$var",
|
||||
},
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "$argarg",
|
||||
}},
|
||||
config: &v1.Config{},
|
||||
replacementEnvs: []string{"var=/test/with'chars'/", "not=used", "argarg=\"a\"b\""},
|
||||
expectedEnv: []string{"key=/var/run", "env=/test/with'chars'/", "foo=\"a\"b\""},
|
||||
}, {
|
||||
name: "test env config update replacing existing variable",
|
||||
envVars: []instructions.KeyValuePair{
|
||||
{
|
||||
Key: "alice",
|
||||
Value: "nice",
|
||||
},
|
||||
{
|
||||
Key: "bob",
|
||||
Value: "cool",
|
||||
}},
|
||||
config: &v1.Config{Env: []string{"bob=used", "more=test"}},
|
||||
replacementEnvs: []string{},
|
||||
expectedEnv: []string{"bob=cool", "more=test", "alice=nice"},
|
||||
},
|
||||
}
|
||||
|
||||
func Test_UpdateConfigEnvTests(t *testing.T) {
|
||||
for _, test := range updateConfigEnvTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if err := UpdateConfigEnv(test.envVars, test.config, test.replacementEnvs); err != nil {
|
||||
t.Fatalf("error updating config with env vars: %s", err)
|
||||
}
|
||||
testutil.CheckDeepEqual(t, test.expectedEnv, test.config.Env)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var isSrcValidTests = []struct {
|
||||
name string
|
||||
srcsAndDest []string
|
||||
|
||||
+28
-28
@@ -33,7 +33,7 @@ import (
|
||||
|
||||
otiai10Cpy "github.com/otiai10/copy"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/docker/docker/builder/dockerignore"
|
||||
"github.com/docker/docker/pkg/fileutils"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
@@ -51,7 +51,7 @@ type WhitelistEntry struct {
|
||||
|
||||
var initialWhitelist = []WhitelistEntry{
|
||||
{
|
||||
Path: "/kaniko",
|
||||
Path: config.KanikoDir,
|
||||
PrefixMatchOnly: false,
|
||||
},
|
||||
{
|
||||
@@ -125,7 +125,7 @@ func GetFSFromLayers(root string, layers []v1.Layer, opts ...FSOpt) ([]string, e
|
||||
return nil, errors.New("must supply an extract function")
|
||||
}
|
||||
|
||||
if err := DetectFilesystemWhitelist(constants.WhitelistPath); err != nil {
|
||||
if err := DetectFilesystemWhitelist(config.WhitelistPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -161,18 +161,15 @@ func GetFSFromLayers(root string, layers []v1.Layer, opts ...FSOpt) ([]string, e
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
if strings.HasPrefix(base, ".wh.") {
|
||||
logrus.Debugf("Whiting out %s", path)
|
||||
|
||||
name := strings.TrimPrefix(base, ".wh.")
|
||||
if err := os.RemoveAll(filepath.Join(dir, name)); err != nil {
|
||||
return nil, errors.Wrapf(err, "removing whiteout %s", hdr.Name)
|
||||
}
|
||||
|
||||
if !cfg.includeWhiteout {
|
||||
logrus.Debug("not including whiteout files")
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Debugf("Whiting out %s", path)
|
||||
name := strings.TrimPrefix(base, ".wh.")
|
||||
if err := os.RemoveAll(filepath.Join(dir, name)); err != nil {
|
||||
return nil, errors.Wrapf(err, "removing whiteout %s", hdr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if err := cfg.extractFunc(root, hdr, tr); err != nil {
|
||||
@@ -188,7 +185,7 @@ func GetFSFromLayers(root string, layers []v1.Layer, opts ...FSOpt) ([]string, e
|
||||
// DeleteFilesystem deletes the extracted image file system
|
||||
func DeleteFilesystem() error {
|
||||
logrus.Info("Deleting filesystem...")
|
||||
return filepath.Walk(constants.RootDir, func(path string, info os.FileInfo, err error) error {
|
||||
return filepath.Walk(config.RootDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
// ignore errors when deleting.
|
||||
return nil
|
||||
@@ -209,7 +206,7 @@ func DeleteFilesystem() error {
|
||||
logrus.Debugf("Not deleting %s, as it contains a whitelisted path", path)
|
||||
return nil
|
||||
}
|
||||
if path == constants.RootDir {
|
||||
if path == config.RootDir {
|
||||
return nil
|
||||
}
|
||||
return os.RemoveAll(path)
|
||||
@@ -388,7 +385,7 @@ func CheckWhitelist(path string) bool {
|
||||
}
|
||||
|
||||
func checkWhitelistRoot(root string) bool {
|
||||
if root == constants.RootDir {
|
||||
if root == config.RootDir {
|
||||
return false
|
||||
}
|
||||
return CheckWhitelist(root)
|
||||
@@ -423,7 +420,7 @@ func DetectFilesystemWhitelist(path string) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if lineArr[4] != constants.RootDir {
|
||||
if lineArr[4] != config.RootDir {
|
||||
logrus.Tracef("Appending %s from line: %s", lineArr[4], line)
|
||||
whitelist = append(whitelist, WhitelistEntry{
|
||||
Path: lineArr[4],
|
||||
@@ -463,16 +460,18 @@ func RelativeFiles(fp string, root string) ([]string, error) {
|
||||
// ParentDirectories returns a list of paths to all parent directories
|
||||
// Ex. /some/temp/dir -> [/, /some, /some/temp, /some/temp/dir]
|
||||
func ParentDirectories(path string) []string {
|
||||
path = filepath.Clean(path)
|
||||
dirs := strings.Split(path, "/")
|
||||
dirPath := constants.RootDir
|
||||
paths := []string{constants.RootDir}
|
||||
for index, dir := range dirs {
|
||||
if dir == "" || index == (len(dirs)-1) {
|
||||
continue
|
||||
dir := filepath.Clean(path)
|
||||
var paths []string
|
||||
for {
|
||||
if dir == filepath.Clean(config.RootDir) || dir == "" || dir == "." {
|
||||
break
|
||||
}
|
||||
dirPath = filepath.Join(dirPath, dir)
|
||||
paths = append(paths, dirPath)
|
||||
dir, _ = filepath.Split(dir)
|
||||
dir = filepath.Clean(dir)
|
||||
paths = append([]string{dir}, paths...)
|
||||
}
|
||||
if len(paths) == 0 {
|
||||
paths = []string{config.RootDir}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
@@ -484,7 +483,7 @@ func ParentDirectoriesWithoutLeadingSlash(path string) []string {
|
||||
path = filepath.Clean(path)
|
||||
dirs := strings.Split(path, "/")
|
||||
dirPath := ""
|
||||
paths := []string{constants.RootDir}
|
||||
paths := []string{config.RootDir}
|
||||
for index, dir := range dirs {
|
||||
if dir == "" || index == (len(dirs)-1) {
|
||||
continue
|
||||
@@ -728,7 +727,7 @@ func mkdirAllWithPermissions(path string, mode os.FileMode, uid, gid int64) erro
|
||||
}
|
||||
if uid > math.MaxUint32 || gid > math.MaxUint32 {
|
||||
// due to https://github.com/golang/go/issues/8537
|
||||
return errors.New(fmt.Sprintf("Numeric User-ID or Group-ID greater than %v are not properly supported.", math.MaxUint32))
|
||||
return errors.New(fmt.Sprintf("Numeric User-ID or Group-ID greater than %v are not properly supported.", uint64(math.MaxUint32)))
|
||||
}
|
||||
if err := os.Chown(path, int(uid), int(gid)); err != nil {
|
||||
return err
|
||||
@@ -824,12 +823,13 @@ func getSymlink(path string) error {
|
||||
// For cross stage dependencies kaniko must persist the referenced path so that it can be used in
|
||||
// the dependent stage. For symlinks we copy the target path because copying the symlink would
|
||||
// result in a dead link
|
||||
func CopyFileOrSymlink(src string, destDir string) error {
|
||||
func CopyFileOrSymlink(src string, destDir string, root string) error {
|
||||
destFile := filepath.Join(destDir, src)
|
||||
src = filepath.Join(root, src)
|
||||
if fi, _ := os.Lstat(src); IsSymlink(fi) {
|
||||
link, err := os.Readlink(src)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "copying file or symlink")
|
||||
}
|
||||
if err := createParentDirectory(destFile); err != nil {
|
||||
return err
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/mocks/go-containerregistry/mockv1"
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/golang/mock/gomock"
|
||||
@@ -157,11 +158,13 @@ func Test_ParentDirectories(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
rootDir string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "regular path",
|
||||
path: "/path/to/dir",
|
||||
name: "regular path",
|
||||
path: "/path/to/dir",
|
||||
rootDir: "/",
|
||||
expected: []string{
|
||||
"/",
|
||||
"/path",
|
||||
@@ -169,17 +172,48 @@ func Test_ParentDirectories(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "current directory",
|
||||
path: ".",
|
||||
name: "current directory",
|
||||
path: ".",
|
||||
rootDir: "/",
|
||||
expected: []string{
|
||||
"/",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non / root directory",
|
||||
path: "/tmp/kaniko/test/another/dir",
|
||||
rootDir: "/tmp/kaniko/",
|
||||
expected: []string{
|
||||
"/tmp/kaniko",
|
||||
"/tmp/kaniko/test",
|
||||
"/tmp/kaniko/test/another",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non / root director same path",
|
||||
path: "/tmp/123",
|
||||
rootDir: "/tmp/123",
|
||||
expected: []string{
|
||||
"/tmp/123",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non / root directory path",
|
||||
path: "/tmp/120162240/kaniko",
|
||||
rootDir: "/tmp/120162240",
|
||||
expected: []string{
|
||||
"/tmp/120162240",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
original := config.RootDir
|
||||
defer func() { config.RootDir = original }()
|
||||
config.RootDir = tt.rootDir
|
||||
actual := ParentDirectories(tt.path)
|
||||
|
||||
testutil.CheckErrorAndDeepEqual(t, false, nil, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
|
||||
+10
-6
@@ -28,6 +28,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -76,14 +77,17 @@ func (t *Tar) AddFileToTar(p string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if p != "/" {
|
||||
// Docker uses no leading / in the tarball
|
||||
hdr.Name = strings.TrimLeft(p, "/")
|
||||
} else {
|
||||
if p == config.RootDir {
|
||||
// allow entry for / to preserve permission changes etc. (currently ignored anyway by Docker runtime)
|
||||
hdr.Name = p
|
||||
hdr.Name = "/"
|
||||
} else {
|
||||
// Docker uses no leading / in the tarball
|
||||
hdr.Name = strings.TrimPrefix(p, config.RootDir)
|
||||
hdr.Name = strings.TrimLeft(hdr.Name, "/")
|
||||
}
|
||||
if hdr.Typeflag == tar.TypeDir && !strings.HasSuffix(hdr.Name, "/") {
|
||||
hdr.Name = hdr.Name + "/"
|
||||
}
|
||||
|
||||
// rootfs may not have been extracted when using cache, preventing uname/gname from resolving
|
||||
// this makes this layer unnecessarily differ from a cached layer which does contain this information
|
||||
hdr.Uname = ""
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -134,3 +135,12 @@ func currentPlatform() v1.Platform {
|
||||
Architecture: runtime.GOARCH,
|
||||
}
|
||||
}
|
||||
|
||||
// GetInputFrom returns Reader content
|
||||
func GetInputFrom(r io.Reader) ([]byte, error) {
|
||||
output, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
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 (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
)
|
||||
|
||||
func TestGetInputFrom(t *testing.T) {
|
||||
validInput := []byte("Valid\n")
|
||||
validReader := bufio.NewReader(bytes.NewReader((validInput)))
|
||||
validValue, err := GetInputFrom(validReader)
|
||||
testutil.CheckErrorAndDeepEqual(t, false, err, validInput, validValue)
|
||||
}
|
||||
Reference in New Issue
Block a user