Merge pull request #1155 from tejal29/fix_multi_stage_symlinks

Refactor Kaniko to test across multistages
This commit is contained in:
Tejal Desai
2020-03-30 10:21:51 -07:00
committed by GitHub
15 changed files with 378 additions and 78 deletions
+21 -11
View File
@@ -98,7 +98,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 {
@@ -299,7 +299,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")
}
@@ -308,7 +308,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")
}
@@ -525,7 +525,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:
@@ -641,20 +640,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))
}
}
@@ -666,14 +668,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)
}
}
@@ -729,7 +739,7 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e
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
}
+13 -16
View File
@@ -371,10 +371,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 +396,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)
}
})
}
@@ -1129,9 +1126,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 +1138,7 @@ COPY %s bar.txt
assertCacheKeys(t, tc.expectedCacheKeys, lc.receivedKeys, "receive")
assertCacheKeys(t, tc.pushedCacheKeys, keys, "push")
commands.RootDir = tmp
config.RootDir = tmp
})
}
+187
View File
@@ -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
}
}