diff --git a/README.md b/README.md index f285a6080..b79611724 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ _If you are interested in contributing to kaniko, see [DEVELOPMENT.md](DEVELOPME - [--tarPath](#--tarpath) - [--verbosity](#--verbosity) - [--whitelist-var-run](#--whitelist-var-run) + - [--label](#--label) - [Debug Image](#debug-image) - [Security](#security) - [Comparison with Other Tools](#comparison-with-other-tools) @@ -521,6 +522,10 @@ Set this flag as `--verbosity=` to set the lo Ignore /var/run when taking image snapshot. Set it to false to preserve /var/run/* in destination image. (Default true). +#### --label + +Set this flag as `--label key=value` to set some metadata to the final image. This is equivalent as using the `LABEL` within the Dockerfile. + ### Debug Image The kaniko executor image is based on scratch and doesn't contain a shell. diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 62f10d60e..87c661e29 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -158,6 +158,7 @@ func addKanikoOptionsFlags() { RootCmd.PersistentFlags().VarP(&opts.RegistriesCertificates, "registry-certificate", "", "Use the provided certificate for TLS communication with the given registry. Expected format is 'my.registry.url=/path/to/the/server/certificate'.") RootCmd.PersistentFlags().StringVarP(&opts.RegistryMirror, "registry-mirror", "", "", "Registry mirror to use has pull-through cache instead of docker.io.") RootCmd.PersistentFlags().BoolVarP(&opts.WhitelistVarRun, "whitelist-var-run", "", true, "Ignore /var/run directory when taking image snapshot. Set it to false to preserve /var/run/ in destination image. (Default true).") + RootCmd.PersistentFlags().VarP(&opts.Labels, "label", "", "Set metadata for an image. Set it repeatedly for multiple labels.") } // addHiddenFlags marks certain flags as hidden from the executor help text diff --git a/integration/integration_test.go b/integration/integration_test.go index f70522b35..357b46cb0 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -309,6 +309,49 @@ func TestBuildViaRegistryMirror(t *testing.T) { checkContainerDiffOutput(t, diff, expected) } +func TestBuildWithLabels(t *testing.T) { + repo := getGitRepo() + dockerfile := "integration/dockerfiles/Dockerfile_test_label" + + testLabel := "mylabel=myvalue" + + // Build with docker + dockerImage := GetDockerImage(config.imageRepo, "Dockerfile_test_label:mylabel") + dockerCmd := exec.Command("docker", + append([]string{"build", + "-t", dockerImage, + "-f", dockerfile, + "--label", testLabel, + repo})...) + 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_label:mylabel") + dockerRunFlags := []string{"run", "--net=host"} + dockerRunFlags = addServiceAccountFlags(dockerRunFlags, config.serviceAccount) + dockerRunFlags = append(dockerRunFlags, ExecutorImage, + "-f", dockerfile, + "-d", kanikoImage, + "--label", testLabel, + "-c", fmt.Sprintf("git://%s", repo), + ) + + 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 TestLayers(t *testing.T) { offset := map[string]int{ "Dockerfile_test_add": 12, diff --git a/pkg/config/options.go b/pkg/config/options.go index b0ba6e8d1..47436a5fd 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -43,6 +43,7 @@ type KanikoOptions struct { Destinations multiArg BuildArgs multiArg InsecureRegistries multiArg + Labels multiArg SkipTLSVerifyRegistries multiArg RegistriesCertificates keyValueArg Insecure bool diff --git a/pkg/executor/build.go b/pkg/executor/build.go index cf7e8bfbf..ecfcd51d8 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -21,6 +21,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "time" "github.com/google/go-containerregistry/pkg/v1/partial" @@ -82,7 +83,7 @@ func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, cross return nil, err } - imageConfig, err := initializeConfig(sourceImage) + imageConfig, err := initializeConfig(sourceImage, opts) if err != nil { return nil, err } @@ -134,7 +135,7 @@ func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, cross return s, nil } -func initializeConfig(img partial.WithConfigFile) (*v1.ConfigFile, error) { +func initializeConfig(img partial.WithConfigFile, opts *config.KanikoOptions) (*v1.ConfigFile, error) { imageConfig, err := img.ConfigFile() if err != nil { return nil, err @@ -143,6 +144,25 @@ func initializeConfig(img partial.WithConfigFile) (*v1.ConfigFile, error) { if imageConfig.Config.Env == nil { imageConfig.Config.Env = constants.ScratchEnvVars } + + if opts == nil { + return imageConfig, nil + } + + if l := len(opts.Labels); l > 0 { + if imageConfig.Config.Labels == nil { + imageConfig.Config.Labels = make(map[string]string) + } + for _, label := range opts.Labels { + parts := strings.SplitN(label, "=", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("labels must be of the form key=value, got %s", label) + } + + imageConfig.Config.Labels[parts[0]] = parts[1] + } + } + return imageConfig, nil } @@ -484,7 +504,7 @@ func CalculateDependencies(opts *config.KanikoOptions) (map[int][]string, error) return nil, err } } - cfg, err := initializeConfig(image) + cfg, err := initializeConfig(image, opts) if err != nil { return nil, err } diff --git a/pkg/executor/build_test.go b/pkg/executor/build_test.go index 64046bc29..d468aee19 100644 --- a/pkg/executor/build_test.go +++ b/pkg/executor/build_test.go @@ -462,7 +462,7 @@ func TestInitializeConfig(t *testing.T) { t.Errorf("error seen when running test %s", err) t.Fail() } - actual, _ := initializeConfig(img) + actual, _ := initializeConfig(img, nil) testutil.CheckDeepEqual(t, tt.expected, actual.Config) } }