diff --git a/README.md b/README.md index d0075183df..f9b0465836 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,10 @@ Set this flag to strip timestamps out of the built image and make it reproducibl Set this flag as `--tarPath=` 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. diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 3f4fd18e74..75ee38f3f5 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -44,6 +44,7 @@ var ( tarPath string singleSnapshot bool reproducible bool + target string ) func init() { @@ -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{ @@ -92,6 +94,7 @@ var RootCmd = &cobra.Command{ Args: buildArgs, SingleSnapshot: singleSnapshot, Reproducible: reproducible, + Target: target, }) if err != nil { logrus.Error(err) diff --git a/integration/dockerfiles/Dockerfile_test_target b/integration/dockerfiles/Dockerfile_test_target new file mode 100644 index 0000000000..a4d2dd5065 --- /dev/null +++ b/integration/dockerfiles/Dockerfile_test_target @@ -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 diff --git a/integration/integration_test.go b/integration/integration_test.go index 0684dfc8d3..f5fe4b9c0f 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -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" @@ -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" ) @@ -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 @@ -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) @@ -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", diff --git a/pkg/dockerfile/dockerfile.go b/pkg/dockerfile/dockerfile.go index 6e482a4d51..d6aff04fd7 100644 --- a/pkg/dockerfile/dockerfile.go +++ b/pkg/dockerfile/dockerfile.go @@ -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 @@ -40,6 +42,15 @@ func Parse(b []byte) ([]instructions.Stage, error) { return stages, err } +func ValidateTarget(stages []instructions.Stage, target string) error { + 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) { diff --git a/pkg/dockerfile/dockerfile_test.go b/pkg/dockerfile/dockerfile_test.go index 758f54f160..9523771fc0 100644 --- a/pkg/dockerfile/dockerfile_test.go +++ b/pkg/dockerfile/dockerfile_test.go @@ -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) { @@ -55,6 +56,45 @@ func Test_ResolveStages(t *testing.T) { } } +func Test_ValidateTarget(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 := ValidateTarget(stages, test.target) + testutil.CheckError(t, test.shouldErr, actualErr) + }) + } +} + func Test_Dependencies(t *testing.T) { testDir, err := ioutil.TempDir("", "") if err != nil { diff --git a/pkg/executor/executor.go b/pkg/executor/executor.go index 47f3c6f2c3..d062a43333 100644 --- a/pkg/executor/executor.go +++ b/pkg/executor/executor.go @@ -52,6 +52,7 @@ type KanikoBuildArgs struct { Args []string SingleSnapshot bool Reproducible bool + Target string } func DoBuild(k KanikoBuildArgs) (v1.Image, error) { @@ -65,6 +66,9 @@ func DoBuild(k KanikoBuildArgs) (v1.Image, error) { if err != nil { return nil, err } + if err := dockerfile.ValidateTarget(stages, k.Target); err != nil { + return nil, err + } dockerfile.ResolveStages(stages) hasher, err := getHasher(k.SnapshotMode) @@ -72,7 +76,7 @@ func DoBuild(k KanikoBuildArgs) (v1.Image, error) { 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 { diff --git a/pkg/util/image_util.go b/pkg/util/image_util.go index 646f3546c7..47400d0529 100644 --- a/pkg/util/image_util.go +++ b/pkg/util/image_util.go @@ -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