From e2cd0cfb6cc8b50c73c1f632026efa269977896e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Ma=C5=82achowski?= <38684517+KacperMalachowski@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:16:48 +0100 Subject: [PATCH] Add image-builder support for Jenkins (#12598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use GIT_COMMIT as pull request head for jenkins * Add support for Jenkins * Fix signing step success value * Add ability to save report to file * gomod(deps): bump github.com/spf13/pflag from 1.0.5 to 1.0.6 (#12599) Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.5 to 1.0.6. - [Release notes](https://github.com/spf13/pflag/releases) - [Commits](https://github.com/spf13/pflag/compare/v1.0.5...v1.0.6) --- updated-dependencies: - dependency-name: github.com/spf13/pflag dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * gomod(deps): bump github.com/zricethezav/gitleaks/v8 (#12600) Bumps [github.com/zricethezav/gitleaks/v8](https://github.com/zricethezav/gitleaks) from 8.23.2 to 8.23.3. - [Release notes](https://github.com/zricethezav/gitleaks/releases) - [Changelog](https://github.com/gitleaks/gitleaks/blob/master/.goreleaser.yml) - [Commits](https://github.com/zricethezav/gitleaks/compare/v8.23.2...v8.23.3) --- updated-dependencies: - dependency-name: github.com/zricethezav/gitleaks/v8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Wojciech Sołtys <74361703+Sawthis@users.noreply.github.com> * gomod(deps): bump google.golang.org/api from 0.218.0 to 0.219.0 (#12601) Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.218.0 to 0.219.0. - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.218.0...v0.219.0) --- updated-dependencies: - dependency-name: google.golang.org/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bumping test-infra and testimages (#12603) No eu.gcr.io/kyma-project/test-infra/ changes. europe-docker.pkg.dev/kyma-project/prod/ changes: https://github.com/kyma-project/test-infra/compare/67a01b66...45cbab52 (2025‑01‑24 → 2025‑01‑30) * Bumping sec-scanners-config.yaml (#12606) * Delete prow/jobs/kyma-project/test-infra/kyma-bot.yaml (#12607) * Update config.yaml (#12604) * Fix linter --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Wojciech Sołtys <74361703+Sawthis@users.noreply.github.com> Co-authored-by: Kyma Bot --- cmd/image-builder/config.go | 9 ++- cmd/image-builder/config_test.go | 15 ++-- cmd/image-builder/main.go | 57 ++++++++------- cmd/image-builder/main_test.go | 99 -------------------------- pkg/azuredevops/pipelines/variables.go | 7 ++ pkg/imagebuilder/report.go | 71 ++++++++++++++++++ pkg/imagebuilder/report_suite_test.go | 13 ++++ pkg/imagebuilder/report_test.go | 89 +++++++++++++++++++++++ 8 files changed, 223 insertions(+), 137 deletions(-) create mode 100644 pkg/azuredevops/pipelines/variables.go create mode 100644 pkg/imagebuilder/report.go create mode 100644 pkg/imagebuilder/report_suite_test.go create mode 100644 pkg/imagebuilder/report_test.go diff --git a/cmd/image-builder/config.go b/cmd/image-builder/config.go index d056f9631e55..aaee6213bc1d 100644 --- a/cmd/image-builder/config.go +++ b/cmd/image-builder/config.go @@ -345,6 +345,9 @@ func loadJenkinsGitState(logger Logger) (GitStateConfig, error) { return GitStateConfig{}, fmt.Errorf("failed to extract owner and repository from git URL %s: %w", gitURL, err) } + // TODO(kacpermalachowski): For PRs this is a head commit, not a base commit. + // There is no reliable way to get the base commit SHA in Jenkins. + // See: https://github.tools.sap/kyma/oci-image-builder/issues/165 baseCommitSHA, present := os.LookupEnv("GIT_COMMIT") if !present { return GitStateConfig{}, fmt.Errorf("GIT_COMMIT environment variable is not set, please set it to valid commit SHA") @@ -367,14 +370,10 @@ func loadJenkinsGitState(logger Logger) (GitStateConfig, error) { if !present { return GitStateConfig{}, fmt.Errorf("CHANGE_BRANCH environment variable is not set, please set it to valid base branch name") } - pullRequestHeadSHA, present := os.LookupEnv("CHANGE_HEAD_SHA") - if !present { - return GitStateConfig{}, fmt.Errorf("CHANGE_HEAD_SHA environment variable is not set, please set it to valid commit SHA") - } gitState.JobType = "presubmit" gitState.PullRequestNumber = pullNumber gitState.BaseCommitRef = baseRef - gitState.PullHeadCommitSHA = pullRequestHeadSHA + gitState.PullHeadCommitSHA = baseCommitSHA gitState.isPullRequest = true } diff --git a/cmd/image-builder/config_test.go b/cmd/image-builder/config_test.go index 22bc26d3c993..2cafa63f38eb 100644 --- a/cmd/image-builder/config_test.go +++ b/cmd/image-builder/config_test.go @@ -404,12 +404,11 @@ func TestLoadGitStateConfig(t *testing.T) { ciSystem: Jenkins, }, env: map[string]string{ - "CHANGE_BRANCH": "refs/heads/main", - "JENKINS_HOME": "/some/absolute/path", - "CHANGE_ID": "14", - "GIT_URL": "github.com/kyma-project/test-infra.git", - "GIT_COMMIT": "1234", - "CHANGE_HEAD_SHA": "4321", // Must be explicitly set when calling docker run + "CHANGE_BRANCH": "refs/heads/main", + "JENKINS_HOME": "/some/absolute/path", + "CHANGE_ID": "14", + "GIT_URL": "github.com/kyma-project/test-infra.git", + "GIT_COMMIT": "1234", }, gitState: GitStateConfig{ RepositoryName: "test-infra", @@ -418,7 +417,7 @@ func TestLoadGitStateConfig(t *testing.T) { BaseCommitSHA: "1234", BaseCommitRef: "refs/heads/main", PullRequestNumber: 14, - PullHeadCommitSHA: "4321", + PullHeadCommitSHA: "1234", isPullRequest: true, }, }, @@ -520,4 +519,4 @@ func Test_determineCISystem(t *testing.T) { } }) } -} \ No newline at end of file +} diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index 5dba40cf6263..bc37e09edbbd 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -14,14 +14,13 @@ import ( "os/exec" "path" "path/filepath" - "regexp" "strconv" "strings" "sync" adopipelines "github.com/kyma-project/test-infra/pkg/azuredevops/pipelines" - "github.com/kyma-project/test-infra/pkg/extractimageurls" "github.com/kyma-project/test-infra/pkg/github/actions" + "github.com/kyma-project/test-infra/pkg/imagebuilder" "github.com/kyma-project/test-infra/pkg/logging" "github.com/kyma-project/test-infra/pkg/sets" "github.com/kyma-project/test-infra/pkg/sign" @@ -65,6 +64,12 @@ type options struct { dryRun bool tagsOutputFile string useGoInternalSAPModules bool + // buildReportPath is a path to the file where the build report will be saved + // build report will be used by SRE team to gather information about the build + buildReportPath string + // adoStateOutput indicates if the success or failure of the command (sign or build) should be + // reported as an output variable in Azure DevOps + adoStateOutput bool } type Logger interface { @@ -330,6 +335,7 @@ func buildInADO(o options) error { var ( pipelineRunResult *pipelines.RunResult logs string + buildReport *imagebuilder.BuildReport ) if !o.dryRun { ctx := context.Background() @@ -371,6 +377,12 @@ func buildInADO(o options) error { } else { fmt.Printf("ADO pipeline image build logs:\n%s", logs) } + + // Parse the build report from the ADO pipeline run logs. + buildReport, err = imagebuilder.NewBuildReportFromLogs(logs) + if err != nil { + return fmt.Errorf("build in ADO failed, failed parsing build report from ADO pipeline run logs, err: %s", err) + } } else { dryRunPipelineRunResult := pipelines.RunResult("Succeeded") pipelineRunResult = &dryRunPipelineRunResult @@ -381,9 +393,8 @@ func buildInADO(o options) error { // if run in github actions, set output parameters if o.ciSystem == GithubActions { fmt.Println("Setting GitHub outputs.") - var images []string + images := buildReport.GetImages() if !o.dryRun { - images = extractImagesFromADOLogs(logs) fmt.Printf("Extracted built images from ADO logs: %v\n", images) } else { fmt.Println("Running in dry-run mode. Skipping extracting images and results from ADO.") @@ -406,6 +417,13 @@ func buildInADO(o options) error { fmt.Println("adoResult GitHub output set") } + if o.buildReportPath != "" { + err = imagebuilder.WriteReportToFile(buildReport, o.buildReportPath) + if err != nil { + return fmt.Errorf("failed writing build report to file: %w", err) + } + } + // Handle the ADO pipeline run failure. if *pipelineRunResult == pipelines.RunResultValues.Failed || *pipelineRunResult == pipelines.RunResultValues.Unknown { return fmt.Errorf("build in ADO finished with status: %s", *pipelineRunResult) @@ -841,6 +859,8 @@ func (o *options) gatherOptions(flagSet *flag.FlagSet) *flag.FlagSet { flagSet.StringVar(&o.azureAccessToken, "azure-access-token", "", "Token used to authenticate against Azure DevOps API") flagSet.StringVar(&o.tagsOutputFile, "tags-output-file", "/generated-tags.json", "Path to file where generated tags will be written as JSON") flagSet.BoolVar(&o.useGoInternalSAPModules, "use-go-internal-sap-modules", false, "Allow access to Go internal modules in ADO backend") + flagSet.StringVar(&o.buildReportPath, "build-report-path", "", "Path to file where build report will be written as JSON") + flagSet.BoolVar(&o.adoStateOutput, "ado-state-output", false, "Set output variables with result of image-buidler exececution") return flagSet } @@ -904,9 +924,17 @@ func main() { if o.signOnly { err = signImages(&o, o.imagesToSign) if err != nil { + if o.adoStateOutput { + adopipelines.SetVariable("signing_success", false, false, true) + } + fmt.Println(err) os.Exit(1) } + + if o.adoStateOutput { + adopipelines.SetVariable("signing_success", true, false, true) + } os.Exit(0) } @@ -1102,24 +1130,3 @@ func getDockerfileDirPath(logger Logger, o options) (string, error) { logger.Debugw("dockerfile directory path constructed", "dockerfileDirPath", dockerfileDirPath) return dockerfileDirPath, err } - -// extractImagesFromADOLogs extract docker images from Azure DevOps logs to allow us prepare list of images built in ADO backend -// The list can be than saved and provided as input for developers to use in next steps of their workflows. -// ADO Logs that we fetch anyway are the simplest solution to get such list from ADO backend. -func extractImagesFromADOLogs(logs string) []string { - re := regexp.MustCompile(`--images-to-sign=(([a-z0-9]+(?:[.-][a-z0-9]+)*/)*([a-z0-9]+(?:[.-][a-z0-9]+)*)(?::[a-z0-9.-]+)?/([a-z0-9-]+)/([a-z0-9-]+)(?::[a-zA-Z0-9.-]+))`) - matches := re.FindAllStringSubmatch(logs, -1) - - images := []string{} - if len(matches) > 0 { - for _, match := range matches { - if len(match) > 1 { - images = append(images, match[1]) - } - } - } - - images = extractimageurls.UniqueImages(images) - - return images -} diff --git a/cmd/image-builder/main_test.go b/cmd/image-builder/main_test.go index b881e81eabf2..ed30e6a1b38d 100644 --- a/cmd/image-builder/main_test.go +++ b/cmd/image-builder/main_test.go @@ -870,105 +870,6 @@ func Test_prepareADOTemplateParameters(t *testing.T) { } } -func Test_extractImagesFromADOLogs(t *testing.T) { - tc := []struct { - name string - expectedImages []string - logs string - }{ - { - name: "sign image task log", - expectedImages: []string{"europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10854", "europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10852"}, - logs: `2024-05-28T09:49:07.8176591Z ============================================================================== - 2024-05-28T09:49:07.8176701Z Task : Docker - 2024-05-28T09:49:07.8176776Z Description : Build or push Docker images, login or logout, start or stop containers, or run a Docker command - 2024-05-28T09:49:07.8176902Z Version : 2.240.2 - 2024-05-28T09:49:07.8176962Z Author : Microsoft Corporation - 2024-05-28T09:49:07.8177044Z Help : https://aka.ms/azpipes-docker-tsg - 2024-05-28T09:49:07.8177121Z ============================================================================== - 2024-05-28T09:49:08.2220004Z [command]/usr/bin/docker run --env REPO_NAME=test-infra --env REPO_OWNER=kyma-project --env CI=true --env JOB_TYPE=presubmit --mount type=bind,src=/agent/_work/1/s/kaniko-build-config.yaml,dst=/kaniko-build-config.yaml --mount type=bind,src=/agent/_work/1/s/signify-prod-secret.yaml,dst=/secret-prod/secret.yaml europe-docker.pkg.dev/kyma-project/prod/image-builder:v20240515-f756e622 --sign-only --name=image-builder --context=. --dockerfile=cmd/image-builder/images/kaniko/Dockerfile --images-to-sign=europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10854 --images-to-sign=europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10852 --config=/kaniko-build-config.yaml - 2024-05-28T09:49:08.4547604Z sign images using services signify-prod - 2024-05-28T09:49:08.4548507Z signer signify-prod ignored, because is not enabled for a CI job of type: presubmit - 2024-05-28T09:49:08.4549247Z Start signing images europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10854 - 2024-05-28T09:49:08.5907215Z ##[section]Finishing: sign_images`, - }, - { - name: "prepare args and sign tasks log", - expectedImages: []string{"europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10696"}, - logs: `2024-05-28T07:36:31.8953681Z ##[section]Starting: prepare_build_and_sign_args - 2024-05-28T07:36:31.8958057Z ============================================================================== - 2024-05-28T07:36:31.8958168Z Task : Python script - 2024-05-28T07:36:31.8958230Z Description : Run a Python file or inline script - 2024-05-28T07:36:31.8958324Z Version : 0.237.1 - 2024-05-28T07:36:31.8958385Z Author : Microsoft Corporation - 2024-05-28T07:36:31.8958459Z Help : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/python-script - 2024-05-28T07:36:31.8958587Z ============================================================================== - 2024-05-28T07:36:33.6944350Z [command]/usr/bin/python /agent/_work/1/s/scripts/prepare_kaniko_and_sign_arguments.py --PreparedTagsFile /agent/_work/_temp/task_outputs/run_1716881791884.txt --ExportTags False --JobType presubmit --Context . --Dockerfile cmd/image-builder/images/kaniko/Dockerfile --ImageName image-builder --BuildArgs --Platforms --BuildConfigPath /agent/_work/1/s/kaniko-build-config.yaml - 2024-05-28T07:36:33.7426177Z ##[command]Read build config file: - 2024-05-28T07:36:33.7426567Z ##[group]Build config file content: - 2024-05-28T07:36:33.7430240Z ##[debug] {'tag-template': 'v{{ .Date }}-{{ .ShortSHA }}', 'registry': ['europe-docker.pkg.dev/kyma-project/prod'], 'dev-registry': ['europe-docker.pkg.dev/kyma-project/dev'], 'reproducible': False, 'log-format': 'json', 'ado-config': {'ado-organization-url': 'https://dev.azure.com/hyperspace-pipelines', 'ado-project-name': 'kyma', 'ado-pipeline-id': 14902}, 'cache': {'enabled': True, 'cache-repo': 'europe-docker.pkg.dev/kyma-project/cache/cache', 'cache-run-layers': True}, 'sign-config': {'enabled-signers': {'*': ['signify-prod']}, 'signers': [{'name': 'signify-prod', 'type': 'notary', 'job-type': ['postsubmit'], 'config': {'endpoint': 'https://signing.repositories.cloud.sap/signingsvc/sign', 'timeout': '5m', 'retry-timeout': '10s', 'secret': {'path': '/secret-prod/secret.yaml', 'type': 'signify'}}}]}} - 2024-05-28T07:36:33.7431327Z ##[endgroup] - 2024-05-28T07:36:33.7431542Z Running in presubmit mode - 2024-05-28T07:36:33.7432035Z ##[debug]Using dev registries: ['europe-docker.pkg.dev/kyma-project/dev'] - 2024-05-28T07:36:33.7432334Z ##[debug]Using build context: . - 2024-05-28T07:36:33.7432779Z ##[debug]Using Dockerfile: ./cmd/image-builder/images/kaniko/Dockerfile - 2024-05-28T07:36:33.7433181Z ##[debug]Using image name: image-builder - 2024-05-28T07:36:33.7433438Z ##[command]Using prepared OCI image tags: - 2024-05-28T07:36:33.7433924Z ##[debug]Prepared tags file content: [{"name":"default_tag","value":"PR-10696"}] - 2024-05-28T07:36:33.7434608Z - 2024-05-28T07:36:33.7435959Z ##[command]Setting job scope pipeline variable kanikoArgs with value: --cache=True --cache-run-layers=True --cache-repo=europe-docker.pkg.dev/kyma-project/cache/cache --context=dir:///workspace/. --dockerfile=/workspace/./cmd/image-builder/images/kaniko/Dockerfile --build-arg=default_tag=PR-10696 --destination=europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10696 - 2024-05-28T07:36:33.7438292Z ##[command]Setting job scope pipeline variable imagesToSign with value: --images-to-sign=europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10696 - 2024-05-28T07:36:33.7496968Z - 2024-05-28T07:36:33.7549637Z ##[section]Finishing: prepare_build_and_sign_args - 2024-05-28T07:38:12.4360275Z ##[section]Starting: sign_images - 2024-05-28T07:38:12.4364459Z ============================================================================== - 2024-05-28T07:38:12.4364568Z Task : Docker - 2024-05-28T07:38:12.4364645Z Description : Build or push Docker images, login or logout, start or stop containers, or run a Docker command - 2024-05-28T07:38:12.4364762Z Version : 2.240.2 - 2024-05-28T07:38:12.4364823Z Author : Microsoft Corporation - 2024-05-28T07:38:12.4364906Z Help : https://aka.ms/azpipes-docker-tsg - 2024-05-28T07:38:12.4364993Z ============================================================================== - 2024-05-28T07:38:12.8400661Z [command]/usr/bin/docker run --env REPO_NAME=test-infra --env REPO_OWNER=kyma-project --env CI=true --env JOB_TYPE=presubmit --mount type=bind,src=/agent/_work/1/s/kaniko-build-config.yaml,dst=/kaniko-build-config.yaml --mount type=bind,src=/agent/_work/1/s/signify-prod-secret.yaml,dst=/secret-prod/secret.yaml europe-docker.pkg.dev/kyma-project/prod/image-builder:v20240515-f756e622 --sign-only --name=image-builder --context=. --dockerfile=cmd/image-builder/images/kaniko/Dockerfile --images-to-sign=europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10696 --config=/kaniko-build-config.yaml - 2024-05-28T07:38:13.0389131Z sign images using services signify-prod - 2024-05-28T07:38:13.0389670Z signer signify-prod ignored, because is not enabled for a CI job of type: presubmit - 2024-05-28T07:38:13.0390290Z Start signing images europe-docker.pkg.dev/kyma-project/dev/image-builder:PR-10696 - 2024-05-28T07:38:13.1669325Z ##[section]Finishing: sign_images`, - }, - { - name: "prepare args and sign tasks logs only", - expectedImages: []string{"europe-docker.pkg.dev/kyma-project/dev/serverless-operator/ga:PR-1043"}, - logs: `2024-07-03T09:04:35.8674788Z ##[section]Starting: prepare_build_and_sign_args -2024-07-03T09:04:35.8681603Z ============================================================================== -2024-07-03T09:04:35.8681824Z Task : Python script -2024-07-03T09:04:35.8681947Z Description : Run a Python file or inline script -2024-07-03T09:04:35.8682099Z Version : 0.237.1 -2024-07-03T09:04:35.8682232Z Author : Microsoft Corporation -2024-07-03T09:04:35.8682356Z Help : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/python-script -2024-07-03T09:04:35.8682540Z ============================================================================== -2024-07-03T09:04:37.5031097Z [command]/usr/bin/python /agent/_work/1/s/scripts/prepare_kaniko_and_sign_arguments.py --PreparedTagsFile /agent/_work/_temp/task_outputs/run_1719997475854.txt --ExportTags False --JobType presubmit --Context . --Dockerfile components/operator/Dockerfile --ImageName serverless-operator/ga --BuildArgs --Platforms --BuildConfigPath /agent/_work/1/s/kaniko-build-config.yaml -2024-07-03T09:04:37.5527518Z ##[command]Read build config file: -Build config file content: -2024-07-03T09:04:37.5533715Z Running in presubmit mode -2024-07-03T09:04:37.5536685Z ##[command]Using prepared OCI image tags: -2024-07-03T09:04:37.5537692Z -2024-07-03T09:04:37.5539311Z ##[command]Setting job scope pipeline variable kanikoArgs with value: --cache=True --cache-run-layers=True --cache-repo=europe-docker.pkg.dev/kyma-project/cache/cache --context=dir:///repository/. --dockerfile=/repository/./components/operator/Dockerfile --build-arg=default_tag=PR-1043 --destination=europe-docker.pkg.dev/kyma-project/dev/serverless-operator/ga:PR-1043 -2024-07-03T09:04:37.5542470Z ##[command]Setting job scope pipeline variable imagesToSign with value: --images-to-sign=europe-docker.pkg.dev/kyma-project/dev/serverless-operator/ga:PR-1043 -2024-07-03T09:04:37.5597039Z -2024-07-03T09:04:37.5659445Z ##[section]Finishing: prepare_build_and_sign_args`, - }, - } - - for _, c := range tc { - t.Run(c.name, func(t *testing.T) { - actualImages := extractImagesFromADOLogs(c.logs) - - if !reflect.DeepEqual(actualImages, c.expectedImages) { - t.Errorf("Expected %v, but got %v", c.expectedImages, actualImages) - } - }) - } -} - type mockSignerFactory struct{} func (m *mockSignerFactory) NewSigner() (sign.Signer, error) { diff --git a/pkg/azuredevops/pipelines/variables.go b/pkg/azuredevops/pipelines/variables.go new file mode 100644 index 000000000000..20cf53c41c7b --- /dev/null +++ b/pkg/azuredevops/pipelines/variables.go @@ -0,0 +1,7 @@ +package pipelines + +import "fmt" + +func SetVariable(name string, value interface{}, isSecret bool, isOutput bool) { + fmt.Printf("##vso[task.setvariable variable=%s;issecret=%v;isoutput=%v]%v\n", name, isSecret, isOutput, value) +} diff --git a/pkg/imagebuilder/report.go b/pkg/imagebuilder/report.go new file mode 100644 index 000000000000..2b20102d8f0e --- /dev/null +++ b/pkg/imagebuilder/report.go @@ -0,0 +1,71 @@ +package imagebuilder + +import ( + "encoding/json" + "fmt" + "os" + "regexp" +) + +// reportRegex is a regular expression that matches the image build report +var reportRegex = regexp.MustCompile(`(?s)---IMAGE BUILD REPORT---\n(.*)\n---END OF IMAGE BUILD REPORT---`) + +type BuildReport struct { + Status string `json:"status"` + IsSigned bool `json:"signed"` + IsProduction bool `json:"is_production"` + ImageSpec ImageSpec `json:"image_spec"` +} + +type ImageSpec struct { + Name string `json:"image_name"` + Tags []string `json:"tags"` + RepositoryPath string `json:"repository_path"` +} + +func (br *BuildReport) GetImages() []string { + var images []string + + if br == nil { + return images + } + + for _, tag := range br.ImageSpec.Tags { + images = append(images, fmt.Sprintf("%s%s:%s", br.ImageSpec.RepositoryPath, br.ImageSpec.Name, tag)) + } + + return images +} + +func NewBuildReportFromLogs(log string) (*BuildReport, error) { + matches := reportRegex.FindStringSubmatch(log) + if len(matches) < 2 { + return nil, nil + } + + var report BuildReport + if err := json.Unmarshal([]byte(matches[1]), &report); err != nil { + return nil, err + } + + return &report, nil +} + +func WriteReportToFile(report *BuildReport, path string) error { + data, err := json.Marshal(report) + if err != nil { + return fmt.Errorf("failed to marshal report: %w", err) + } + + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + if _, err := file.Write(data); err != nil { + return fmt.Errorf("failed to write report to file: %w", err) + } + + return nil +} diff --git a/pkg/imagebuilder/report_suite_test.go b/pkg/imagebuilder/report_suite_test.go new file mode 100644 index 000000000000..bb81e48c6db5 --- /dev/null +++ b/pkg/imagebuilder/report_suite_test.go @@ -0,0 +1,13 @@ +package imagebuilder + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestPipelines(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Image Builder Suite") +} diff --git a/pkg/imagebuilder/report_test.go b/pkg/imagebuilder/report_test.go new file mode 100644 index 000000000000..076a8e0edb07 --- /dev/null +++ b/pkg/imagebuilder/report_test.go @@ -0,0 +1,89 @@ +package imagebuilder + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Report", func() { + Describe("NewReportFromLogs", func() { + logs := `Starting: prepare_image_build_report +============================================================================== +Task : Python script +Description : Run a Python file or inline script +Version : 0.248.1 +Author : Microsoft Corporation +Help : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/python-script +============================================================================== +/usr/bin/python /home/vsts/work/1/s/scripts/prepare_image_build_report.py --image-build-report-file /home/vsts/work/1/s/image-report.json --image-name ginkgo-test-image/ginkgo --sign-step-succeeded true --job-status Succeeded --image-build-report-file /home/vsts/work/_temp/generated-tags.json --images-to-sign=europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:1.23.0-50049457 --images-to-sign=europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:wartosc --images-to-sign=europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:innytag --images-to-sign=europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:v20250129-50049457 --images-to-sign=europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:1.23.0 +---IMAGE BUILD REPORT--- +{ + "status": "Succeeded", + "signed": true, + "is_production": true, + "image_spec": { + "image_name": "ginkgo-test-image/ginkgo", + "tags": [ + "1.23.0-50049457", + "wartosc", + "innytag", + "v20250129-50049457", + "1.23.0" + ], + "repository_path": "europe-docker.pkg.dev/kyma-project/prod/" + } +} +---END OF IMAGE BUILD REPORT--- + +Finishing: prepare_image_build_report` + expectedReport := &BuildReport{ + Status: "Succeeded", + IsSigned: true, + IsProduction: true, + ImageSpec: ImageSpec{ + Name: "ginkgo-test-image/ginkgo", + Tags: []string{"1.23.0-50049457", "wartosc", "innytag", "v20250129-50049457", "1.23.0"}, + RepositoryPath: "europe-docker.pkg.dev/kyma-project/prod/", + }, + } + + It("parses the image build report", func() { + actual, err := NewBuildReportFromLogs(logs) + Expect(err).ToNot(HaveOccurred()) + + Expect(actual).To(Equal(expectedReport)) + }) + }) + + Describe("GetImages", func() { + report := &BuildReport{ + ImageSpec: ImageSpec{ + Name: "ginkgo-test-image/ginkgo", + Tags: []string{"1.23.0-50049457", "wartosc", "innytag", "v20250129-50049457", "1.23.0"}, + RepositoryPath: "europe-docker.pkg.dev/kyma-project/prod/", + }, + } + + It("returns the list of images", func() { + expectedImages := []string{ + "europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:1.23.0-50049457", + "europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:wartosc", + "europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:innytag", + "europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:v20250129-50049457", + "europe-docker.pkg.dev/kyma-project/prod/ginkgo-test-image/ginkgo:1.23.0", + } + + Expect(report.GetImages()).To(Equal(expectedImages)) + }) + + It("returns an empty list if there are no tags", func() { + report.ImageSpec.Tags = []string{} + Expect(report.GetImages()).To(BeEmpty()) + }) + + It("returns an empty list if build report is nil", func() { + var nilReport *BuildReport + Expect(nilReport.GetImages()).To(BeEmpty()) + }) + }) +})