diff --git a/cmd/kanikoExecute.go b/cmd/kanikoExecute.go index d5eb21cb9..1254c791a 100644 --- a/cmd/kanikoExecute.go +++ b/cmd/kanikoExecute.go @@ -134,7 +134,7 @@ func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.Cus containerImageNameAndTag := fmt.Sprintf("%v:%v", image, containerImageTag) dest = []string{"--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageNameAndTag)} buildOpts := append(config.BuildOptions, dest...) - err = runKaniko(file, buildOpts, execRunner, fileUtils) + err = runKaniko(file, buildOpts, execRunner, fileUtils, commonPipelineEnvironment) if err != nil { return fmt.Errorf("failed to build image '%v' using '%v': %w", image, file, err) } @@ -206,15 +206,23 @@ func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.Cus } // no support for building multiple containers - return runKaniko(config.DockerfilePath, config.BuildOptions, execRunner, fileUtils) + return runKaniko(config.DockerfilePath, config.BuildOptions, execRunner, fileUtils, commonPipelineEnvironment) } -func runKaniko(dockerFilepath string, buildOptions []string, execRunner command.ExecRunner, fileUtils piperutils.FileUtils) error { +func runKaniko(dockerFilepath string, buildOptions []string, execRunner command.ExecRunner, fileUtils piperutils.FileUtils, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment) error { cwd, err := fileUtils.Getwd() if err != nil { return fmt.Errorf("failed to get current working directory: %w", err) } - kanikoOpts := []string{"--dockerfile", dockerFilepath, "--context", cwd} + + tmpDir, err := fileUtils.TempDir("", "*-kanikoExecute") + if err != nil { + return fmt.Errorf("failed to create tmp dir for kanikoExecute: %w", err) + } + + digestFilePath := fmt.Sprintf("%s/digest.txt", tmpDir) + + kanikoOpts := []string{"--dockerfile", dockerFilepath, "--context", cwd, "--reproducible", "--digest-file", digestFilePath} kanikoOpts = append(kanikoOpts, buildOptions...) err = execRunner.RunExecutable("/kaniko/executor", kanikoOpts...) @@ -222,5 +230,23 @@ func runKaniko(dockerFilepath string, buildOptions []string, execRunner command. log.SetErrorCategory(log.ErrorBuild) return errors.Wrap(err, "execution of '/kaniko/executor' failed") } + + if b, err := fileUtils.FileExists(digestFilePath); err == nil && b { + digest, err := fileUtils.FileRead(digestFilePath) + + if err != nil { + return errors.Wrap(err, "error while reading image digest") + } + + digestStr := string(digest) + + log.Entry().Debugf("image digest: %s", digestStr) + + commonPipelineEnvironment.container.imageDigest = string(digestStr) + commonPipelineEnvironment.container.imageDigests = append(commonPipelineEnvironment.container.imageDigests, digestStr) + } else { + log.Entry().Warn("couldn't resolve image digest") + } + return nil } diff --git a/cmd/kanikoExecute_generated.go b/cmd/kanikoExecute_generated.go index d6cbc18a4..ae8dfae6e 100644 --- a/cmd/kanikoExecute_generated.go +++ b/cmd/kanikoExecute_generated.go @@ -38,8 +38,10 @@ type kanikoExecuteCommonPipelineEnvironment struct { container struct { registryURL string imageNameTag string + imageDigest string imageNames []string imageNameTags []string + imageDigests []string } custom struct { buildSettingsInfo string @@ -54,8 +56,10 @@ func (p *kanikoExecuteCommonPipelineEnvironment) persist(path, resourceName stri }{ {category: "container", name: "registryUrl", value: p.container.registryURL}, {category: "container", name: "imageNameTag", value: p.container.imageNameTag}, + {category: "container", name: "imageDigest", value: p.container.imageDigest}, {category: "container", name: "imageNames", value: p.container.imageNames}, {category: "container", name: "imageNameTags", value: p.container.imageNameTags}, + {category: "container", name: "imageDigests", value: p.container.imageDigests}, {category: "custom", name: "buildSettingsInfo", value: p.custom.buildSettingsInfo}, } @@ -369,7 +373,7 @@ func kanikoExecuteMetadata() config.StepData { }, }, Containers: []config.Container{ - {Image: "gcr.io/kaniko-project/executor:debug", EnvVars: []config.EnvVar{{Name: "container", Value: "docker"}, {Name: "TMPDIR", Value: "/"}}, Options: []config.Option{{Name: "-u", Value: "0"}, {Name: "--entrypoint", Value: ""}}}, + {Image: "gcr.io/kaniko-project/executor:debug", EnvVars: []config.EnvVar{{Name: "container", Value: "docker"}}, Options: []config.Option{{Name: "-u", Value: "0"}, {Name: "--entrypoint", Value: ""}}}, }, Outputs: config.StepOutputs{ Resources: []config.StepResources{ @@ -379,8 +383,10 @@ func kanikoExecuteMetadata() config.StepData { Parameters: []map[string]interface{}{ {"name": "container/registryUrl"}, {"name": "container/imageNameTag"}, + {"name": "container/imageDigest"}, {"name": "container/imageNames", "type": "[]string"}, {"name": "container/imageNameTags", "type": "[]string"}, + {"name": "container/imageDigests", "type": "[]string"}, {"name": "custom/buildSettingsInfo"}, }, }, diff --git a/cmd/kanikoExecute_test.go b/cmd/kanikoExecute_test.go index a3a17dff0..973ef9b5b 100644 --- a/cmd/kanikoExecute_test.go +++ b/cmd/kanikoExecute_test.go @@ -68,6 +68,7 @@ func TestRunKanikoExecute(t *testing.T) { fileUtils := &mock.FilesMock{} fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) + fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`)) err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) @@ -83,7 +84,7 @@ func TestRunKanikoExecute(t *testing.T) { assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec) cwd, _ := fileUtils.Getwd() - assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, runner.Calls[1].Params) + assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--skip-tls-verify-pull", "--destination", "myImage:tag"}, runner.Calls[1].Params) assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"mavenExecuteBuild":[{"dockerImage":"maven"}]`) assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"kanikoExecute":[{"dockerImage":"gcr.io/kaniko-project/executor:debug"}]`) @@ -92,6 +93,9 @@ func TestRunKanikoExecute(t *testing.T) { assert.Equal(t, "https://index.docker.io", commonPipelineEnvironment.container.registryURL) assert.Equal(t, []string{"myImage"}, commonPipelineEnvironment.container.imageNames) assert.Equal(t, []string{"myImage:tag"}, commonPipelineEnvironment.container.imageNameTags) + + assert.Equal(t, "sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0", commonPipelineEnvironment.container.imageDigest) + assert.Equal(t, []string{"sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0"}, commonPipelineEnvironment.container.imageDigests) }) t.Run("success case - image params", func(t *testing.T) { @@ -115,6 +119,7 @@ func TestRunKanikoExecute(t *testing.T) { fileUtils := &mock.FilesMock{} fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) + fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`)) err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) @@ -130,12 +135,15 @@ func TestRunKanikoExecute(t *testing.T) { assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec) cwd, _ := fileUtils.Getwd() - assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "my.registry.com:50000/myImage:1.2.3-a-x"}, runner.Calls[1].Params) + assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--skip-tls-verify-pull", "--destination", "my.registry.com:50000/myImage:1.2.3-a-x"}, runner.Calls[1].Params) assert.Equal(t, "myImage:1.2.3-a-x", commonPipelineEnvironment.container.imageNameTag) assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL) assert.Equal(t, []string{"myImage"}, commonPipelineEnvironment.container.imageNames) assert.Equal(t, []string{"myImage:1.2.3-a-x"}, commonPipelineEnvironment.container.imageNameTags) + + assert.Equal(t, "sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0", commonPipelineEnvironment.container.imageDigest) + assert.Equal(t, []string{"sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0"}, commonPipelineEnvironment.container.imageDigests) }) t.Run("success case - image params with custom destination", func(t *testing.T) { @@ -156,6 +164,7 @@ func TestRunKanikoExecute(t *testing.T) { fileUtils := &mock.FilesMock{} fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) + fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`)) err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) @@ -171,12 +180,15 @@ func TestRunKanikoExecute(t *testing.T) { assert.Equal(t, "/kaniko/executor", runner.Calls[1].Exec) cwd, _ := fileUtils.Getwd() - assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "my.other.registry.com:50000/myImage:3.2.1-a-x"}, runner.Calls[1].Params) + assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--skip-tls-verify-pull", "--destination", "my.other.registry.com:50000/myImage:3.2.1-a-x"}, runner.Calls[1].Params) assert.Equal(t, "myImage:3.2.1-a-x", commonPipelineEnvironment.container.imageNameTag) assert.Equal(t, "https://my.other.registry.com:50000", commonPipelineEnvironment.container.registryURL) assert.Equal(t, []string{"myImage"}, commonPipelineEnvironment.container.imageNames) assert.Equal(t, []string{"myImage:3.2.1-a-x"}, commonPipelineEnvironment.container.imageNameTags) + + assert.Equal(t, "sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0", commonPipelineEnvironment.container.imageDigest) + assert.Equal(t, []string{"sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0"}, commonPipelineEnvironment.container.imageDigests) }) t.Run("no error case - when cert update skipped", func(t *testing.T) { @@ -198,6 +210,7 @@ func TestRunKanikoExecute(t *testing.T) { fileUtils := &mock.FilesMock{} fileUtils.AddFile("path/to/docker/config.json", []byte(``)) fileUtils.FileReadErrors = map[string]error{"/kaniko/ssl/certs/ca-certificates.crt": fmt.Errorf("read error")} + fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`)) err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) @@ -220,6 +233,7 @@ func TestRunKanikoExecute(t *testing.T) { } fileUtils := &mock.FilesMock{} fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) + fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`)) err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) @@ -230,7 +244,7 @@ func TestRunKanikoExecute(t *testing.T) { assert.Equal(t, `{"auths":{}}`, string(c)) cwd, _ := fileUtils.Getwd() - assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--no-push"}, runner.Calls[1].Params) + assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--skip-tls-verify-pull", "--no-push"}, runner.Calls[1].Params) }) t.Run("success case - backward compatibility", func(t *testing.T) { @@ -252,12 +266,13 @@ func TestRunKanikoExecute(t *testing.T) { fileUtils := &mock.FilesMock{} fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`)) fileUtils.AddFile("/kaniko/ssl/certs/ca-certificates.crt", []byte(``)) + fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`)) err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, certClient, fileUtils) assert.NoError(t, err) cwd, _ := fileUtils.Getwd() - assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, runner.Calls[1].Params) + assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--skip-tls-verify-pull", "--destination", "myImage:tag"}, runner.Calls[1].Params) }) t.Run("success case - multi image build with root image", func(t *testing.T) { @@ -275,6 +290,7 @@ func TestRunKanikoExecute(t *testing.T) { fileUtils.AddFile("Dockerfile", []byte("some content")) fileUtils.AddFile("sub1/Dockerfile", []byte("some content")) fileUtils.AddFile("sub2/Dockerfile", []byte("some content")) + fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`)) err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, nil, fileUtils) @@ -287,9 +303,9 @@ func TestRunKanikoExecute(t *testing.T) { cwd, _ := fileUtils.Getwd() expectedParams := [][]string{ - {"--dockerfile", "Dockerfile", "--context", cwd, "--destination", "my.registry.com:50000/myImage:myTag"}, - {"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"}, - {"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"}, + {"--dockerfile", "Dockerfile", "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--destination", "my.registry.com:50000/myImage:myTag"}, + {"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--destination", "my.registry.com:50000/myImage-sub1:myTag"}, + {"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--destination", "my.registry.com:50000/myImage-sub2:myTag"}, } // need to go this way since we cannot count on the correct order for _, call := range runner.Calls { @@ -311,6 +327,9 @@ func TestRunKanikoExecute(t *testing.T) { assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage:myTag") assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub1:myTag") assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImage-sub2:myTag") + + assert.Equal(t, commonPipelineEnvironment.container.imageDigest, "sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0") + assert.Equal(t, commonPipelineEnvironment.container.imageDigests, []string{"sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0", "sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0", "sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0"}) }) t.Run("success case - multi image build excluding root image", func(t *testing.T) { @@ -329,6 +348,7 @@ func TestRunKanikoExecute(t *testing.T) { fileUtils.AddFile("Dockerfile", []byte("some content")) fileUtils.AddFile("sub1/Dockerfile", []byte("some content")) fileUtils.AddFile("sub2/Dockerfile", []byte("some content")) + fileUtils.AddFile("/tmp/*-kanikoExecutetest/digest.txt", []byte(`sha256:468dd1253cc9f498fc600454bb8af96d880fec3f9f737e7057692adfe9f7d5b0`)) err := runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, runner, nil, fileUtils) @@ -340,8 +360,8 @@ func TestRunKanikoExecute(t *testing.T) { cwd, _ := fileUtils.Getwd() expectedParams := [][]string{ - {"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"}, - {"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"}, + {"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--destination", "my.registry.com:50000/myImage-sub1:myTag"}, + {"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--reproducible", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt", "--destination", "my.registry.com:50000/myImage-sub2:myTag"}, } // need to go this way since we cannot count on the correct order for _, call := range runner.Calls { diff --git a/pkg/piperutils/FileUtils.go b/pkg/piperutils/FileUtils.go index 0310b0882..316aaf595 100644 --- a/pkg/piperutils/FileUtils.go +++ b/pkg/piperutils/FileUtils.go @@ -42,6 +42,13 @@ type Files struct { // TempDir creates a temporary directory func (f Files) TempDir(dir, pattern string) (name string, err error) { + if len(dir) == 0 { + // lazy init system temp dir in case it doesn't exist + if exists, _ := f.DirExists(os.TempDir()); !exists { + f.MkdirAll(os.TempDir(), 0666) + } + } + return ioutil.TempDir(dir, pattern) } diff --git a/resources/metadata/kanikoExecute.yaml b/resources/metadata/kanikoExecute.yaml index 13f4184c6..978628b0b 100644 --- a/resources/metadata/kanikoExecute.yaml +++ b/resources/metadata/kanikoExecute.yaml @@ -34,8 +34,8 @@ spec: - STEPS default: - --skip-tls-verify-pull - # fixing Kaniko issue with multistage builds and missing busybox - # suggestion from https://github.com/GoogleContainerTools/kaniko/issues/1586#issuecomment-945718536 did not solve the problem + # fixing Kaniko issue https://github.com/GoogleContainerTools/kaniko/issues/1586 + # as per comment https://github.com/GoogleContainerTools/kaniko/issues/1586#issuecomment-945718536 - --ignore-path=/ - name: buildSettingsInfo type: string @@ -173,10 +173,13 @@ spec: params: - name: container/registryUrl - name: container/imageNameTag + - name: container/imageDigest - name: container/imageNames type: "[]string" - name: container/imageNameTags type: "[]string" + - name: container/imageDigests + type: "[]string" - name: custom/buildSettingsInfo containers: - image: gcr.io/kaniko-project/executor:debug @@ -191,5 +194,3 @@ spec: env: - name: container value: docker - - name: TMPDIR - value: / diff --git a/vars/piperPipelineStageInit.groovy b/vars/piperPipelineStageInit.groovy index 760cbc22b..33010b0b6 100644 --- a/vars/piperPipelineStageInit.groovy +++ b/vars/piperPipelineStageInit.groovy @@ -65,7 +65,13 @@ import static com.sap.piper.Prerequisites.checkScript */ 'verbose' ] -@Field STAGE_STEP_KEYS = [] +@Field STAGE_STEP_KEYS = [ + /** + * Sets the build version. + * @possibleValues `true`, `false` + */ + 'artifactPrepareVersion' +] @Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus(STAGE_STEP_KEYS) @Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([ /** @@ -220,6 +226,11 @@ void call(Map parameters = [:]) { if (parameters.script.commonPipelineEnvironment.configuration.runStep?.get('Init')?.slackSendNotification) { slackSendNotification script: script, message: "STARTED: Job <${env.BUILD_URL}|${URLDecoder.decode(env.JOB_NAME, java.nio.charset.StandardCharsets.UTF_8.name())} ${env.BUILD_DISPLAY_NAME}>", color: 'WARNING' } + + config.artifactPrepareVersion = true + } + + if (config.artifactPrepareVersion) { Map prepareVersionParams = [script: script] if (config.inferBuildTool) { prepareVersionParams.buildTool = buildTool