Skip to content
Merged
Show file tree
Hide file tree
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
92 changes: 82 additions & 10 deletions .github/workflows/build-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,41 @@ on:
required: false
type: string
default: ""
target:
description: "Multi-stage build target / stage. Empty = build the default (last) stage."
required: false
type: string
default: ""
build-args:
description: "Newline-separated KEY=VALUE build args passed verbatim to docker/build-push-action. Use this when the Dockerfile expects ARGs (snipe-it: SNIPE_IT_VERSION, BUILD_DATE, VCS_REF, ROLLING_DEPS)."
required: false
type: string
default: ""
cache-scope:
description: "Optional GHA cache scope name. Set this when multiple parallel matrix calls share the same image-name but should keep their build cache separate (e.g. caller's matrix is track × composer-mode). Empty = share one global cache (current default behaviour)."
required: false
type: string
default: ""
provenance:
description: "Buildx provenance mode (passed to docker/build-push-action's `provenance:` input). Common values: empty (action default), 'mode=max', 'false'. Note: this is the in-image attestation, distinct from the SLSA attestation produced by `attest: true` (which uses actions/attest-build-provenance)."
required: false
type: string
default: ""
sbom:
description: "Generate in-image SBOM (passed to docker/build-push-action's `sbom:` input). Off by default."
required: false
type: boolean
default: false
metadata-tags:
description: "Caller-provided override for docker/metadata-action's `tags:` input. When set, REPLACES the reusable's default 5 patterns (ref-branch / ref-pr / semver-{version,major.minor,major}). Use this for callers with bespoke tag fan-out (e.g. snipe-it: `:8.5.0`, `:8.5`, `:8`, plus `-rolling` suffix variants)."
required: false
type: string
default: ""
metadata-labels:
description: "Caller-provided OCI labels (newline-separated KEY=VALUE), appended to the labels docker/metadata-action auto-generates (created/revision/source/etc.). Use this to set image.title, image.description, image.licenses overrides."
required: false
type: string
default: ""
outputs:
digest:
description: "Digest of the pushed image (sha256:...). Empty when push=false."
Expand Down Expand Up @@ -145,19 +180,48 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

- name: Compute default metadata tag patterns
if: inputs.metadata-tags == ''
id: default_patterns
env:
SEMVER_REF: ${{ inputs.ref || github.ref_name }}
REF_PROVIDED: ${{ inputs.ref != '' && 'true' || 'false' }}
IS_TAG_PUSH: ${{ startsWith(github.ref, 'refs/tags/') && 'true' || 'false' }}
run: |
set -euo pipefail
if [[ "$REF_PROVIDED" == "true" || "$IS_TAG_PUSH" == "true" ]]; then
EN_SEMVER=true
else
EN_SEMVER=false
fi
if [[ "$REF_PROVIDED" == "true" ]]; then
EN_REF=false
else
EN_REF=true
fi
{
echo 'patterns<<EOM'
echo "type=ref,event=branch,enable=${EN_REF}"
echo "type=ref,event=pr,enable=${EN_REF}"
echo "type=semver,pattern={{version}},value=${SEMVER_REF},enable=${EN_SEMVER}"
echo "type=semver,pattern={{major}}.{{minor}},value=${SEMVER_REF},enable=${EN_SEMVER}"
echo "type=semver,pattern={{major}},value=${SEMVER_REF},enable=${EN_SEMVER}"
echo 'EOM'
} >> "$GITHUB_OUTPUT"

- name: Gather Docker metadata
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
env:
SEMVER_REF: ${{ inputs.ref || github.ref_name }}
with:
images: ${{ env.IMAGE_REF }}
tags: |
type=ref,event=branch,enable=${{ !inputs.ref }}
type=ref,event=pr,enable=${{ !inputs.ref }}
type=semver,pattern={{version}},value=${{ env.SEMVER_REF }},enable=${{ inputs.ref != '' || startsWith(github.ref, 'refs/tags/') }}
type=semver,pattern={{major}}.{{minor}},value=${{ env.SEMVER_REF }},enable=${{ inputs.ref != '' || startsWith(github.ref, 'refs/tags/') }}
type=semver,pattern={{major}},value=${{ env.SEMVER_REF }},enable=${{ inputs.ref != '' || startsWith(github.ref, 'refs/tags/') }}
# Caller-supplied `metadata-tags` overrides the reusable's default 5
# patterns entirely. Empty (default) → fall back to the default
# patterns computed in the step above.
tags: ${{ inputs.metadata-tags != '' && inputs.metadata-tags || steps.default_patterns.outputs.patterns }}
# Caller-supplied `metadata-labels` are appended to the auto-
# generated set (created/revision/source/etc.) rather than
# replacing them.
labels: ${{ inputs.metadata-labels }}

- name: Log in to ${{ inputs.registry }}
if: inputs.push
Expand All @@ -176,12 +240,20 @@ jobs:
with:
context: ${{ env.BUILD_CONTEXT }}
file: ${{ env.DOCKERFILE }}
target: ${{ inputs.target }}
push: ${{ inputs.push }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: ${{ inputs.platforms }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: ${{ inputs.build-args }}
provenance: ${{ inputs.provenance }}
sbom: ${{ inputs.sbom }}
# cache-scope: when set, both cache-from and cache-to suffix the GHA
# scope so parallel matrix calls keep separate caches. When empty,
# the GHA action uses a single default-scope cache (backwards
# compatible with the pre-cache-scope reusable).
cache-from: type=gha${{ inputs.cache-scope != '' && format(',scope={0}', inputs.cache-scope) || '' }}
cache-to: type=gha,mode=max${{ inputs.cache-scope != '' && format(',scope={0}', inputs.cache-scope) || '' }}

- name: Run Trivy vulnerability scanner
id: trivy
Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/security-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ on:
description: "Upload SARIF to GitHub code-scanning. Disable when callers want raw Trivy text output only."
type: boolean
default: true
tolerate-pull-failure:
description: |
When true, the workflow pre-flights `docker manifest inspect`
against the image-ref. If the manifest is unavailable
(`manifest unknown` because a rolling tag was never published,
or the image build for that matrix cell was skipped) the
Trivy + SARIF-upload steps are SKIPPED and the job exits
green. The Trivy step itself runs with normal (non-
suppressed) exit semantics, so legitimate CVE findings + any
caller-specified `exit-code: '1'` still produce a real
failure. Default false: a legitimate pull failure correctly
fails the job. Workaround for callers fanning out a matrix
over tags where some cells point at images that may or may
not exist (snipe-it: rolling-variant images that the
composer audit blocked during the build phase). Cannot be
expressed via job-level `continue-on-error:` on the caller
— GitHub forbids that on reusable-caller jobs.
type: boolean
default: false

permissions:
contents: read
Expand Down Expand Up @@ -135,7 +154,33 @@ jobs:
username: ${{ github.actor }}
password: ${{ github.token }}

- name: Pre-flight — image manifest exists
# Only runs when the caller opts into tolerate-pull-failure.
# `docker manifest inspect` is a HEAD-style probe — no full pull —
# so it's cheap. The `continue-on-error: true` here ONLY affects
# this probe: a missing manifest does not fail the job; the next
# step's `if:` reads the outcome and decides whether to run Trivy.
# When tolerate-pull-failure is false (default), this step is
# skipped and the Trivy step's `if:` evaluates to true (no
# outcome to compare against → not 'failure' → runs normally).
id: manifest_check
if: inputs.tolerate-pull-failure
continue-on-error: true
env:
IMAGE_REF: ${{ inputs.image-ref }}
run: |
set -euo pipefail
docker manifest inspect "$IMAGE_REF" > /dev/null
echo "manifest available"

- name: Run Trivy vulnerability scanner
# Runs unless the pre-flight (if it ran) reported failure.
# No `continue-on-error` here: Trivy's own exit semantics
# (caller's `exit-code` input, vuln findings, etc.) propagate
# normally. This was the gap Copilot flagged on PR 141 — a
# broad continue-on-error swallowed legitimate finding-based
# failures.
if: steps.manifest_check.outcome != 'failure'
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
Comment thread
CybotTM marked this conversation as resolved.
with:
image-ref: ${{ inputs.image-ref }}
Expand Down
Loading