From 5e45eca71732ea4702e237dbfb8e9923f3705f7e Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:57:11 +0100 Subject: [PATCH] opt to merge output for multi-platforms build into a single artifact Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/.test-bake.yml | 74 +++++++++++++++++++++++++++++++ .github/workflows/.test-build.yml | 73 ++++++++++++++++++++++++++++++ .github/workflows/bake.yml | 27 +++++++---- .github/workflows/build.yml | 27 +++++++---- .github/workflows/verify.yml | 7 ++- README.md | 2 + 6 files changed, 193 insertions(+), 17 deletions(-) diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index 6264422..d68cb51 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -543,3 +543,77 @@ jobs: - registry: registry-1-stage.docker.io username: ${{ vars.DOCKERHUB_STAGE_USERNAME }} password: ${{ secrets.DOCKERHUB_STAGE_TOKEN }} + + bake-local-nomerge: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + artifact-name: bake-nomerge-output + artifact-upload: true + artifact-merge: false + context: test + output: local + sbom: true + sign: ${{ github.event_name != 'pull_request' }} + target: hello-cross + + bake-local-nomerge-verify: + uses: ./.github/workflows/verify.yml + needs: + - bake-local-nomerge + with: + builder-outputs: ${{ toJSON(needs.bake-local-nomerge.outputs) }} + + bake-local-nomerge-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local-nomerge + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local-nomerge.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-local-single-nomerge: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + artifact-name: bake-single-nomerge-output + artifact-upload: true + artifact-merge: false + context: test + output: local + sbom: true + sign: ${{ github.event_name != 'pull_request' }} + target: hello + + bake-local-single-nomerge-verify: + uses: ./.github/workflows/verify.yml + needs: + - bake-local-single-nomerge + with: + builder-outputs: ${{ toJSON(needs.bake-local-single-nomerge.outputs) }} + + bake-local-single-nomerge-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local-single-nomerge + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local-single-nomerge.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); diff --git a/.github/workflows/.test-build.yml b/.github/workflows/.test-build.yml index 1775397..b1cc5db 100644 --- a/.github/workflows/.test-build.yml +++ b/.github/workflows/.test-build.yml @@ -574,3 +574,76 @@ jobs: - registry: registry-1-stage.docker.io username: ${{ vars.DOCKERHUB_STAGE_USERNAME }} password: ${{ secrets.DOCKERHUB_STAGE_TOKEN }} + + build-local-nomerge: + uses: ./.github/workflows/build.yml + permissions: + contents: read + id-token: write + with: + artifact-name: build-nomerge-output + artifact-upload: true + artifact-merge: false + file: test/hello.Dockerfile + output: local + platforms: linux/amd64,linux/arm64 + sbom: true + sign: ${{ github.event_name != 'pull_request' }} + + build-local-nomerge-verify: + uses: ./.github/workflows/verify.yml + needs: + - build-local-nomerge + with: + builder-outputs: ${{ toJSON(needs.build-local-nomerge.outputs) }} + + build-local-nomerge-outputs: + runs-on: ubuntu-24.04 + needs: + - build-local-nomerge + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-local-nomerge.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + build-local-single-nomerge: + uses: ./.github/workflows/build.yml + permissions: + contents: read + id-token: write + with: + artifact-name: build-single-nomerge-output + artifact-upload: true + artifact-merge: false + file: test/hello.Dockerfile + output: local + sbom: true + sign: ${{ github.event_name != 'pull_request' }} + + build-local-single-nomerge-verify: + uses: ./.github/workflows/verify.yml + needs: + - build-local-single-nomerge + with: + builder-outputs: ${{ toJSON(needs.build-local-single-nomerge.outputs) }} + + build-local-single-nomerge-outputs: + runs-on: ubuntu-24.04 + needs: + - build-local-nomerge + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-local-single-nomerge.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index e41e905..ddd6041 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -28,6 +28,11 @@ on: description: "Upload build output GitHub artifact (for local output)" required: false default: false + artifact-merge: + type: boolean + description: "Merge output for multi-platforms build into a single artifact (for local output)" + required: false + default: true cache: type: boolean description: "Enable cache to GitHub Actions cache backend" @@ -138,6 +143,9 @@ on: artifact-name: description: "Name of the uploaded artifact (for local output)" value: ${{ jobs.finalize.outputs.artifact-name }} + artifact-merge: + description: "Whether multiple artifacts were merged (for local output)" + value: ${{ jobs.finalize.outputs.artifact-merge }} output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} @@ -560,6 +568,7 @@ jobs: INPUT_PLATFORM: ${{ matrix.platform }} INPUT_SBOM-IMAGE: ${{ env.SBOM_IMAGE }} INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} + INPUT_ARTIFACT-MERGE: ${{ inputs.artifact-merge }} INPUT_CACHE: ${{ inputs.cache }} INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} INPUT_CACHE-MODE: ${{ inputs.cache-mode }} @@ -587,10 +596,10 @@ jobs: const inpPlatform = core.getInput('platform'); const platformPairSuffix = inpPlatform ? `-${inpPlatform.replace(/\//g, '-')}` : ''; - core.setOutput('platform-pair-suffix', platformPairSuffix); const inpSbomImage = core.getInput('sbom-image'); const inpLocalExportDir = core.getInput('local-export-dir'); + const inpArtifactMerge = core.getBooleanInput('artifact-merge'); const inpCache = core.getBooleanInput('cache'); const inpCacheScope = core.getInput('cache-scope'); @@ -617,6 +626,12 @@ jobs: core.setOutput('source', bakeSource); }); + if (platformPairSuffix) { + core.setOutput('artifact-suffix', platformPairSuffix); + } else if (inpArtifactMerge) { + core.setOutput('artifact-suffix', '0'); + } + const sbom = inpSbom ? `generator=${inpSbomImage}` : 'false'; await core.group(`Set sbom`, async () => { core.info(sbom); @@ -852,7 +867,7 @@ jobs: if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: - name: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix || '0' }} + name: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.artifact-suffix }} path: ${{ env.LOCAL_EXPORT_DIR }} if-no-files-found: error - @@ -863,22 +878,17 @@ jobs: INPUT_INDEX: ${{ matrix.index }} INPUT_VERIFY-COMMANDS: ${{ steps.signing-attestation-manifests.outputs.verify-commands || steps.signing-local-artifacts.outputs.verify-commands }} INPUT_IMAGE-DIGEST: ${{ steps.get-image-digest.outputs.digest }} - INPUT_ARTIFACT-NAME: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix }} - INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} INPUT_SIGNED: ${{ needs.prepare.outputs.sign }} with: script: | const inpIndex = core.getInput('index'); const inpVerifyCommands = core.getInput('verify-commands'); const inpImageDigest = core.getInput('image-digest'); - const inpArtifactName = core.getInput('artifact-name'); - const inpArtifactUpload = core.getBooleanInput('artifact-upload'); const inpSigned = core.getBooleanInput('signed'); const result = { verifyCommands: inpVerifyCommands, imageDigest: inpImageDigest, - artifactName: inpArtifactUpload ? inpArtifactName : '', signed: inpSigned } core.info(JSON.stringify(result, null, 2)); @@ -892,6 +902,7 @@ jobs: cosign-version: ${{ env.COSIGN_VERSION }} cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} + artifact-merge: ${{ inputs.artifact-merge }} output-type: ${{ inputs.output }} signed: ${{ needs.prepare.outputs.sign }} needs: @@ -973,7 +984,7 @@ jobs: } - name: Merge artifacts - if: ${{ inputs.output == 'local' && inputs.artifact-upload }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload && inputs.artifact-merge }} uses: actions/upload-artifact/merge@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 21e589e..999f1f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,11 @@ on: description: "Upload build output GitHub artifact (for local output)" required: false default: false + artifact-merge: + type: boolean + description: "Merge output for multi-platforms build into a single artifact (for local output)" + required: false + default: true annotations: type: string description: "List of annotations to set to the image (for image output)" @@ -141,6 +146,9 @@ on: artifact-name: description: "Name of the uploaded artifact (for local output)" value: ${{ jobs.finalize.outputs.artifact-name }} + artifact-merge: + description: "Whether multiple artifacts were merged (for local output)" + value: ${{ jobs.finalize.outputs.artifact-merge }} output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} @@ -497,6 +505,7 @@ jobs: INPUT_SBOM-IMAGE: ${{ env.SBOM_IMAGE }} INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} INPUT_DISTRIBUTE: ${{ inputs.distribute }} + INPUT_ARTIFACT-MERGE: ${{ inputs.artifact-merge }} INPUT_ANNOTATIONS: ${{ inputs.annotations }} INPUT_CACHE: ${{ inputs.cache }} INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} @@ -519,11 +528,11 @@ jobs: const inpPlatform = core.getInput('platform'); const platformPairSuffix = inpPlatform ? `-${inpPlatform.replace(/\//g, '-')}` : ''; - core.setOutput('platform-pair-suffix', platformPairSuffix); const inpSbomImage = core.getInput('sbom-image'); const inpLocalExportDir = core.getInput('local-export-dir'); const inpDistribute = core.getBooleanInput('distribute'); + const inpArtifactMerge = core.getBooleanInput('artifact-merge'); const inpAnnotations = core.getMultilineInput('annotations'); const inpCache = core.getBooleanInput('cache'); @@ -546,6 +555,12 @@ jobs: const buildContext = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}.git#${process.env.GITHUB_REF}:${inpContext}`; core.setOutput('context', buildContext); + if (platformPairSuffix) { + core.setOutput('artifact-suffix', platformPairSuffix); + } else if (inpArtifactMerge) { + core.setOutput('artifact-suffix', '0'); + } + switch (inpOutput) { case 'image': if (inpMetaImages.length == 0) { @@ -717,7 +732,7 @@ jobs: if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: - name: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix || '0' }} + name: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.artifact-suffix }} path: ${{ env.LOCAL_EXPORT_DIR }} if-no-files-found: error - @@ -728,22 +743,17 @@ jobs: INPUT_INDEX: ${{ matrix.index }} INPUT_VERIFY-COMMANDS: ${{ steps.signing-attestation-manifests.outputs.verify-commands || steps.signing-local-artifacts.outputs.verify-commands }} INPUT_IMAGE-DIGEST: ${{ steps.build.outputs.digest }} - INPUT_ARTIFACT-NAME: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix }} - INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} INPUT_SIGNED: ${{ needs.prepare.outputs.sign }} with: script: | const inpIndex = core.getInput('index'); const inpVerifyCommands = core.getInput('verify-commands'); const inpImageDigest = core.getInput('image-digest'); - const inpArtifactName = core.getInput('artifact-name'); - const inpArtifactUpload = core.getBooleanInput('artifact-upload'); const inpSigned = core.getBooleanInput('signed'); const result = { verifyCommands: inpVerifyCommands, imageDigest: inpImageDigest, - artifactName: inpArtifactUpload ? inpArtifactName : '', signed: inpSigned } core.info(JSON.stringify(result, null, 2)); @@ -757,6 +767,7 @@ jobs: cosign-version: ${{ env.COSIGN_VERSION }} cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} + artifact-merge: ${{ inputs.artifact-merge }} output-type: ${{ inputs.output }} signed: ${{ needs.prepare.outputs.sign }} needs: @@ -837,7 +848,7 @@ jobs: } - name: Merge artifacts - if: ${{ inputs.output == 'local' && inputs.artifact-upload }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload && inputs.artifact-merge }} uses: actions/upload-artifact/merge@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }} diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 453162f..9d1a1e3 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -33,6 +33,7 @@ jobs: const cosignVersion = builderOutputs['cosign-version']; const cosignVerifyCommands = builderOutputs['cosign-verify-commands']; const artifactName = builderOutputs['artifact-name']; + const artifactMerge = builderOutputs['artifact-merge'] === 'true'; const outputType = builderOutputs['output-type']; const signed = builderOutputs['signed'] === 'true'; if (!signed) { @@ -44,7 +45,9 @@ jobs: core.setOutput('cosign-version', cosignVersion); core.setOutput('cosign-verify-commands', cosignVerifyCommands); - core.setOutput('artifact-name', artifactName); + core.setOutput('artifact-name', artifactMerge ? artifactName : ''); + core.setOutput('artifact-pattern', artifactMerge ? '' : `${artifactName}*`); + core.setOutput('artifact-merge-multiple', artifactMerge ? 'false' : 'true'); core.setOutput('output-type', outputType); core.setOutput('signed', signed); - @@ -92,6 +95,8 @@ jobs: uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: ${{ steps.vars.outputs.artifact-name }} + pattern: ${{ steps.vars.outputs.artifact-pattern }} + merge-multiple: ${{ steps.vars.outputs.artifact-merge-multiple }} - name: Verify signatures if: ${{ steps.vars.outputs.signed == 'true' }} diff --git a/README.md b/README.md index 7776045..0f55b0e 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,7 @@ on: | `setup-qemu` | Bool | `false` | Runs the `setup-qemu-action` step to install QEMU static binaries | | `artifact-name` | String | `docker-github-builder-assets` | Name of the uploaded GitHub artifact (for `local` output) | | `artifact-upload` | Bool | `false` | Upload build output GitHub artifact (for `local` output) | +| `artifact-merge` | Bool | `true` | Merge output for multi-platforms build into a single artifact (for `local` output) | | `annotations` | List | | List of annotations to set to the image (for `image` output) | | `build-args` | List | `auto` | List of [build-time variables](https://docs.docker.com/engine/reference/commandline/buildx_build/#build-arg). If you want to set a build-arg through an environment variable, use the `envs` input | | `cache` | Bool | `false` | Enable [GitHub Actions cache](https://docs.docker.com/build/cache/backends/gha/) exporter | @@ -331,6 +332,7 @@ on: | `setup-qemu` | Bool | `false` | Runs the `setup-qemu-action` step to install QEMU static binaries | | `artifact-name` | String | `docker-github-builder-assets` | Name of the uploaded GitHub artifact (for `local` output) | | `artifact-upload` | Bool | `false` | Upload build output GitHub artifact (for `local` output) | +| `artifact-merge` | Bool | `true` | Merge output for multi-platforms build into a single artifact (for `local` output) | | `cache` | Bool | `false` | Enable [GitHub Actions cache](https://docs.docker.com/build/cache/backends/gha/) exporter | | `cache-scope` | String | target name or `buildkit` | Which [scope cache object belongs to](https://docs.docker.com/build/cache/backends/gha/#scope) if `cache` is enabled. This is the cache blob prefix name used when pushing cache to GitHub Actions cache backend | | `cache-mode` | String | `min` | [Cache layers to export](https://docs.docker.com/build/cache/backends/#cache-mode) if cache enabled (`min` or `max`). In `min` cache mode, only layers that are exported into the resulting image are cached, while in `max` cache mode, all layers are cached, even those of intermediate steps |