From 30b80cdf5e58057792ec9122605a0beeb1f38c31 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:28:37 +0100 Subject: [PATCH 1/4] add image-digest output to return pushed digest on manifest creation Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/bake.yml | 42 ++++++++++++++++++++++++++++--------- .github/workflows/build.yml | 42 ++++++++++++++++++++++++++++--------- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index c72cbe5..1e9b7bb 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -138,6 +138,9 @@ on: artifact-name: description: "Name of the uploaded artifact (for local output)" value: ${{ jobs.finalize.outputs.artifact-name }} + image-digest: + description: "Digest of the built image (for image output if pushed)" + value: ${{ jobs.finalize.outputs.image-digest }} output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} @@ -904,12 +907,21 @@ jobs: cosign-version: ${{ env.COSIGN_VERSION }} cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} + image-digest: ${{ steps.manifest.outputs.digest }} output-type: ${{ inputs.output }} signed: ${{ needs.prepare.outputs.sign }} needs: - prepare - build steps: + - + name: Install @docker/actions-toolkit + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_DAT-MODULE: ${{ env.DOCKER_ACTIONS_TOOLKIT_MODULE }} + with: + script: | + await exec.exec('npm', ['install', '--prefer-offline', '--ignore-scripts', core.getInput('dat-module')]); - name: Docker meta id: meta @@ -941,6 +953,7 @@ jobs: cache-binary: false - name: Create manifest + id: manifest if: ${{ inputs.output == 'image' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: @@ -950,6 +963,8 @@ jobs: INPUT_BUILD-OUTPUTS: ${{ toJSON(needs.build.outputs) }} with: script: | + const { ImageTools } = require('@docker/actions-toolkit/lib/buildx/imagetools'); + const inpPush = core.getBooleanInput('push'); const inpImageNames = core.getMultilineInput('image-names'); const inpTagNames = core.getMultilineInput('tag-names'); @@ -967,22 +982,29 @@ jobs: return; } + let digest; for (const imageName of inpImageNames) { - let createArgs = ['buildx', 'imagetools', 'create']; + const tags = []; for (const tag of inpTagNames) { - createArgs.push('-t', `${imageName}:${tag}`); - } - for (const digest of digests) { - createArgs.push(digest); + tags.push(`${imageName}:${tag}`); } + const result = await new ImageTools().create({ + sources: digests, + tags: tags, + skipExec: !inpPush + }); if (inpPush) { - await exec.exec('docker', createArgs); - } else { - await core.group(`Generated imagetools create command for ${imageName}`, async () => { - core.info(`docker ${createArgs.join(' ')}`); - }); + if (!result.digest) { + core.setFailed('Failed to create manifest, no digest returned'); + return; + } + digest = result.digest; + core.info(`Manifest created: ${imageName}@${result.digest}`); } } + if (digest) { + core.setOutput('digest', digest); + } - name: Merge artifacts if: ${{ inputs.output == 'local' && inputs.artifact-upload }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7f44ce..be96e9c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -141,6 +141,9 @@ on: artifact-name: description: "Name of the uploaded artifact (for local output)" value: ${{ jobs.finalize.outputs.artifact-name }} + image-digest: + description: "Digest of the built image (for image output if pushed)" + value: ${{ jobs.finalize.outputs.image-digest }} output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} @@ -758,12 +761,21 @@ jobs: cosign-version: ${{ env.COSIGN_VERSION }} cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} + image-digest: ${{ steps.manifest.outputs.digest }} output-type: ${{ inputs.output }} signed: ${{ needs.prepare.outputs.sign }} needs: - prepare - build steps: + - + name: Install @docker/actions-toolkit + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_DAT-MODULE: ${{ env.DOCKER_ACTIONS_TOOLKIT_MODULE }} + with: + script: | + await exec.exec('npm', ['install', '--prefer-offline', '--ignore-scripts', core.getInput('dat-module')]); - name: Docker meta id: meta @@ -794,6 +806,7 @@ jobs: cache-binary: false - name: Create manifest + id: manifest if: ${{ inputs.output == 'image' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: @@ -803,6 +816,8 @@ jobs: INPUT_BUILD-OUTPUTS: ${{ toJSON(needs.build.outputs) }} with: script: | + const { ImageTools } = require('@docker/actions-toolkit/lib/buildx/imagetools'); + const inpPush = core.getBooleanInput('push'); const inpImageNames = core.getMultilineInput('image-names'); const inpTagNames = core.getMultilineInput('tag-names'); @@ -820,22 +835,29 @@ jobs: return; } + let digest; for (const imageName of inpImageNames) { - let createArgs = ['buildx', 'imagetools', 'create']; + const tags = []; for (const tag of inpTagNames) { - createArgs.push('-t', `${imageName}:${tag}`); - } - for (const digest of digests) { - createArgs.push(digest); + tags.push(`${imageName}:${tag}`); } + const result = await new ImageTools().create({ + sources: digests, + tags: tags, + skipExec: !inpPush + }); if (inpPush) { - await exec.exec('docker', createArgs); - } else { - await core.group(`Generated imagetools create command for ${imageName}`, async () => { - core.info(`docker ${createArgs.join(' ')}`); - }); + if (!result.digest) { + core.setFailed('Failed to create manifest, no digest returned'); + return; + } + digest = result.digest; + core.info(`Manifest created: ${imageName}@${result.digest}`); } } + if (digest) { + core.setOutput('digest', digest); + } - name: Merge artifacts if: ${{ inputs.output == 'local' && inputs.artifact-upload }} From 5af3c2f6ab5e626127b10c989a5ff5ca26b9cfb6 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:27:47 +0100 Subject: [PATCH 2/4] add image-names output Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/bake.yml | 7 +++++++ .github/workflows/build.yml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index 1e9b7bb..ac61744 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -141,6 +141,9 @@ on: image-digest: description: "Digest of the built image (for image output if pushed)" value: ${{ jobs.finalize.outputs.image-digest }} + image-names: + description: "JSON array of image names (for image output if pushed)" + value: ${{ jobs.finalize.outputs.image-names }} output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} @@ -908,6 +911,7 @@ jobs: cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} image-digest: ${{ steps.manifest.outputs.digest }} + image-names: ${{ inputs.output == 'image' && steps.manifest.outputs.names || '[]' }} output-type: ${{ inputs.output }} signed: ${{ needs.prepare.outputs.sign }} needs: @@ -983,6 +987,7 @@ jobs: } let digest; + const names = []; for (const imageName of inpImageNames) { const tags = []; for (const tag of inpTagNames) { @@ -999,12 +1004,14 @@ jobs: return; } digest = result.digest; + names.push(...result.imageNames); core.info(`Manifest created: ${imageName}@${result.digest}`); } } if (digest) { core.setOutput('digest', digest); } + core.setOutput('names', JSON.stringify(names)); - name: Merge artifacts if: ${{ inputs.output == 'local' && inputs.artifact-upload }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be96e9c..ae4fa6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -144,6 +144,9 @@ on: image-digest: description: "Digest of the built image (for image output if pushed)" value: ${{ jobs.finalize.outputs.image-digest }} + image-names: + description: "JSON array of image names (for image output if pushed)" + value: ${{ jobs.finalize.outputs.image-names }} output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} @@ -762,6 +765,7 @@ jobs: cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} image-digest: ${{ steps.manifest.outputs.digest }} + image-names: ${{ inputs.output == 'image' && steps.manifest.outputs.names || '[]' }} output-type: ${{ inputs.output }} signed: ${{ needs.prepare.outputs.sign }} needs: @@ -836,6 +840,7 @@ jobs: } let digest; + const names = []; for (const imageName of inpImageNames) { const tags = []; for (const tag of inpTagNames) { @@ -852,12 +857,14 @@ jobs: return; } digest = result.digest; + names.push(...result.imageNames); core.info(`Manifest created: ${imageName}@${result.digest}`); } } if (digest) { core.setOutput('digest', digest); } + core.setOutput('names', JSON.stringify(names)); - name: Merge artifacts if: ${{ inputs.output == 'local' && inputs.artifact-upload }} From 31d7271c36995b3ea43a4b7411776d29198d0cab Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:10:50 +0100 Subject: [PATCH 3/4] test image-names and image-digest Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/.test-bake.yml | 19 +++++++++++++++++++ .github/workflows/.test-build.yml | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index 382851b..fcd4810 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -129,6 +129,25 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); + bake-aws-scan: + runs-on: ubuntu-24.04 + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-aws + steps: + - + name: Login to registry + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + with: + registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + name: Scan for vulnerabilities + uses: crazy-max/ghaction-container-scan@a0a3900b79d158c85ccf034e5368fae620a9233a # v4.0.0 + with: + image: ${{ fromJSON(needs.bake-aws.outputs.image-names)[0] }}@${{ needs.bake-aws.outputs.image-digest }} + bake-aws-nosign: uses: ./.github/workflows/bake.yml permissions: diff --git a/.github/workflows/.test-build.yml b/.github/workflows/.test-build.yml index aaaebe7..59c0a0e 100644 --- a/.github/workflows/.test-build.yml +++ b/.github/workflows/.test-build.yml @@ -129,6 +129,25 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); + build-aws-scan: + runs-on: ubuntu-24.04 + if: ${{ github.event_name != 'pull_request' }} + needs: + - build-aws + steps: + - + name: Login to registry + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + with: + registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + name: Scan for vulnerabilities + uses: crazy-max/ghaction-container-scan@a0a3900b79d158c85ccf034e5368fae620a9233a # v4.0.0 + with: + image: ${{ fromJSON(needs.build-aws.outputs.image-names)[0] }}@${{ needs.build-aws.outputs.image-digest }} + build-aws-nosign: uses: ./.github/workflows/build.yml permissions: From 85729f123b36ee92b4ba3e49e9f519af0f336832 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:40:13 +0100 Subject: [PATCH 4/4] readme: outputs docs Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- README.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9abbc21..c04fedf 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,11 @@ ___ * [Build reusable workflow](#build-reusable-workflow) * [Inputs](#inputs) * [Secrets](#secrets) + * [Outputs](#outputs) * [Bake reusable workflow](#bake-reusable-workflow) - * [Inputs](#inputs) - * [Secrets](#secrets) + * [Inputs](#inputs-1) + * [Secrets](#secrets-1) + * [Outputs](#outputs-1) ## Overview @@ -252,6 +254,23 @@ on: | `registry-auths` | | Raw authentication to registries, defined as YAML objects (for `image` output) | | `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | +#### Outputs + +These outputs are available as `needs..outputs.*` and can be passed +directly to the [`verify.yml` reusable workflow](.github/workflows/verify.yml) +with `builder-outputs: ${{ toJSON(needs..outputs) }}`. + +| Name | Type | Description | +|--------------------------|--------|------------------------------------------------------------------------------| +| `meta-json` | JSON | Metadata JSON output from `docker/metadata-action` (for `image` output) | +| `cosign-version` | String | Cosign version used for verification | +| `cosign-verify-commands` | List | Newline-delimited `cosign verify` commands generated when signing is enabled | +| `artifact-name` | String | Name of the uploaded merged artifact (for `local` output) | +| `image-digest` | String | Digest of the built image manifest (for `image` output when pushed) | +| `image-names` | JSON | JSON array of image names (for `image` output when pushed) | +| `output-type` | String | Output type selected for the workflow (`image` or `local`) | +| `signed` | Bool | Whether attestation manifests or local artifacts were signed | + ### Bake reusable workflow The [`bake.yml` reusable workflow](.github/workflows/bake.yml) lets you build @@ -357,3 +376,20 @@ on: |------------------|-----------------------|--------------------------------------------------------------------------------| | `registry-auths` | | Raw authentication to registries, defined as YAML objects (for `image` output) | | `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | + +#### Outputs + +These outputs are available as `needs..outputs.*` and can be passed +directly to the [`verify.yml` reusable workflow](.github/workflows/verify.yml) +with `builder-outputs: ${{ toJSON(needs..outputs) }}`. + +| Name | Type | Description | +|--------------------------|--------|------------------------------------------------------------------------------| +| `meta-json` | JSON | Metadata JSON output from `docker/metadata-action` (for `image` output) | +| `cosign-version` | String | Cosign version used for verification | +| `cosign-verify-commands` | List | Newline-delimited `cosign verify` commands generated when signing is enabled | +| `artifact-name` | String | Name of the uploaded merged artifact (for `local` output) | +| `image-digest` | String | Digest of the built image manifest (for `image` output when pushed) | +| `image-names` | JSON | JSON array of image names (for `image` output when pushed) | +| `output-type` | String | Output type selected for the workflow (`image` or `local`) | +| `signed` | Bool | Whether attestation manifests or local artifacts were signed |