diff --git a/README.md b/README.md index 2e0de1929..726dc2708 100644 --- a/README.md +++ b/README.md @@ -432,6 +432,12 @@ If `--destination=gcr.io/kaniko-project/test`, then cached layers will be stored _This flag must be used in conjunction with the `--cache=true` flag._ +#### --context-sub-path + +Set a sub path within the given `--context`. + +Its particularly useful when your context is, for example, a git repository, +and you want to build one of its subfolders instead of the root folder. #### --digest-file diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index ff198060d..db27d75f3 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -39,10 +39,11 @@ import ( ) var ( - opts = &config.KanikoOptions{} - force bool - logLevel string - logFormat string + opts = &config.KanikoOptions{} + ctxSubPath string + force bool + logLevel string + logFormat string ) func init() { @@ -131,6 +132,7 @@ var RootCmd = &cobra.Command{ func addKanikoOptionsFlags() { RootCmd.PersistentFlags().StringVarP(&opts.DockerfilePath, "dockerfile", "f", "Dockerfile", "Path to the dockerfile to be built.") RootCmd.PersistentFlags().StringVarP(&opts.SrcContext, "context", "c", "/workspace/", "Path to the dockerfile build context.") + RootCmd.PersistentFlags().StringVarP(&ctxSubPath, "context-sub-path", "", "", "Sub path within the given context.") RootCmd.PersistentFlags().StringVarP(&opts.Bucket, "bucket", "b", "", "Name of the GCS bucket from which to access build context as tarball.") RootCmd.PersistentFlags().VarP(&opts.Destinations, "destination", "d", "Registry the final image should be pushed to. Set it repeatedly for multiple destinations.") RootCmd.PersistentFlags().StringVarP(&opts.SnapshotMode, "snapshotMode", "", "full", "Change the file attributes inspected during snapshotting") @@ -259,6 +261,12 @@ func resolveSourceContext() error { if err != nil { return err } + if ctxSubPath != "" { + opts.SrcContext = filepath.Join(opts.SrcContext, ctxSubPath) + if _, err := os.Stat(opts.SrcContext); os.IsNotExist(err) { + return err + } + } logrus.Debugf("Build context located at %s", opts.SrcContext) return nil } diff --git a/integration/integration_test.go b/integration/integration_test.go index 0ae62e4b4..5cb854782 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -260,6 +260,50 @@ func TestGitBuildcontext(t *testing.T) { checkContainerDiffOutput(t, diff, expected) } +func TestGitBuildcontextSubPath(t *testing.T) { + repo := getGitRepo() + dockerfile := "Dockerfile_test_run_2" + + // Build with docker + dockerImage := GetDockerImage(config.imageRepo, "Dockerfile_test_git") + dockerCmd := exec.Command("docker", + append([]string{ + "build", + "-t", dockerImage, + "-f", dockerfile, + repo + ":" + filepath.Join(integrationPath, dockerfilesPath), + })...) + out, err := RunCommandWithoutTest(dockerCmd) + if err != nil { + t.Errorf("Failed to build image %s with docker command %q: %s %s", dockerImage, dockerCmd.Args, err, string(out)) + } + + // Build with kaniko + kanikoImage := GetKanikoImage(config.imageRepo, "Dockerfile_test_git") + dockerRunFlags := []string{"run", "--net=host"} + dockerRunFlags = addServiceAccountFlags(dockerRunFlags, config.serviceAccount) + dockerRunFlags = append( + dockerRunFlags, + ExecutorImage, + "-f", dockerfile, + "-d", kanikoImage, + "-c", fmt.Sprintf("git://%s", repo), + "--context-sub-path", filepath.Join(integrationPath, dockerfilesPath), + ) + + kanikoCmd := exec.Command("docker", dockerRunFlags...) + + out, err = RunCommandWithoutTest(kanikoCmd) + if err != nil { + t.Errorf("Failed to build image %s with kaniko command %q: %v %s", dockerImage, kanikoCmd.Args, err, string(out)) + } + + diff := containerDiff(t, daemonPrefix+dockerImage, kanikoImage, "--no-cache") + + expected := fmt.Sprintf(emptyContainerDiff, dockerImage, kanikoImage, dockerImage, kanikoImage) + checkContainerDiffOutput(t, diff, expected) +} + func TestBuildViaRegistryMirror(t *testing.T) { repo := getGitRepo() dockerfile := "integration/dockerfiles/Dockerfile_registry_mirror" diff --git a/pkg/dockerfile/dockerfile.go b/pkg/dockerfile/dockerfile.go index 7279b7b3b..3e41f40de 100644 --- a/pkg/dockerfile/dockerfile.go +++ b/pkg/dockerfile/dockerfile.go @@ -259,7 +259,6 @@ func MakeKanikoStages(opts *config.KanikoOptions, stages []instructions.Stage, m logrus.Infof("Resolved base name %s to %s", stage.BaseName, stage.Name) } baseImageIndex := baseImageIndex(index, stages) - kanikoStages = append(kanikoStages, config.KanikoStage{ Stage: stage, BaseImageIndex: baseImageIndex, diff --git a/pkg/dockerfile/dockerfile_test.go b/pkg/dockerfile/dockerfile_test.go index 7055eaf00..8fb612297 100644 --- a/pkg/dockerfile/dockerfile_test.go +++ b/pkg/dockerfile/dockerfile_test.go @@ -35,10 +35,10 @@ func Test_ParseStages_ArgValueWithQuotes(t *testing.T) { ARG FOO=bar FROM ${IMAGE} RUN echo hi > /hi - + FROM scratch AS second COPY --from=0 /hi /hi2 - + FROM scratch COPY --from=second /hi2 /hi3 ` @@ -190,46 +190,6 @@ func Test_stripEnclosingQuotes(t *testing.T) { } } -func Test_ResolveCrossStageCommands(t *testing.T) { - type testCase struct { - name string - cmd instructions.CopyCommand - stageToIdx map[string]string - expFrom string - } - - tests := []testCase{ - { - name: "resolve copy command", - cmd: instructions.CopyCommand{From: "builder"}, - stageToIdx: map[string]string{"builder": "0"}, - expFrom: "0", - }, - { - name: "resolve upper case FROM", - cmd: instructions.CopyCommand{From: "BuIlDeR"}, - stageToIdx: map[string]string{"builder": "0"}, - expFrom: "0", - }, - { - name: "nothing to resolve", - cmd: instructions.CopyCommand{From: "downloader"}, - stageToIdx: map[string]string{"builder": "0"}, - expFrom: "downloader", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - cmds := []instructions.Command{&test.cmd} - ResolveCrossStageCommands(cmds, test.stageToIdx) - if test.cmd.From != test.expFrom { - t.Fatalf("Failed to resolve command: expected from %s, resolved to %s", test.expFrom, test.cmd.From) - } - }) - } -} - func Test_GetOnBuildInstructions(t *testing.T) { type testCase struct { name string @@ -294,10 +254,10 @@ func Test_targetStage(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 ` @@ -461,7 +421,6 @@ func Test_ResolveStagesArgs(t *testing.T) { t.Fatal(err) } stagesLen := len(stages) - args := unifyArgs(metaArgs, buildArgs) if err := resolveStagesArgs(stages, args); err != nil { t.Fatalf("fail to resolves args %v: %v", buildArgs, err)