From 78bfe26727ff1a2e424c3ee7171663ac82ffaece Mon Sep 17 00:00:00 2001 From: Faycal SKHIRI Date: Tue, 5 May 2026 11:38:39 +0200 Subject: [PATCH 1/7] feat(buildsettings): add dockerBuild support in CreateBuildSettingsInfo dockerBuild was falling through to the default branch, emitting a warning and returning empty buildSettingsInfo. Add it as a first-class supported tool alongside kanikoExecute and cnbBuild. --- pkg/buildsettings/buildSettings.go | 5 +++++ pkg/buildsettings/buildSettings_test.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/pkg/buildsettings/buildSettings.go b/pkg/buildsettings/buildSettings.go index 7717bd21e1..0e9bd2f85c 100644 --- a/pkg/buildsettings/buildSettings.go +++ b/pkg/buildsettings/buildSettings.go @@ -10,6 +10,7 @@ import ( ) type BuildSettings struct { + DockerBuild []BuildOptions `json:"dockerBuild,omitempty"` GolangBuild []BuildOptions `json:"golangBuild,omitempty"` GradleExecuteBuild []BuildOptions `json:"gradleExecuteBuild,omitempty"` HelmExecute []BuildOptions `json:"helmExecute,omitempty"` @@ -78,6 +79,10 @@ func CreateBuildSettingsInfo(config *BuildOptions, buildTool string) (string, er settings = append(settings, currentBuildSettingsInfo) var err error switch buildTool { + case "dockerBuild": + jsonResult, err = json.Marshal(BuildSettings{ + DockerBuild: settings, + }) case "golangBuild": jsonResult, err = json.Marshal(BuildSettings{ GolangBuild: settings, diff --git a/pkg/buildsettings/buildSettings_test.go b/pkg/buildsettings/buildSettings_test.go index 264d2145f2..00336ea655 100644 --- a/pkg/buildsettings/buildSettings_test.go +++ b/pkg/buildsettings/buildSettings_test.go @@ -69,6 +69,11 @@ func TestCreateBuildSettingsInfo(t *testing.T) { buildTool: "cnbBuild", expected: "{\"cnbBuild\":[{\"dockerImage\":\"builder:latest\"}]}", }, + { + config: BuildOptions{DockerImage: "docker:latest"}, + buildTool: "dockerBuild", + expected: "{\"dockerBuild\":[{\"dockerImage\":\"docker:latest\"}]}", + }, } for _, testCase := range testTableConfig { From 6be1e497587f69fc5e3b693b7928608f33ee7f61 Mon Sep 17 00:00:00 2001 From: Faycal SKHIRI Date: Wed, 6 May 2026 11:56:18 +0200 Subject: [PATCH 2/7] docs(dockerBuild): add step documentation page --- documentation/docs/steps/dockerBuild.md | 82 +++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 documentation/docs/steps/dockerBuild.md diff --git a/documentation/docs/steps/dockerBuild.md b/documentation/docs/steps/dockerBuild.md new file mode 100644 index 0000000000..c24a3c6c5d --- /dev/null +++ b/documentation/docs/steps/dockerBuild.md @@ -0,0 +1,82 @@ +# ${docGenStepName} + +## ${docGenDescription} + +## Prerequisites + +This step is intended for use in **GitHub Actions**, where Docker + BuildKit are natively available on the runner. +It is not supported on Jenkins or Azure DevOps runners. + +When pushing to a container registry, you need to provide credentials via one of these approaches: + +* Pass `containerRegistryUser` and `containerRegistryPassword` parameters — the step will write a `config.json` automatically. +* Provide a pre-existing `config.json` via the `dockerConfigJSON` parameter. + +## ${docJenkinsPluginDependencies} + +## Example + +### Building a single image + +```yaml +steps: + dockerBuild: + containerImageName: myImage + containerRegistryUrl: my.registry.example.com + containerRegistryUser: myUser + containerRegistryPassword: myPassword +``` + +### Building multiple images from sub-directories + +```yaml +steps: + dockerBuild: + containerImageName: myImage + containerMultiImageBuild: true +``` + +With the following Dockerfiles present in the repository: + +* `sub1/Dockerfile` +* `sub2/Dockerfile` + +The following images will be built and pushed: + +* `myImage-sub1` +* `myImage-sub2` + +### Using registry mirrors + +```yaml +steps: + dockerBuild: + containerImageName: myImage + registryMirrors: + - mirror.gcr.io + - mycompany-docker-virtual.jfrog.io +``` + +### Enabling BOM creation + +```yaml +steps: + dockerBuild: + containerImageName: myImage + createBOM: true +``` + +### Opting in (replacing kanikoExecute) + +By default `kanikoExecute` is active and `dockerBuild` is off. To switch: + +```yaml +stages: + Build: + dockerBuild: true + kanikoExecute: false +``` + +## ${docGenParameters} + +## ${docGenConfiguration} From e38e4e715eaa8dea67a16a6df4661d72c5e190e9 Mon Sep 17 00:00:00 2001 From: Faycal SKHIRI Date: Wed, 6 May 2026 13:45:29 +0200 Subject: [PATCH 3/7] docs(kanikoExecute): point GitHub Actions users to dockerBuild kanikoExecute is Jenkins/Kubernetes-centric and requires a sidecar container. Users running on GitHub Actions have a simpler native alternative (dockerBuild via Docker BuildKit). Add a visible note at the top of the doc so they discover it before going deep into Kaniko setup. --- documentation/docs/steps/kanikoExecute.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/docs/steps/kanikoExecute.md b/documentation/docs/steps/kanikoExecute.md index cceeb3c8ab..e45bf270cc 100644 --- a/documentation/docs/steps/kanikoExecute.md +++ b/documentation/docs/steps/kanikoExecute.md @@ -2,6 +2,9 @@ ## ${docGenDescription} +> **Note:** For pipelines running on **GitHub Actions**, consider using the [`dockerBuild`](dockerBuild.md) step instead. +> It uses Docker BuildKit natively and does not require a Kaniko sidecar container. + ## Prerequisites When pushing to a container registry, you need to maintain the respective credentials in your Jenkins credentials store: From 7997aeba613a640913c0e1576494325754a8c677 Mon Sep 17 00:00:00 2001 From: Faycal SKHIRI Date: Mon, 11 May 2026 11:48:15 +0200 Subject: [PATCH 4/7] fix(buildsettings): align struct literal field order with definition --- pkg/buildsettings/buildSettings.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/buildsettings/buildSettings.go b/pkg/buildsettings/buildSettings.go index 0e9bd2f85c..1f7637bca6 100644 --- a/pkg/buildsettings/buildSettings.go +++ b/pkg/buildsettings/buildSettings.go @@ -41,12 +41,13 @@ func CreateBuildSettingsInfo(config *BuildOptions, buildTool string) (string, er dockerImage = envDockerImage } + // BuildSettingsInfo is intentionally omitted to avoid recursive nesting. currentBuildSettingsInfo := BuildOptions{ - CreateBOM: config.CreateBOM, - GlobalSettingsFile: config.GlobalSettingsFile, - LogSuccessfulMavenTransfers: config.LogSuccessfulMavenTransfers, Profiles: config.Profiles, Publish: config.Publish, + CreateBOM: config.CreateBOM, + LogSuccessfulMavenTransfers: config.LogSuccessfulMavenTransfers, + GlobalSettingsFile: config.GlobalSettingsFile, DefaultNpmRegistry: config.DefaultNpmRegistry, DockerImage: dockerImage, } From 331926fd25a9220043ce689024c8aae0eaca2c78 Mon Sep 17 00:00:00 2001 From: Faycal SKHIRI Date: Mon, 11 May 2026 11:48:35 +0200 Subject: [PATCH 5/7] test(buildsettings): add coverage for merge path and env override --- pkg/buildsettings/buildSettings_test.go | 115 ++++++++++++++++++++---- 1 file changed, 98 insertions(+), 17 deletions(-) diff --git a/pkg/buildsettings/buildSettings_test.go b/pkg/buildsettings/buildSettings_test.go index 00336ea655..e2296faecf 100644 --- a/pkg/buildsettings/buildSettings_test.go +++ b/pkg/buildsettings/buildSettings_test.go @@ -1,14 +1,19 @@ +//go:build unit + package buildsettings import ( + "os" "testing" "github.com/stretchr/testify/assert" ) func TestCreateBuildSettingsInfo(t *testing.T) { + t.Parallel() - t.Run("test build settings cpe with no previous and existing values", func(t *testing.T) { + t.Run("fresh path - no previous build settings", func(t *testing.T) { + t.Parallel() testTableConfig := []struct { config BuildOptions buildTool string @@ -17,62 +22,57 @@ func TestCreateBuildSettingsInfo(t *testing.T) { { config: BuildOptions{CreateBOM: true}, buildTool: "golangBuild", - expected: "{\"golangBuild\":[{\"createBOM\":true}]}", + expected: `{"golangBuild":[{"createBOM":true}]}`, }, { config: BuildOptions{DockerImage: "golang:latest"}, buildTool: "golangBuild", - expected: "{\"golangBuild\":[{\"dockerImage\":\"golang:latest\"}]}", + expected: `{"golangBuild":[{"dockerImage":"golang:latest"}]}`, }, { config: BuildOptions{CreateBOM: true, DockerImage: "gradle:latest"}, buildTool: "gradleExecuteBuild", - expected: "{\"gradleExecuteBuild\":[{\"createBOM\":true,\"dockerImage\":\"gradle:latest\"}]}", + expected: `{"gradleExecuteBuild":[{"createBOM":true,"dockerImage":"gradle:latest"}]}`, }, { config: BuildOptions{Publish: true}, buildTool: "helmExecute", - expected: "{\"helmExecute\":[{\"publish\":true}]}", + expected: `{"helmExecute":[{"publish":true}]}`, }, { config: BuildOptions{Publish: true}, buildTool: "kanikoExecute", - expected: "{\"kanikoExecute\":[{\"publish\":true}]}", + expected: `{"kanikoExecute":[{"publish":true}]}`, }, { config: BuildOptions{Profiles: []string{"profile1", "profile2"}, CreateBOM: true}, buildTool: "mavenBuild", - expected: "{\"mavenBuild\":[{\"profiles\":[\"profile1\",\"profile2\"],\"createBOM\":true}]}", - }, - { - config: BuildOptions{Profiles: []string{"profile1", "profile2"}, CreateBOM: true, BuildSettingsInfo: "{\"mavenBuild\":[{\"createBOM\":true}]}"}, - buildTool: "mavenBuild", - expected: "{\"mavenBuild\":[{\"createBOM\":true},{\"profiles\":[\"profile1\",\"profile2\"],\"createBOM\":true}]}", + expected: `{"mavenBuild":[{"profiles":["profile1","profile2"],"createBOM":true}]}`, }, { config: BuildOptions{Profiles: []string{"release.build"}, Publish: true, GlobalSettingsFile: "http://nexus.test:8081/nexus/"}, buildTool: "mtaBuild", - expected: "{\"mtaBuild\":[{\"profiles\":[\"release.build\"],\"publish\":true,\"globalSettingsFile\":\"http://nexus.test:8081/nexus/\"}]}", + expected: `{"mtaBuild":[{"profiles":["release.build"],"publish":true,"globalSettingsFile":"http://nexus.test:8081/nexus/"}]}`, }, { config: BuildOptions{CreateBOM: true}, buildTool: "pythonBuild", - expected: "{\"pythonBuild\":[{\"createBOM\":true}]}", + expected: `{"pythonBuild":[{"createBOM":true}]}`, }, { config: BuildOptions{CreateBOM: true}, buildTool: "npmExecuteScripts", - expected: "{\"npmExecuteScripts\":[{\"createBOM\":true}]}", + expected: `{"npmExecuteScripts":[{"createBOM":true}]}`, }, { config: BuildOptions{DockerImage: "builder:latest"}, buildTool: "cnbBuild", - expected: "{\"cnbBuild\":[{\"dockerImage\":\"builder:latest\"}]}", + expected: `{"cnbBuild":[{"dockerImage":"builder:latest"}]}`, }, { config: BuildOptions{DockerImage: "docker:latest"}, buildTool: "dockerBuild", - expected: "{\"dockerBuild\":[{\"dockerImage\":\"docker:latest\"}]}", + expected: `{"dockerBuild":[{"dockerImage":"docker:latest"}]}`, }, } @@ -83,4 +83,85 @@ func TestCreateBuildSettingsInfo(t *testing.T) { } }) + t.Run("fresh path - unsupported buildTool returns empty string without error", func(t *testing.T) { + t.Parallel() + config := BuildOptions{CreateBOM: true} + result, err := CreateBuildSettingsInfo(&config, "unsupportedTool") + assert.NoError(t, err) + assert.Empty(t, result) + }) +} + +func TestCreateBuildSettingsInfo_MergePath(t *testing.T) { + t.Parallel() + + t.Run("appends to existing key when buildTool already present", func(t *testing.T) { + t.Parallel() + config := BuildOptions{ + Profiles: []string{"profile1", "profile2"}, + CreateBOM: true, + BuildSettingsInfo: `{"mavenBuild":[{"createBOM":true}]}`, + } + result, err := CreateBuildSettingsInfo(&config, "mavenBuild") + assert.NoError(t, err) + assert.Equal(t, `{"mavenBuild":[{"createBOM":true},{"profiles":["profile1","profile2"],"createBOM":true}]}`, result) + }) + + t.Run("adds new key alongside existing when buildTool not yet present", func(t *testing.T) { + t.Parallel() + config := BuildOptions{ + CreateBOM: true, + BuildSettingsInfo: `{"mavenBuild":[{"createBOM":true}]}`, + } + result, err := CreateBuildSettingsInfo(&config, "golangBuild") + assert.NoError(t, err) + assert.Contains(t, result, `"mavenBuild":[{"createBOM":true}]`) + assert.Contains(t, result, `"golangBuild":[{"createBOM":true}]`) + }) + + t.Run("returns error on malformed BuildSettingsInfo JSON", func(t *testing.T) { + t.Parallel() + config := BuildOptions{ + BuildSettingsInfo: `{not-valid-json`, + } + _, err := CreateBuildSettingsInfo(&config, "golangBuild") + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to unmarshal existing build settings json") + }) + + t.Run("unsupported buildTool silently adds new key in merge path", func(t *testing.T) { + t.Parallel() + config := BuildOptions{ + CreateBOM: true, + BuildSettingsInfo: `{"mavenBuild":[{"createBOM":true}]}`, + } + result, err := CreateBuildSettingsInfo(&config, "unknownTool") + assert.NoError(t, err) + assert.Contains(t, result, `"mavenBuild"`) + assert.Contains(t, result, `"unknownTool"`) + }) +} + +// Env-override tests are sequential because os.Setenv affects the whole process. +func TestCreateBuildSettingsInfo_EnvDockerImageOverride(t *testing.T) { + t.Run("fresh path - PIPER_dockerImage env overrides config.DockerImage", func(t *testing.T) { + os.Setenv("PIPER_dockerImage", "override:fresh") + defer os.Unsetenv("PIPER_dockerImage") + config := BuildOptions{DockerImage: "original:fresh"} + result, err := CreateBuildSettingsInfo(&config, "golangBuild") + assert.NoError(t, err) + assert.Equal(t, `{"golangBuild":[{"dockerImage":"override:fresh"}]}`, result) + }) + + t.Run("merge path - PIPER_dockerImage applies to appended entry only, historical entry preserved", func(t *testing.T) { + os.Setenv("PIPER_dockerImage", "override:v2") + defer os.Unsetenv("PIPER_dockerImage") + config := BuildOptions{ + DockerImage: "original:v1", + BuildSettingsInfo: `{"golangBuild":[{"dockerImage":"original:v1"}]}`, + } + result, err := CreateBuildSettingsInfo(&config, "golangBuild") + assert.NoError(t, err) + assert.Equal(t, `{"golangBuild":[{"dockerImage":"original:v1"},{"dockerImage":"override:v2"}]}`, result) + }) } From 7aa2622d296b7f3191da0660a1dc71d759b1e22f Mon Sep 17 00:00:00 2001 From: Faycal SKHIRI Date: Mon, 11 May 2026 11:48:59 +0200 Subject: [PATCH 6/7] docs(dockerBuild): fix incomplete multi-image example, registry fields required --- documentation/docs/steps/dockerBuild.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/documentation/docs/steps/dockerBuild.md b/documentation/docs/steps/dockerBuild.md index c24a3c6c5d..4c681a38f0 100644 --- a/documentation/docs/steps/dockerBuild.md +++ b/documentation/docs/steps/dockerBuild.md @@ -29,10 +29,16 @@ steps: ### Building multiple images from sub-directories +`containerRegistryUrl`, `containerRegistryUser`, and `containerRegistryPassword` are required — +the multi-image build path will fail immediately if `containerRegistryUrl` is empty. + ```yaml steps: dockerBuild: containerImageName: myImage + containerRegistryUrl: my.registry.example.com + containerRegistryUser: myUser + containerRegistryPassword: myPassword containerMultiImageBuild: true ``` From 7f7a1810688ba87c510303ddcf895f36477800a7 Mon Sep 17 00:00:00 2001 From: Faycal SKHIRI Date: Fri, 29 May 2026 10:40:09 +0200 Subject: [PATCH 7/7] docs(dockerBuild): mark step as beta, hide from docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dockerBuild is not yet GA — expose it too early would invite support requests for an unstable interface. The page is excluded from search and not listed in the mkdocs nav, making it discoverable only via direct URL. The forward-reference in kanikoExecute is commented out so it can be uncommented cleanly when dockerBuild reaches GA. --- documentation/docs/steps/dockerBuild.md | 8 ++++++++ documentation/docs/steps/kanikoExecute.md | 2 ++ 2 files changed, 10 insertions(+) diff --git a/documentation/docs/steps/dockerBuild.md b/documentation/docs/steps/dockerBuild.md index 4c681a38f0..7fadf0a451 100644 --- a/documentation/docs/steps/dockerBuild.md +++ b/documentation/docs/steps/dockerBuild.md @@ -1,5 +1,13 @@ +--- +search: + exclude: true +--- + # ${docGenStepName} +!!! note + This step is in **beta**. Breaking changes may occur before it reaches general availability. + ## ${docGenDescription} ## Prerequisites diff --git a/documentation/docs/steps/kanikoExecute.md b/documentation/docs/steps/kanikoExecute.md index e45bf270cc..0e140940a2 100644 --- a/documentation/docs/steps/kanikoExecute.md +++ b/documentation/docs/steps/kanikoExecute.md @@ -2,8 +2,10 @@ ## ${docGenDescription} + ## Prerequisites