From d4286bcd19df581c4d814aedbbb166d48f66d1dc Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Mon, 8 Jun 2026 14:04:40 -0500 Subject: [PATCH 1/2] Notify on failure when no run history exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The action previously skipped silently when no previous workflow runs were found on main. This prevented merge queue failures from triggering notifications, since merge queue runs land on ephemeral refs and never appear in a main-branch run query. Now: no history + failed → fire failure notification; no history + passed → skip. State-transition logic is unchanged for builds with history. --- .github/actions/slack-build-notify/action.yml | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/.github/actions/slack-build-notify/action.yml b/.github/actions/slack-build-notify/action.yml index efccfdecc..55ffb0eff 100644 --- a/.github/actions/slack-build-notify/action.yml +++ b/.github/actions/slack-build-notify/action.yml @@ -54,32 +54,36 @@ runs: .filter(r => r.id !== context.runId && r.conclusion !== 'cancelled') .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - if (previousRuns.length === 0) { - core.info('No previous completed runs found, skipping notification'); - return; - } - - const previousRun = previousRuns[0]; const currentFailed = currentResult !== 'success'; - // Compare against the previous run's CI job conclusion (not the workflow - // conclusion) so that clean-job flaps don't trigger false notifications. - // Falls back to workflow conclusion if the CI job isn't found. - const { data: { jobs: prevJobs } } = await github.rest.actions.listJobsForWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: previousRun.id, - per_page: 100, - }); - const prevCiJob = prevJobs.find(j => j.name === 'CI'); - const previousConclusion = prevCiJob - ? prevCiJob.conclusion - : previousRun.conclusion; - const previousFailed = previousConclusion !== 'success'; - - if (currentFailed === previousFailed) { - core.info(`No state transition (current: ${currentResult}, previous: ${previousConclusion}), skipping`); - return; + if (previousRuns.length === 0) { + if (!currentFailed) { + core.info('No previous completed runs found and build passed, skipping notification'); + return; + } + // No history — notify on failure directly (e.g. merge queue runs on ephemeral refs) + } else { + const previousRun = previousRuns[0]; + + // Compare against the previous run's CI job conclusion (not the workflow + // conclusion) so that clean-job flaps don't trigger false notifications. + // Falls back to workflow conclusion if the CI job isn't found. + const { data: { jobs: prevJobs } } = await github.rest.actions.listJobsForWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: previousRun.id, + per_page: 100, + }); + const prevCiJob = prevJobs.find(j => j.name === 'CI'); + const previousConclusion = prevCiJob + ? prevCiJob.conclusion + : previousRun.conclusion; + const previousFailed = previousConclusion !== 'success'; + + if (currentFailed === previousFailed) { + core.info(`No state transition (current: ${currentResult}, previous: ${previousConclusion}), skipping`); + return; + } } const runUrl = currentRun.html_url; From 3adcc3008de1ea9a5e2deca0f932428945f7914e Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Mon, 8 Jun 2026 14:58:37 -0500 Subject: [PATCH 2/2] Add --latest to bakery ci matrix; add latest-only to PR workflow Extend `bakery ci matrix` with a `--latest` flag that mirrors the flag already on `bakery build`. For regular versions it keeps only those with `latest: true`; for matrix images it keeps only the highest-version combination (the row where `ImageVersion.latest` is set by `_matches_latest`). Add a `latest-only` boolean input to `bakery-build-pr.yml` that passes `--latest` to the matrix generation steps, so calling workflows can opt individual event types into a faster subset run. Add a `multiversion` test context and BDD scenarios that verify the flag filters non-latest versions from the output. --- .github/workflows/bakery-build-pr.yml | 15 ++++++++++-- posit-bakery/posit_bakery/cli/ci.py | 10 ++++++++ .../ci/matrix/multiversion/default.json | 1 + .../ci/matrix/multiversion/latest_only.json | 1 + .../test/features/cli/ci/matrix.feature | 16 +++++++++++++ .../test/resources/multiversion/bakery.yaml | 23 +++++++++++++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 posit-bakery/test/cli/testdata/ci/matrix/multiversion/default.json create mode 100644 posit-bakery/test/cli/testdata/ci/matrix/multiversion/latest_only.json create mode 100644 posit-bakery/test/resources/multiversion/bakery.yaml diff --git a/.github/workflows/bakery-build-pr.yml b/.github/workflows/bakery-build-pr.yml index 1cfe51ad3..40ccd5f67 100644 --- a/.github/workflows/bakery-build-pr.yml +++ b/.github/workflows/bakery-build-pr.yml @@ -35,6 +35,11 @@ on: default: "exclude" required: false type: string + latest-only: + description: "Build only the latest version of each image (faster PR checks)" + default: false + required: false + type: boolean retry: description: "Number of times to retry a failed build" default: 1 @@ -102,9 +107,12 @@ jobs: IS_FORK: ${{ needs.detect.outputs.is-fork }} DEV_VERSIONS: ${{ inputs.dev-versions }} MATRIX_VERSIONS: ${{ inputs.matrix-versions }} + LATEST_ONLY: ${{ inputs.latest-only }} BAKERY_CONTEXT: ${{ inputs.context }} run: | - FULL_MATRIX=$(bakery ci matrix --quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" --context "$BAKERY_CONTEXT" | jq --compact-output .) + LATEST_FLAGS=() + if [ "$LATEST_ONLY" = "true" ]; then LATEST_FLAGS=(--latest); fi + FULL_MATRIX=$(bakery ci matrix --quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" "${LATEST_FLAGS[@]}" --context "$BAKERY_CONTEXT" | jq --compact-output .) if [ "$IS_FORK" = "true" ]; then # Skip arm64 for fork PRs — paid runners may not be available FULL_MATRIX=$(echo "$FULL_MATRIX" | jq --compact-output '[.[] | select(.platform != "linux/arm64")]') @@ -116,9 +124,12 @@ jobs: env: DEV_VERSIONS: ${{ inputs.dev-versions }} MATRIX_VERSIONS: ${{ inputs.matrix-versions }} + LATEST_ONLY: ${{ inputs.latest-only }} BAKERY_CONTEXT: ${{ inputs.context }} run: | - result=$(bakery ci matrix --quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" --exclude platform --context "$BAKERY_CONTEXT") + LATEST_FLAGS=() + if [ "$LATEST_ONLY" = "true" ]; then LATEST_FLAGS=(--latest); fi + result=$(bakery ci matrix --quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" "${LATEST_FLAGS[@]}" --exclude platform --context "$BAKERY_CONTEXT") echo "versions_matrix=$(echo "$result" | jq --compact-output .)" >> "$GITHUB_OUTPUT" build-test: diff --git a/posit-bakery/posit_bakery/cli/ci.py b/posit-bakery/posit_bakery/cli/ci.py index d0b5e3511..b4da5e378 100644 --- a/posit-bakery/posit_bakery/cli/ci.py +++ b/posit-bakery/posit_bakery/cli/ci.py @@ -75,6 +75,14 @@ def matrix( rich_help_panel=RichHelpPanelEnum.FILTERS, ), ] = None, + latest: Annotated[ + Optional[bool], + typer.Option( + "--latest", + help="Include only the latest version of each image. For matrix images, includes only the highest-version combination.", + rich_help_panel=RichHelpPanelEnum.FILTERS, + ), + ] = False, exclude: Annotated[ Optional[list[BakeryCIMatrixFieldEnum]], typer.Option(help="Fields to exclude splitting the matrix by."), @@ -152,6 +160,8 @@ def matrix( continue if image_version is not None and not version_matches(ver.name, image_version): continue + if latest and not ver.latest: + continue if BakeryCIMatrixFieldEnum.VERSION not in exclude: entry["version"] = ver.name diff --git a/posit-bakery/test/cli/testdata/ci/matrix/multiversion/default.json b/posit-bakery/test/cli/testdata/ci/matrix/multiversion/default.json new file mode 100644 index 000000000..699c5c863 --- /dev/null +++ b/posit-bakery/test/cli/testdata/ci/matrix/multiversion/default.json @@ -0,0 +1 @@ +[{"image": "test-image", "version": "2.0.0", "dev": false, "platform": "linux/amd64"}, {"image": "test-image", "version": "1.0.0", "dev": false, "platform": "linux/amd64"}] diff --git a/posit-bakery/test/cli/testdata/ci/matrix/multiversion/latest_only.json b/posit-bakery/test/cli/testdata/ci/matrix/multiversion/latest_only.json new file mode 100644 index 000000000..fcd48eec6 --- /dev/null +++ b/posit-bakery/test/cli/testdata/ci/matrix/multiversion/latest_only.json @@ -0,0 +1 @@ +[{"image": "test-image", "version": "2.0.0", "dev": false, "platform": "linux/amd64"}] diff --git a/posit-bakery/test/features/cli/ci/matrix.feature b/posit-bakery/test/features/cli/ci/matrix.feature index 243a794ba..c6d4fa156 100644 --- a/posit-bakery/test/features/cli/ci/matrix.feature +++ b/posit-bakery/test/features/cli/ci/matrix.feature @@ -47,3 +47,19 @@ Feature: matrix | --image-version | 9.9.9 | When I execute the command Then The command fails + + Scenario: Generating a full CI matrix for the multiversion suite + Given I call bakery ci matrix + * in the multiversion context + When I execute the command + Then The command succeeds + * the matrix matches testdata ci/matrix/multiversion/default.json + + Scenario: Filtering the CI matrix to latest versions only + Given I call bakery ci matrix + * in the multiversion context + * with the arguments: + | --latest | | + When I execute the command + Then The command succeeds + * the matrix matches testdata ci/matrix/multiversion/latest_only.json diff --git a/posit-bakery/test/resources/multiversion/bakery.yaml b/posit-bakery/test/resources/multiversion/bakery.yaml new file mode 100644 index 000000000..a7a2d91bb --- /dev/null +++ b/posit-bakery/test/resources/multiversion/bakery.yaml @@ -0,0 +1,23 @@ +repository: + url: "github.com/posit-dev/images-shared" + vendor: "Posit Software, PBC" + maintainer: + name: "Posit Docker Team" + email: "docker@posit.co" + +registries: + - host: "ghcr.io" + namespace: "posit-dev" + +images: + - name: "test-image" + versions: + - name: "2.0.0" + latest: true + os: + - name: Ubuntu 22.04 + primary: true + - name: "1.0.0" + os: + - name: Ubuntu 22.04 + primary: true