From e242e0e4f2afcd00a34d2178bb87b72e697f4538 Mon Sep 17 00:00:00 2001 From: Louis Parkin Date: Mon, 10 Aug 2026 10:03:57 +0200 Subject: [PATCH 1/4] STAC-25565 Port the beest verification trigger to GitHub Actions beest_trigger_verification was the last job in the GitLab pipeline with no GitHub Actions equivalent. Every other job is covered by the STAC-25142 / STAC-25457 / STAC-25500 stack. In GitLab the job sits in the postbuild stage, needs both merge_docker_manifest jobs and is `when: manual`, passing AGENT_BRANCH_UNDER_TEST, AGENT_HASH_UNDER_TEST and TRIGGER_AGENT_X86_TESTS into the stackvista/integrations/beest project. beest has since migrated to GitHub, and its agent-x86.yml and arm.yml both expose workflow_dispatch with an agent_branch_under_test input, so the port is a cross-repo workflow dispatch rather than a pipeline trigger. beest resolves the agent image from the branch name, so the commit SHA is no longer part of its input contract; it is recorded in the run summary for traceability instead. Keeping the workflow workflow_dispatch-only preserves the GitLab `when: manual` semantics. These runs provision real EKS infrastructure in the sandbox account and share a single global concurrency lock in beest, so firing them automatically on push would queue runs behind each other and spend hours of cluster time per merge. The suite input defaults to x86, matching TRIGGER_AGENT_X86_TESTS: true; arm and both are available because beest now exposes an arm workflow that the GitLab job never reached. The scenario selector is passed through rather than re-declared, so beest stays the single owner of the valid scenario list. Requires a GitHub App credential in this repo with actions:write on StackVista/beest, provisioned via pulumi-infra: BEEST_DISPATCH_APP_CLIENT_ID (variable) and BEEST_DISPATCH_APP_PRIVATE_KEY (secret). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/beest-verification.yml | 109 +++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 .github/workflows/beest-verification.yml diff --git a/.github/workflows/beest-verification.yml b/.github/workflows/beest-verification.yml new file mode 100644 index 000000000000..7b48df3daa06 --- /dev/null +++ b/.github/workflows/beest-verification.yml @@ -0,0 +1,109 @@ +name: Beest verification + +on: + workflow_dispatch: + inputs: + suite: + description: Which beest agent suite to run (GitLab triggered x86 only) + type: choice + default: x86 + options: + - x86 + - arm + - both + agent_branch_under_test: + description: Agent branch beest should deploy (empty = the branch this workflow runs on) + type: string + default: "" + scenarios: + description: Beest scenario selector (empty = whatever the beest workflow defaults to) + type: string + default: "" + no_destroy: + description: Keep the beest infrastructure after the run (for debugging a failure) + type: boolean + default: false + beest_ref: + description: Ref of StackVista/beest to dispatch + type: string + default: main + +permissions: {} + +concurrency: + group: beest-verification-${{ github.ref }} + cancel-in-progress: false + +jobs: + trigger: + name: Trigger beest agent verification + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: {} + steps: + - name: Mint GitHub App token (dispatch beest workflows) + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.BEEST_DISPATCH_APP_CLIENT_ID }} + private-key: ${{ secrets.BEEST_DISPATCH_APP_PRIVATE_KEY }} + owner: StackVista + repositories: beest + permission-actions: write + + - name: Dispatch beest verification + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + SUITE: ${{ inputs.suite }} + AGENT_BRANCH: ${{ inputs.agent_branch_under_test || github.ref_name }} + AGENT_SHA: ${{ github.sha }} + SCENARIOS: ${{ inputs.scenarios }} + NO_DESTROY: ${{ inputs.no_destroy }} + BEEST_REF: ${{ inputs.beest_ref }} + run: | + set -euo pipefail + + case "${SUITE}" in + x86) workflows=("agent-x86.yml") ;; + arm) workflows=("arm.yml") ;; + both) workflows=("agent-x86.yml" "arm.yml") ;; + *) echo "::error::unknown suite '${SUITE}'"; exit 1 ;; + esac + + # beest resolves the agent image from the branch name; the SHA is recorded + # for traceability only and is not part of beest's dispatch contract. + echo "agent branch under test: ${AGENT_BRANCH} (${AGENT_SHA})" + + { + echo "## Beest verification dispatched" + echo + echo "| field | value |" + echo "| --- | --- |" + echo "| agent branch under test | \`${AGENT_BRANCH}\` |" + echo "| agent commit | \`${AGENT_SHA}\` |" + echo "| suite | \`${SUITE}\` |" + echo "| scenarios | \`${SCENARIOS:-(beest default)}\` |" + echo "| keep infrastructure | \`${NO_DESTROY}\` |" + echo "| beest ref | \`${BEEST_REF}\` |" + echo + } >> "${GITHUB_STEP_SUMMARY}" + + for wf in "${workflows[@]}"; do + args=(--repo StackVista/beest --ref "${BEEST_REF}") + args+=(--field "agent_branch_under_test=${AGENT_BRANCH}") + args+=(--field "no_destroy=${NO_DESTROY}") + if [ -n "${SCENARIOS}" ]; then + args+=(--field "scenarios=${SCENARIOS}") + fi + + echo "dispatching ${wf}" + gh workflow run "${wf}" "${args[@]}" + + # gh does not report the queued run, and every beest AWS workflow shares + # one global lock, so look the run up rather than assuming it started. + sleep 10 + url="$(gh run list --repo StackVista/beest --workflow "${wf}" \ + --limit 1 --json url --jq '.[0].url' 2>/dev/null || true)" + fallback="https://github.com/StackVista/beest/actions/workflows/${wf}" + echo "- \`${wf}\` -> ${url:-${fallback}}" >> "${GITHUB_STEP_SUMMARY}" + done From 7f47d6c051962f4da54e3f78a8e4147f3017412e Mon Sep 17 00:00:00 2001 From: Louis Parkin Date: Mon, 10 Aug 2026 10:17:28 +0200 Subject: [PATCH 2/4] STAC-25565: pin the agent commit when dispatching beest The first version of this workflow sent only the branch and put the SHA in the run summary, on the reasoning that beest had no hash input. That was the wrong conclusion: beest's own GitLab port dropped the input while keeping the machinery, so the missing input was a regression to fix rather than a constraint to design around. beest#61 restores it. A branch builds many images, so branch-only means always testing whichever build is newest -- there is no way to verify a specific commit or to reproduce a failure against the image that produced it. Defaults to this run's commit, matching the GitLab job's CI_COMMIT_SHA. When agent_branch_under_test points at some other branch our SHA does not exist there, so the pin is left unset and beest falls back instead of dispatching a hash that resolves to nothing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/beest-verification.yml | 25 ++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/beest-verification.yml b/.github/workflows/beest-verification.yml index 7b48df3daa06..21c947bfd81a 100644 --- a/.github/workflows/beest-verification.yml +++ b/.github/workflows/beest-verification.yml @@ -15,6 +15,10 @@ on: description: Agent branch beest should deploy (empty = the branch this workflow runs on) type: string default: "" + agent_hash_under_test: + description: Agent commit beest should pin the image to (empty = the commit this workflow runs on) + type: string + default: "" scenarios: description: Beest scenario selector (empty = whatever the beest workflow defaults to) type: string @@ -56,6 +60,8 @@ jobs: GH_TOKEN: ${{ steps.app-token.outputs.token }} SUITE: ${{ inputs.suite }} AGENT_BRANCH: ${{ inputs.agent_branch_under_test || github.ref_name }} + AGENT_BRANCH_INPUT: ${{ inputs.agent_branch_under_test }} + AGENT_HASH_INPUT: ${{ inputs.agent_hash_under_test }} AGENT_SHA: ${{ github.sha }} SCENARIOS: ${{ inputs.scenarios }} NO_DESTROY: ${{ inputs.no_destroy }} @@ -70,9 +76,17 @@ jobs: *) echo "::error::unknown suite '${SUITE}'"; exit 1 ;; esac - # beest resolves the agent image from the branch name; the SHA is recorded - # for traceability only and is not part of beest's dispatch contract. - echo "agent branch under test: ${AGENT_BRANCH} (${AGENT_SHA})" + # A branch builds many images, so pin the exact commit rather than letting + # beest pick the newest build. Default to this run's SHA, but only when the + # branch was not overridden -- against another branch our SHA means nothing, + # so leave it unset and let beest fall back. + agent_hash="${AGENT_HASH_INPUT}" + if [ -z "${agent_hash}" ] && [ -z "${AGENT_BRANCH_INPUT}" ]; then + agent_hash="${AGENT_SHA}" + fi + + echo "agent branch under test: ${AGENT_BRANCH}" + echo "agent commit under test: ${agent_hash:-(unpinned - beest chart default)}" { echo "## Beest verification dispatched" @@ -80,7 +94,7 @@ jobs: echo "| field | value |" echo "| --- | --- |" echo "| agent branch under test | \`${AGENT_BRANCH}\` |" - echo "| agent commit | \`${AGENT_SHA}\` |" + echo "| agent commit under test | \`${agent_hash:-(unpinned - beest chart default)}\` |" echo "| suite | \`${SUITE}\` |" echo "| scenarios | \`${SCENARIOS:-(beest default)}\` |" echo "| keep infrastructure | \`${NO_DESTROY}\` |" @@ -92,6 +106,9 @@ jobs: args=(--repo StackVista/beest --ref "${BEEST_REF}") args+=(--field "agent_branch_under_test=${AGENT_BRANCH}") args+=(--field "no_destroy=${NO_DESTROY}") + if [ -n "${agent_hash}" ]; then + args+=(--field "agent_hash_under_test=${agent_hash}") + fi if [ -n "${SCENARIOS}" ]; then args+=(--field "scenarios=${SCENARIOS}") fi From 6b6397d8d1d0f55a81a8b1061a29397cedd0cf57 Mon Sep 17 00:00:00 2001 From: Louis Parkin Date: Tue, 11 Aug 2026 10:00:47 +0200 Subject: [PATCH 3/4] STAC-25565 use the provisioned beest GitHub App credentials The workflow referenced BEEST_DISPATCH_APP_CLIENT_ID/PRIVATE_KEY, which are provisioned nowhere. The beest App already exists as BEEST_GH_APP_CLIENT_ID / BEEST_GH_APP_PRIVATE_KEY, matching the _GH_APP_* convention every other App credential in the estate follows. Those variables are currently bound only to the beest repo, so pulumi-infra must also bind them to stackstate-agent before this workflow can mint a token. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/beest-verification.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/beest-verification.yml b/.github/workflows/beest-verification.yml index 21c947bfd81a..ccea8754e43e 100644 --- a/.github/workflows/beest-verification.yml +++ b/.github/workflows/beest-verification.yml @@ -49,8 +49,8 @@ jobs: id: app-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - client-id: ${{ vars.BEEST_DISPATCH_APP_CLIENT_ID }} - private-key: ${{ secrets.BEEST_DISPATCH_APP_PRIVATE_KEY }} + client-id: ${{ vars.BEEST_GH_APP_CLIENT_ID }} + private-key: ${{ secrets.BEEST_GH_APP_PRIVATE_KEY }} owner: StackVista repositories: beest permission-actions: write From 44b78c221dd28c9aeab0ed6944ab36ebe2f3ec55 Mon Sep 17 00:00:00 2001 From: Louis Parkin Date: Tue, 11 Aug 2026 10:13:57 +0200 Subject: [PATCH 4/4] STAC-25565 dispatch the consolidated beest pin field beest replaces agent_hash_under_test with hashes_under_test in StackVista/beest#63, so the dispatch has to send agent= through the new field. Must land together with that PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/beest-verification.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beest-verification.yml b/.github/workflows/beest-verification.yml index ccea8754e43e..b8b7e435e265 100644 --- a/.github/workflows/beest-verification.yml +++ b/.github/workflows/beest-verification.yml @@ -107,7 +107,7 @@ jobs: args+=(--field "agent_branch_under_test=${AGENT_BRANCH}") args+=(--field "no_destroy=${NO_DESTROY}") if [ -n "${agent_hash}" ]; then - args+=(--field "agent_hash_under_test=${agent_hash}") + args+=(--field "hashes_under_test=agent=${agent_hash}") fi if [ -n "${SCENARIOS}" ]; then args+=(--field "scenarios=${SCENARIOS}")