mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-22 11:31:51 +02:00
Add --target flag for multistage builds (#255)
* Add --target flag for multistage builds * change validate to validateTarget
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user