From 02272431ba0d968c92f7842d431d6adc4482fb59 Mon Sep 17 00:00:00 2001 From: Lukasz Lancucki Date: Tue, 8 Sep 2026 15:33:00 +0100 Subject: [PATCH] ci: allow only one E2E run at a time and scan before E2E Pushes to main and release/** run an E2E step that takes hours, but the workflow had no concurrency guard. Every merge started another run while the previous one was still going, so several E2E suites executed at once against the shared TEST environment. Split push-release-branch.yml into a validation job and an E2E job, and put the concurrency group on the E2E job alone. The group name is the same literal there and in cron-main-e2e.yml, because the contended resource is the TEST environment rather than the branch: a merge to main, a push to a release branch and the twice-weekly scheduled run all want the same deployment, so one E2E cancels another wherever it came from. Scoping the group to the E2E job is what keeps make check-all and the SonarCloud scan out of it. Held at workflow level, the shared group let a scheduled or dispatched E2E run cancel a push run mid-validation, and the cancelling workflow repeats neither - main would have lost the lint, unit-test and scan signal it is the only place to get. The validation job carries no group at all, so nothing cancels it; E2E still waits on it through needs, the same ordering the single job had. Cancelling a running suite is safe here. Over the last 61 active days the final push to main had at least 180 minutes of clear runway on 59 of them, usually overnight, so each working day still ends with one completed suite on that day's tip, and the runs that get cancelled are on commits that were superseded anyway. Release branches see three pushes in five months. The SonarCloud scan also moves ahead of E2E within the validation job. Until #427 raised the job timeout, every push run on main reached it while still inside E2E, so the scan that followed was reported as skipped and main was never scanned - only pull requests were. release.yml can still run the suite outside this group; isolating it needs its own job there so serialization never covers the irreversible publish steps, tracked in MPT-25395. Refs MPT-25022. Co-Authored-By: Claude Opus 5 --- .github/workflows/cron-main-e2e.yml | 5 ++ .github/workflows/push-release-branch.yml | 57 +++++++++++++++++++---- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cron-main-e2e.yml b/.github/workflows/cron-main-e2e.yml index bfc0b5d0..32658692 100644 --- a/.github/workflows/cron-main-e2e.yml +++ b/.github/workflows/cron-main-e2e.yml @@ -4,6 +4,11 @@ on: - cron: '0 8 * * 1,4' workflow_dispatch: {} +# Limits E2E test concurrency to 1, canceling any previous run in progress. +concurrency: + group: e2e + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/push-release-branch.yml b/.github/workflows/push-release-branch.yml index ba5653ff..c360360e 100644 --- a/.github/workflows/push-release-branch.yml +++ b/.github/workflows/push-release-branch.yml @@ -10,10 +10,15 @@ permissions: contents: read jobs: + + # Validation and the SonarCloud scan are deliberately outside the E2E + # concurrency group. They are cheap, they are the only place main gets + # linted, tested and scanned, and the workflow that would cancel them + # (cron-main-e2e.yml) does not repeat them. build: runs-on: ubuntu-latest - timeout-minutes: 180 + timeout-minutes: 30 steps: - name: "Checkout" @@ -27,8 +32,6 @@ jobs: - name: "Create environment file" run: env | grep -E '^MPT_' > .env env: - RP_ENDPOINT: ${{ secrets.RP_ENDPOINT }} - RP_API_KEY: ${{ secrets.RP_API_KEY }} MPT_API_BASE_URL: ${{ secrets.MPT_API_BASE_URL }} MPT_API_TOKEN: ${{ secrets.MPT_API_TOKEN }} MPT_API_TOKEN_CLIENT: ${{ secrets.MPT_API_TOKEN_CLIENT }} @@ -38,6 +41,48 @@ jobs: - name: "Run validation & test" run: make check-all + - name: "Run SonarCloud Scan" + uses: SonarSource/sonarqube-scan-action@v8.2.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + - name: "Stop containers" + if: always() + run: make down + + # E2E carries the concurrency group on its own, so only one suite runs + # against the shared TEST environment at a time and a newer run cancels + # the one in flight. The group is a fixed literal shared with + # cron-main-e2e.yml because the contended resource is the environment, + # not the branch. Keeping it on this job rather than on the workflow is + # what stops a scheduled E2E run from cancelling the validation above. + e2e: + + needs: build + runs-on: ubuntu-latest + timeout-minutes: 180 + + concurrency: + group: e2e + cancel-in-progress: true + + steps: + - name: "Checkout" + uses: actions/checkout@v7 + + - name: "Build test containers" + run: make build + + - name: "Create environment file" + run: env | grep -E '^MPT_' > .env + env: + MPT_API_BASE_URL: ${{ secrets.MPT_API_BASE_URL }} + MPT_API_TOKEN: ${{ secrets.MPT_API_TOKEN }} + MPT_API_TOKEN_CLIENT: ${{ secrets.MPT_API_TOKEN_CLIENT }} + MPT_API_TOKEN_OPERATIONS: ${{ secrets.MPT_API_TOKEN_OPERATIONS }} + MPT_API_TOKEN_VENDOR: ${{ secrets.MPT_API_TOKEN_VENDOR }} + - name: "Run E2E test" continue-on-error: true run: make e2e args="--reportportal --rp-launch=$RP_LAUNCH --rp-api-key=$RP_API_KEY --rp-endpoint=$RP_ENDPOINT -o rp_launch_attributes=\"$RP_LAUNCH_ATTR\"" @@ -47,12 +92,6 @@ jobs: RP_API_KEY: ${{ secrets.RP_API_KEY }} RP_LAUNCH_ATTR: ref:${{ github.ref }} event_name:${{ github.event_name }} - - name: "Run SonarCloud Scan" - uses: SonarSource/sonarqube-scan-action@v8.2.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - name: "Stop containers" if: always() run: make down