Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --target flag for multistage builds #255

Merged
merged 2 commits into from
Jul 30, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ Set this flag to strip timestamps out of the built image and make it reproducibl

Set this flag as `--tarPath=<path>` to save the image as a tarball at path instead of pushing the image.

#### --target

Set this flag to indicate which build stage is the target build stage.

### Debug Image

The kaniko executor image is based off of scratch and doesn't contain a shell.
Expand Down
3 changes: 3 additions & 0 deletions cmd/executor/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ var (
tarPath string
singleSnapshot bool
reproducible bool
target string
)

func init() {
Expand All @@ -60,6 +61,7 @@ func init() {
RootCmd.PersistentFlags().StringVarP(&tarPath, "tarPath", "", "", "Path to save the image in as a tarball instead of pushing")
RootCmd.PersistentFlags().BoolVarP(&singleSnapshot, "single-snapshot", "", false, "Set this flag to take a single snapshot at the end of the build.")
RootCmd.PersistentFlags().BoolVarP(&reproducible, "reproducible", "", false, "Strip timestamps out of the image to make it reproducible")
RootCmd.PersistentFlags().StringVarP(&target, "target", "", "", " Set the target build stage to build")
}

var RootCmd = &cobra.Command{
Expand Down Expand Up @@ -92,6 +94,7 @@ var RootCmd = &cobra.Command{
Args: buildArgs,
SingleSnapshot: singleSnapshot,
Reproducible: reproducible,
Target: target,
})
if err != nil {
logrus.Error(err)
Expand Down
10 changes: 10 additions & 0 deletions integration/dockerfiles/Dockerfile_test_target
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM gcr.io/distroless/base:latest as base
COPY . .

FROM scratch as second
ENV foopath context/foo
COPY --from=0 $foopath context/b* /foo/

FROM base
ARG file
COPY --from=second /foo $file
21 changes: 14 additions & 7 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ package integration
import (
"encoding/json"
"fmt"
"github.com/GoogleContainerTools/kaniko/pkg/constants"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"math"
"os"
"os/exec"
Expand All @@ -31,6 +28,10 @@ import (
"strings"
"testing"

"github.com/GoogleContainerTools/kaniko/pkg/constants"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/daemon"

"github.com/GoogleContainerTools/kaniko/testutil"
)

Expand Down Expand Up @@ -113,10 +114,15 @@ func TestRun(t *testing.T) {
"Dockerfile_test_multistage": {"file=/foo2"},
}

// Map for additional flags
additionalFlagsMap := map[string][]string{
// Map for additional docker flags
additionalDockerFlagsMap := map[string][]string{
"Dockerfile_test_target": {"--target=second"},
}
// Map for additional kaniko flags
additionalKanikoFlagsMap := map[string][]string{
"Dockerfile_test_add": {"--single-snapshot"},
"Dockerfile_test_scratch": {"--single-snapshot"},
"Dockerfile_test_target": {"--target=second"},
}

// TODO: remove test_user_run from this when https://github.com/GoogleContainerTools/container-diff/issues/237 is fixed
Expand Down Expand Up @@ -144,13 +150,14 @@ func TestRun(t *testing.T) {
buildArgs = append(buildArgs, arg)
}
// build docker image
additionalFlags := append(buildArgs, additionalDockerFlagsMap[dockerfile]...)
dockerImage := strings.ToLower(testRepo + dockerPrefix + dockerfile)
dockerCmd := exec.Command("docker",
append([]string{"build",
"-t", dockerImage,
"-f", path.Join(dockerfilesPath, dockerfile),
"."},
buildArgs...)...,
additionalFlags...)...,
)
RunCommand(dockerCmd, t)

Expand All @@ -173,7 +180,7 @@ func TestRun(t *testing.T) {
}

// build kaniko image
additionalFlags := append(buildArgs, additionalFlagsMap[dockerfile]...)
additionalFlags = append(buildArgs, additionalKanikoFlagsMap[dockerfile]...)
kanikoImage := strings.ToLower(testRepo + kanikoPrefix + dockerfile)
kanikoCmd := exec.Command("docker",
append([]string{"run",
Expand Down
17 changes: 14 additions & 3 deletions pkg/dockerfile/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ package dockerfile

import (
"bytes"
"fmt"
"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"
"path/filepath"
"strconv"
"strings"
)

// Parse parses the contents of a Dockerfile and returns a list of commands
Expand All @@ -40,6 +42,15 @@ func Parse(b []byte) ([]instructions.Stage, error) {
return stages, err
}

func Validate(stages []instructions.Stage, target string) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this makes it sound like you're going to validate the whole Dockerfile, when all we're really checking is that the target exists. Maybe rename it to that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

for _, stage := range stages {
if stage.Name == target {
return nil
}
}
return 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
func ResolveStages(stages []instructions.Stage) {
Expand Down
44 changes: 42 additions & 2 deletions pkg/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ package dockerfile

import (
"fmt"
"github.com/GoogleContainerTools/kaniko/testutil"
"github.com/docker/docker/builder/dockerfile/instructions"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"testing"

"github.com/GoogleContainerTools/kaniko/testutil"
"github.com/docker/docker/builder/dockerfile/instructions"
)

func Test_ResolveStages(t *testing.T) {
Expand Down Expand Up @@ -55,6 +56,45 @@ func Test_ResolveStages(t *testing.T) {
}
}

func Test_Validate(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
`
stages, err := Parse([]byte(dockerfile))
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
target string
shouldErr bool
}{
{
name: "test valid target",
target: "second",
shouldErr: false,
},
{
name: "test invalid target",
target: "invalid",
shouldErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actualErr := Validate(stages, test.target)
testutil.CheckError(t, test.shouldErr, actualErr)
})
}
}

func Test_Dependencies(t *testing.T) {
testDir, err := ioutil.TempDir("", "")
if err != nil {
Expand Down
6 changes: 5 additions & 1 deletion pkg/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type KanikoBuildArgs struct {
Args []string
SingleSnapshot bool
Reproducible bool
Target string
}

func DoBuild(k KanikoBuildArgs) (v1.Image, error) {
Expand All @@ -65,14 +66,17 @@ func DoBuild(k KanikoBuildArgs) (v1.Image, error) {
if err != nil {
return nil, err
}
if err := dockerfile.Validate(stages, k.Target); err != nil {
return nil, err
}
dockerfile.ResolveStages(stages)

hasher, err := getHasher(k.SnapshotMode)
if err != nil {
return nil, err
}
for index, stage := range stages {
finalStage := index == len(stages)-1
finalStage := (index == len(stages)-1) || (k.Target == stage.Name)
// Unpack file system to root
sourceImage, err := util.RetrieveSourceImage(index, k.Args, stages)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/util/image_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func tarballImage(index int) (v1.Image, error) {
}

func remoteImage(image string) (v1.Image, error) {
logrus.Infof("Downloading base image %s", image)
ref, err := name.ParseReference(image, name.WeakValidation)
if err != nil {
return nil, err
Expand Down