Skip to content

Commit

Permalink
save and extract stage tarballs if there are dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
Priya Wadhwa committed Aug 1, 2018
1 parent ac258da commit cadab33
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 155 deletions.
5 changes: 4 additions & 1 deletion integration/dockerfiles/Dockerfile_test_multistage
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ FROM scratch as second
ENV foopath context/foo
COPY --from=0 $foopath context/b* /foo/

FROM second
COPY --from=base /context/foo /new/foo

FROM base
ARG file
COPY --from=second /foo $file
COPY --from=second /foo ${file}
49 changes: 7 additions & 42 deletions pkg/dockerfile/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,11 @@ import (
"bytes"
"fmt"
"io/ioutil"
"path/filepath"
"strconv"
"strings"

"github.com/GoogleContainerTools/kaniko/pkg/constants"
"github.com/GoogleContainerTools/kaniko/pkg/util"
"github.com/docker/docker/builder/dockerfile/instructions"
"github.com/docker/docker/builder/dockerfile/parser"
"github.com/sirupsen/logrus"
)

// Stages reads the Dockerfile, validates it's contents, and returns stages
Expand Down Expand Up @@ -114,54 +110,23 @@ func ParseCommands(cmdArray []string) ([]instructions.Command, error) {
return cmds, nil
}

// Dependencies returns a list of files in this stage that will be needed in later stages
func Dependencies(index int, stages []instructions.Stage, buildArgs *BuildArgs) ([]string, error) {
dependencies := []string{}
// SaveStage returns true if the current stage will be needed later in the Dockerfile
func SaveStage(index int, stages []instructions.Stage) bool {
for stageIndex, stage := range stages {
if stageIndex <= index {
continue
}
logrus.Info("retrieving source image!!")
sourceImage, err := util.RetrieveSourceImage(stageIndex, buildArgs.ReplacementEnvs(nil), stages)
if err != nil {
return nil, err
}
imageConfig, err := sourceImage.ConfigFile()
if err != nil {
return nil, err
if stage.Name == stages[index].BaseName {
return true
}
for _, cmd := range stage.Commands {
switch c := cmd.(type) {
case *instructions.EnvCommand:
replacementEnvs := buildArgs.ReplacementEnvs(imageConfig.Config.Env)
if err := util.UpdateConfigEnv(c.Env, &imageConfig.Config, replacementEnvs); err != nil {
return nil, err
}
case *instructions.ArgCommand:
buildArgs.AddArg(c.Key, c.Value)
case *instructions.CopyCommand:
if c.From != strconv.Itoa(index) {
continue
}
// First, resolve any environment replacement
replacementEnvs := buildArgs.ReplacementEnvs(imageConfig.Config.Env)
resolvedEnvs, err := util.ResolveEnvironmentReplacementList(c.SourcesAndDest, replacementEnvs, true)
if err != nil {
return nil, err
}
// Resolve wildcards and get a list of resolved sources
srcs, err := util.ResolveSources(resolvedEnvs, constants.RootDir)
if err != nil {
return nil, err
}
for index, src := range srcs {
if !filepath.IsAbs(src) {
srcs[index] = filepath.Join(constants.RootDir, src)
}
if c.From == strconv.Itoa(index) {
return true
}
dependencies = append(dependencies, srcs...)
}
}
}
return dependencies, nil
return false
}
112 changes: 44 additions & 68 deletions pkg/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package dockerfile

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
Expand Down Expand Up @@ -95,82 +94,59 @@ func Test_ValidateTarget(t *testing.T) {
}
}

func Test_Dependencies(t *testing.T) {
testDir, err := ioutil.TempDir("", "")
func Test_SaveStage(t *testing.T) {
tempDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
helloPath := filepath.Join(testDir, "hello")
if err := os.Mkdir(helloPath, 0755); err != nil {
t.Fatal(err)
t.Fatalf("couldn't create temp dir: %v", err)
}

dockerfile := fmt.Sprintf(`
FROM scratch
COPY %s %s
defer os.RemoveAll(tempDir)
files := map[string]string{
"Dockerfile": `
FROM scratch
RUN echo hi > /hi
FROM scratch AS second
COPY --from=0 /hi /hi2
FROM scratch AS second
ENV hienv %s
COPY a b
COPY --from=0 /$hienv %s /hi2/
`, helloPath, helloPath, helloPath, testDir)

stages, err := Parse([]byte(dockerfile))
if err != nil {
t.Fatal(err)
}

expectedDependencies := [][]string{
{
helloPath,
testDir,
},
{},
}

for index := range stages {
buildArgs := NewBuildArgs([]string{})
actualDeps, err := Dependencies(index, stages, buildArgs)
testutil.CheckErrorAndDeepEqual(t, false, err, expectedDependencies[index], actualDeps)
}
}

func Test_DependenciesWithArg(t *testing.T) {
testDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
FROM second
RUN xxx
FROM scratch
COPY --from=second /hi2 /hi3
`,
}
helloPath := filepath.Join(testDir, "hello")
if err := os.Mkdir(helloPath, 0755); err != nil {
t.Fatal(err)
if err := testutil.SetupFiles(tempDir, files); err != nil {
t.Fatalf("couldn't create dockerfile: %v", err)
}

dockerfile := fmt.Sprintf(`
FROM scratch
COPY %s %s
FROM scratch AS second
ARG hienv
COPY a b
COPY --from=0 /$hienv %s /hi2/
`, helloPath, helloPath, testDir)

stages, err := Parse([]byte(dockerfile))
stages, err := Stages(filepath.Join(tempDir, "Dockerfile"), "")
if err != nil {
t.Fatal(err)
t.Fatalf("couldn't retrieve stages from Dockerfile: %v", err)
}

expectedDependencies := [][]string{
tests := []struct {
name string
index int
expected bool
}{
{
name: "reference stage in later copy command",
index: 0,
expected: true,
},
{
name: "reference stage in later from command",
index: 1,
expected: true,
},
{
helloPath,
testDir,
name: "don't reference stage later",
index: 2,
expected: false,
},
{},
}
buildArgs := NewBuildArgs([]string{fmt.Sprintf("hienv=%s", helloPath)})

for index := range stages {
actualDeps, err := Dependencies(index, stages, buildArgs)
testutil.CheckErrorAndDeepEqual(t, false, err, expectedDependencies[index], actualDeps)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := SaveStage(test.index, stages)
testutil.CheckErrorAndDeepEqual(t, false, nil, test.expected, actual)
})
}
}
62 changes: 22 additions & 40 deletions pkg/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ func DoBuild(k KanikoBuildArgs) (v1.Image, error) {
return nil, err
}
for index, stage := range stages {
finalStage := (index == len(stages)-1) || (k.Target == stage.Name)
finalStage := finalStage(index, k.Target, stages)
// Unpack file system to root
sourceImage, err := util.RetrieveSourceImage(index, k.Args, stages)
if err != nil {
return nil, err
}
if err := util.GetFSFromImage(sourceImage); err != nil {
if err := util.GetFSFromImage(constants.RootDir, sourceImage); err != nil {
return nil, err
}
l := snapshot.NewLayeredMap(hasher)
Expand Down Expand Up @@ -159,11 +159,13 @@ func DoBuild(k KanikoBuildArgs) (v1.Image, error) {
}
return sourceImage, nil
}
if err := saveStageAsTarball(index, sourceImage); err != nil {
return nil, err
}
if err := saveStageDependencies(index, stages, buildArgs.Clone()); err != nil {
return nil, err
if dockerfile.SaveStage(index, stages) {
if err := saveStageAsTarball(index, sourceImage); err != nil {
return nil, err
}
if err := extractImageToDependecyDir(index, sourceImage); err != nil {
return nil, err
}
}
// Delete the filesystem
if err := util.DeleteFilesystem(); err != nil {
Expand Down Expand Up @@ -216,44 +218,24 @@ func DoPush(image v1.Image, destinations []string, tarPath string) error {
}
return nil
}
func saveStageDependencies(index int, stages []instructions.Stage, buildArgs *dockerfile.BuildArgs) error {
// First, get the files in this stage later stages will need
dependencies, err := dockerfile.Dependencies(index, stages, buildArgs)
logrus.Infof("saving dependencies %s", dependencies)
if err != nil {
return err

func finalStage(index int, target string, stages []instructions.Stage) bool {
if index == len(stages)-1 {
return true
}
if len(dependencies) == 0 {
return nil
if target == "" {
return false
}
// Then, create the directory they will exist in
i := strconv.Itoa(index)
dependencyDir := filepath.Join(constants.KanikoDir, i)
return target == stages[index].Name
}

func extractImageToDependecyDir(index int, image v1.Image) error {
dependencyDir := filepath.Join(constants.KanikoDir, strconv.Itoa(index))
if err := os.MkdirAll(dependencyDir, 0755); err != nil {
return err
}
// Now, copy over dependencies to this dir
for _, d := range dependencies {
fi, err := os.Lstat(d)
if err != nil {
return err
}
dest := filepath.Join(dependencyDir, d)
if fi.IsDir() {
if err := util.CopyDir(d, dest); err != nil {
return err
}
} else if fi.Mode()&os.ModeSymlink != 0 {
if err := util.CopySymlink(d, dest); err != nil {
return err
}
} else {
if err := util.CopyFile(d, dest); err != nil {
return err
}
}
}
return nil
logrus.Infof("trying to extract to %s", dependencyDir)
return util.GetFSFromImage(dependencyDir, image)
}

func saveStageAsTarball(stageIndex int, image v1.Image) error {
Expand Down
20 changes: 16 additions & 4 deletions pkg/util/fs_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ var whitelist = []string{
}
var volumeWhitelist = []string{}

func GetFSFromImage(img v1.Image) error {
func GetFSFromImage(root string, img v1.Image) error {
whitelist, err := fileSystemWhitelist(constants.WhitelistPath)
if err != nil {
return err
Expand Down Expand Up @@ -72,7 +72,7 @@ func GetFSFromImage(img v1.Image) error {
if err != nil {
return err
}
path := filepath.Join("/", filepath.Clean(hdr.Name))
path := filepath.Join(root, filepath.Clean(hdr.Name))
base := filepath.Base(path)
dir := filepath.Dir(path)
if strings.HasPrefix(base, ".wh.") {
Expand All @@ -91,7 +91,7 @@ func GetFSFromImage(img v1.Image) error {
continue
}

if CheckWhitelist(path) {
if CheckWhitelist(path) && !checkWhitelistRoot(root) {
logrus.Infof("Not adding %s because it is whitelisted", path)
continue
}
Expand All @@ -103,7 +103,7 @@ func GetFSFromImage(img v1.Image) error {
}
fs[path] = struct{}{}

if err := extractFile("/", hdr, tr); err != nil {
if err := extractFile(root, hdr, tr); err != nil {
return err
}
}
Expand Down Expand Up @@ -256,6 +256,18 @@ func CheckWhitelist(path string) bool {
return false
}

func checkWhitelistRoot(root string) bool {
if root == constants.RootDir {
return false
}
for _, wl := range whitelist {
if HasFilepathPrefix(root, wl) {
return true
}
}
return false
}

// Get whitelist from roots of mounted files
// Each line of /proc/self/mountinfo is in the form:
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
Expand Down

0 comments on commit cadab33

Please sign in to comment.