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: diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index c72cbe5..ac61744 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -138,6 +138,12 @@ 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 }} + 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 }} @@ -904,12 +910,22 @@ 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 }} + image-names: ${{ inputs.output == 'image' && steps.manifest.outputs.names || '[]' }} 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 +957,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 +967,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 +986,32 @@ jobs: return; } + let digest; + const names = []; 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; + 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 b7f44ce..ae4fa6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -141,6 +141,12 @@ 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 }} + 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 }} @@ -758,12 +764,22 @@ 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 }} + image-names: ${{ inputs.output == 'image' && steps.manifest.outputs.names || '[]' }} 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 +810,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 +820,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 +839,32 @@ jobs: return; } + let digest; + const names = []; 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; + 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/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 |