Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 89 additions & 64 deletions .github/actions/push-container-image/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -328,23 +328,6 @@ runs:
name: ${{ steps.globals.outputs.bom-artifact-name }}
path: ${{ inputs.PATH }}

- name: Enable buildkit cache
uses: actions/cache@v5.0.5
if: inputs.enable-buildkit-cache == 'true'
with:
path: /tmp/buildkit-cache/buildkit-state.tar
key: ${{ runner.os }}-buildkit-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildkit-

- name: Load buildkit state from cache
uses: dashevo/gh-action-cache-buildkit-state@v1.0.3
if: inputs.enable-buildkit-cache == 'true'
with:
builder: buildx_buildkit_${{ steps.buildx.outputs.name }}0
cache-path: /tmp/buildkit-cache
cache-max-size: 3g

- name: Configure AWS credentials from main account
id: aws-login
# Only run if dry run is explicitly set to 'false'
Expand Down Expand Up @@ -382,6 +365,25 @@ runs:
id: buildx
uses: docker/setup-buildx-action@v4.0.0

- name: Enable buildkit cache
# Must run after buildx setup: the loader below needs the builder container to exist
# and resolves its name from the buildx step's outputs
uses: actions/cache@v5.0.5
if: inputs.enable-buildkit-cache == 'true'
with:
path: /tmp/buildkit-cache/buildkit-state.tar
key: ${{ runner.os }}-buildkit-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildkit-

- name: Load buildkit state from cache
uses: dashevo/gh-action-cache-buildkit-state@v1.0.3
if: inputs.enable-buildkit-cache == 'true'
with:
builder: buildx_buildkit_${{ steps.buildx.outputs.name }}0
cache-path: /tmp/buildkit-cache
cache-max-size: 3g

- name: Prepare Docker image names and tags
id: meta
uses: docker/metadata-action@v6.0.0
Expand Down Expand Up @@ -419,42 +421,43 @@ runs:
env:
DOCKER_METADATA: ${{ steps.meta.outputs.json}}

- name: Build, tag, and push container image
# Only run if dry run is explicitly set to 'false'
id: build-images
- name: Build container image
# Only run if dry run is explicitly set to 'false'.
# Builds the multi-platform image WITHOUT pushing it, so that the image scans below can
# gate the push. The 'Push container image' step re-runs this build as a pure cache hit
# (same builder, same inputs), so nothing is rebuilt when the image is eventually pushed.
id: build-image
uses: docker/build-push-action@v7.1.0
if: inputs.dry-run == 'false'
env:
DOCKER_BUILDKIT: 1
with:
builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64
# Dependabot triggered PRs don't have access to Quay credentials to push images
push: ${{ ! (inputs.registry == 'quay' && github.actor == 'dependabot[bot]') }}
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
context: ${{ inputs.path }}
file: ${{ inputs.path }}/${{ inputs.dockerfile }}
provenance: mode=max
tags: ${{ steps.meta.outputs.tags }}
target: ${{ inputs.target }}
sbom: true
secrets: |
${{ inputs.package-pat && inputs.package-pat || '' }}
${{ inputs.font-awesome-key && inputs.font-awesome-key || '' }}
build-args: |
${{ inputs.build-args }}

- name: Build container image tarball (dry run)
# Only run if dry run is set to anything other than 'false', indicating that this is a dry run
id: build-images-dry-run
- name: Build container image tarball for scanning
# Only run if this is a dry run or at least one image scan is enabled.
# The default image store in Docker Engine doesn't support loading multi-platform images,
# so a single (native platform) tarball is exported for the local scans. This is a pure
# cache hit against the build above.
id: build-image-tarball
uses: docker/build-push-action@v7.1.0
if: inputs.dry-run != 'false'
if: inputs.dry-run != 'false' || inputs.trivy-scan-image == 'true' || inputs.grype-scan-image == 'true'
env:
DOCKER_BUILDKIT: 1
with:
# The default image store in Docker Engine doesn't support loading multi-platform images.
# Because of this, the dry run excludes the 'platforms' input.
builder: ${{ steps.buildx.outputs.name }}
push: false
cache-from: type=gha
Expand All @@ -468,40 +471,17 @@ runs:
${{ inputs.font-awesome-key && inputs.font-awesome-key || '' }}
build-args: |
${{ inputs.build-args }}
# Filename must match the `scan-name` the Trivy/Grype steps derive below, since they download the
# artifact and `docker load` it by that exact name. Include `-<dockerfile>` to keep them aligned.
outputs: type=docker,dest=/tmp/${{ inputs.app-name-prefix }}${{ inputs.app-name }}${{ inputs.image-suffix }}-${{ inputs.dockerfile }}.tar

- name: Install CoSign
# Only run if dry run is explicitly set to 'false' and the image is being pushed to 'quay'.
if: inputs.dry-run == 'false' && inputs.registry == 'quay'
uses: sigstore/cosign-installer@v3
- name: Sign charts on Quay.io
# Only run if dry run is explicitly set to 'false' and the image is being pushed to 'quay'.
if: inputs.dry-run == 'false' && inputs.registry == 'quay'
shell: bash
env:
DIGEST: ${{ steps.build-images.outputs.digest }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
images=""
for tag in ${TAGS}; do
images+="${tag}@${DIGEST} "
done
cosign sign --yes ${images}

- name: Upload container image tarball artefact
# Only run if dry run is set to anything other than 'false', indicating that this is a dry run
# The artefact name must match '<scan-name>.tar' as the scan actions download it by that name
id: upload-image-artefact
uses: actions/upload-artifact@v7
if: inputs.dry-run != 'false'
if: inputs.dry-run != 'false' || inputs.trivy-scan-image == 'true' || inputs.grype-scan-image == 'true'
with:
# Name and path must match the dry-run build dest above and the `scan-name` used by the Trivy/Grype
# steps. These previously used `APP_NAME_PREFIX`-style refs (underscores), which do not resolve to the
# hyphenated inputs and silently evaluated to empty, so nothing was uploaded and the scans failed to
# download the tarball.
name: ${{ inputs.app-name-prefix }}${{ inputs.app-name }}${{ inputs.image-suffix }}-${{ inputs.dockerfile }}.tar
path: /tmp/${{ inputs.app-name-prefix }}${{ inputs.app-name }}${{ inputs.image-suffix }}-${{ inputs.dockerfile }}.tar
if-no-files-found: error

- name: Capture built container image tags
id: capture-tags
Expand All @@ -525,13 +505,13 @@ runs:
scan-name: ${{ inputs.app-name-prefix }}${{ inputs.app-name }}${{ inputs.image-suffix }}-${{ inputs.dockerfile }}

- name: Trivy image scan
# Only run if Trivy image scan is explicitly set to 'true' AND (is a dry run OR (is not a dry run AND user is not dependabot))
if: inputs.trivy-scan-image == 'true' && (inputs.dry-run != 'false' || (inputs.dry-run == 'false' && github.actor != 'dependabot[bot]'))
# Only run if Trivy image scan is explicitly set to 'true'
if: inputs.trivy-scan-image == 'true'
uses: telicent-oss/shared-workflows/.github/actions/trivy-image-scan@main
with:
github-token: ${{ inputs.github-token }}
# Indicates that the image should be imported from a tarball artefact and scanned locally
image-from-artefact: ${{ inputs.dry-run != 'false' }}
# The image is always scanned locally from the tarball artefact, before it is pushed
image-from-artefact: 'true'
image-ref: ${{ steps.capture-tags.outputs.image }}
registry: ${{ inputs.registry }}
quay-username: ${{ inputs.quay-username }}
Expand All @@ -543,13 +523,13 @@ runs:
uses-java: ${{ inputs.uses-maven }}

- name: Grype image scan
# Only run if Grype image scan is explicitly set to 'true' AND (is a dry run OR (is not a dry run AND user is not dependabot))
if: inputs.grype-scan-image == 'true' && (inputs.dry-run != 'false' || (inputs.dry-run == 'false' && github.actor != 'dependabot[bot]'))
# Only run if Grype image scan is explicitly set to 'true'
if: inputs.grype-scan-image == 'true'
uses: telicent-oss/shared-workflows/.github/actions/grype-image-scan@main
with:
github-token: ${{ inputs.github-token }}
# Indicates that the image should be imported from a tarball artefact and scanned locally
image-from-artefact: ${{ inputs.dry-run != 'false' }}
# The image is always scanned locally from the tarball artefact, before it is pushed
image-from-artefact: 'true'
image-ref: ${{ steps.capture-tags.outputs.image }}
registry: ${{ inputs.registry }}
quay-username: ${{ inputs.quay-username }}
Expand All @@ -559,6 +539,51 @@ runs:
fail-on-unfixed: ${{ inputs.fail-on-unfixed }}
remote-vex: ${{ inputs.remote-vex }}

- name: Push container image
# Only run if dry run is explicitly set to 'false', and only once the image scans above
# have passed. Re-runs the 'Build container image' build as a pure cache hit, so this
# step only generates the provenance/SBOM attestations and pushes the already built image.
id: build-images
uses: docker/build-push-action@v7.1.0
if: inputs.dry-run == 'false'
env:
DOCKER_BUILDKIT: 1
with:
builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64
# Dependabot triggered PRs don't have access to Quay credentials to push images
push: ${{ ! (inputs.registry == 'quay' && github.actor == 'dependabot[bot]') }}
cache-from: type=gha
cache-to: type=gha,mode=max
context: ${{ inputs.path }}
file: ${{ inputs.path }}/${{ inputs.dockerfile }}
provenance: mode=max
tags: ${{ steps.meta.outputs.tags }}
target: ${{ inputs.target }}
sbom: true
secrets: |
${{ inputs.package-pat && inputs.package-pat || '' }}
${{ inputs.font-awesome-key && inputs.font-awesome-key || '' }}
build-args: |
${{ inputs.build-args }}

- name: Install CoSign
# Only run if dry run is explicitly set to 'false' and the image is being pushed to 'quay'.
if: inputs.dry-run == 'false' && inputs.registry == 'quay'
uses: sigstore/cosign-installer@v3
- name: Sign charts on Quay.io
# Only run if dry run is explicitly set to 'false' and the image is being pushed to 'quay'.
if: inputs.dry-run == 'false' && inputs.registry == 'quay'
shell: bash
env:
DIGEST: ${{ steps.build-images.outputs.digest }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
images=""
for tag in ${TAGS}; do
images+="${tag}@${DIGEST} "
done
cosign sign --yes ${images}
- name: SonarQube code health report
# Only run when actually pushing to Quay, and only when the SonarQube token and report destination are
# supplied. Dry runs are skipped so that they remain free of side effects.
Expand Down