From 2b25803e2cec51955ba12265bbe0c0804a0904ba Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Sat, 30 May 2026 23:06:58 +0200 Subject: [PATCH 1/2] Rework e2e CI: PR-comment + dispatch triggers, run summary, script tidy-up Make the Seqera Platform showcase e2e test launchable beyond the [e2e prod]/[e2e stage] commit tag, surface its result, and consolidate CI-only scripts under .github/. - Extract e2e logic into a reusable .github/workflows/e2e.yml (workflow_call + workflow_dispatch). build.yml calls it after the build is green; standalone dispatch runs it for a chosen environment. - Add workflow_dispatch inputs to build.yml (e2e, release) so both can be triggered manually, mirroring the commit-tag behaviour. - Merge the comment trigger into a single pr-commands.yml (replacing claude.yml) so each PR comment fires one workflow run, not two. A maintainer's /e2e-prod or /e2e-stage comment delegates to e2e.yml, which reacts, builds the PR head, launches, and posts the run URL back. - Capture the dispatched showcase-automation run URL (gh workflow run returns nothing) and surface it: an aggregating summary job in build.yml plus a self-summary in the launch script. - Move release.sh, test-ci.sh and the e2e launch script into .github/scripts/; move the e2e docker build context into .github/test-e2e/ (installScratch path updated accordingly). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Phil Ewels --- .github/scripts/e2e-launch.sh | 125 ++++++++++++++++ release.sh => .github/scripts/release.sh | 0 test-ci.sh => .github/scripts/test-ci.sh | 0 {test-e2e => .github/test-e2e}/.gitignore | 0 {test-e2e => .github/test-e2e}/Dockerfile | 0 .github/workflows/build.yml | 148 ++++++++++++------- .github/workflows/claude.yml | 41 ------ .github/workflows/e2e.yml | 172 ++++++++++++++++++++++ .github/workflows/pr-commands.yml | 67 +++++++++ packing.gradle | 2 +- test-e2e/run.sh | 92 ------------ 11 files changed, 461 insertions(+), 186 deletions(-) create mode 100644 .github/scripts/e2e-launch.sh rename release.sh => .github/scripts/release.sh (100%) rename test-ci.sh => .github/scripts/test-ci.sh (100%) rename {test-e2e => .github/test-e2e}/.gitignore (100%) rename {test-e2e => .github/test-e2e}/Dockerfile (100%) delete mode 100644 .github/workflows/claude.yml create mode 100644 .github/workflows/e2e.yml create mode 100644 .github/workflows/pr-commands.yml delete mode 100644 test-e2e/run.sh diff --git a/.github/scripts/e2e-launch.sh b/.github/scripts/e2e-launch.sh new file mode 100644 index 0000000000..9822707ee5 --- /dev/null +++ b/.github/scripts/e2e-launch.sh @@ -0,0 +1,125 @@ +# +# Copyright 2013-2026, Seqera Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +set -e + +# Build a snapshot Nextflow launcher container and trigger the Seqera Platform +# showcase e2e test, surfacing the dispatched run URL via step outputs/summary. +# ENVIRONMENT ('production'|'staging') is set by the workflow; if unset it falls +# back to grepping the commit message for '[e2e prod]'. +SHOWCASE_REPO=${SHOWCASE_REPO:-'seqeralabs/showcase-automation'} +REPO_ROOT="$(git rev-parse --show-toplevel)" +# docker build context: Dockerfile + assembled runtime (installScratch targets its .nextflow/) +CTX="$REPO_ROOT/.github/test-e2e" + +# cleanup +rm -rf "$CTX/.nextflow" && mkdir -p "$CTX/.nextflow" +# copy nextflow dependencies +(cd "$REPO_ROOT" +export NXF_PLUGINS_DIR=$PWD/build/plugins +make releaseInfo assemble installScratch +) + +# copy nextflow plugins +cp -r "$REPO_ROOT/build/plugins" "$CTX/.nextflow/" +# copy nextflow launcher script +cp "$REPO_ROOT/nextflow" "$CTX/" && chmod +x "$CTX/nextflow" +cp "$REPO_ROOT/modules/nextflow/src/main/resources/META-INF/build-info.properties" "$CTX/" +source "$CTX/build-info.properties" + +if [ -z "$version" ]; then + echo "Error: version is empty or missing"; exit 1 +fi +if [ -z "$build" ]; then + echo "Error: build is empty or missing"; exit 1 +fi +if [ -z "$commitId" ]; then + echo "Error: commitId is empty or missing"; exit 1 +fi + +echo "version : $version" +echo "build : $build" +echo "commit id: $commitId" + +# +# build a scratch container image with assembled nextflow runtime and plugins +# +tag=${version}-${commitId} +base=${base:-'public.cr.seqera.io/platform/nf-launcher:j17-base'} +repository=${repository:-'public.cr.seqera.io/snapshots/nextflow-scratch'} +image=${repository}:${tag} + +docker buildx build \ + --platform linux/amd64 \ + --push \ + --progress=plain \ + --tag ${image} \ + --build-arg TARGETPLATFORM=linux/amd64 \ + "$CTX" +echo "Nextflow snapshots launcher image $image" + +# +# Create an ephemeral container with the scratch image and base Platform launcher image +# +launcher=$(wave -i ${base} --include ${image} --platform linux/amd64 --config-env NXF_HOME=/.nextflow --config-env NXF_SYNTAX_PARSER=v1) +echo "Running Platform tests using image launcher: $launcher" + +# determine the e2e environment: prefer $ENVIRONMENT, else fall back to the commit message +if [ -z "$ENVIRONMENT" ]; then + [ -z "$COMMIT_MESSAGE" ] && COMMIT_MESSAGE=$(git show -s --format='%s') + if echo "$COMMIT_MESSAGE" | grep -q "\[e2e prod\]"; then + ENVIRONMENT="production" + else + ENVIRONMENT="staging" + fi +fi + +# +# Launch the showcase automation +# see https://github.com/seqeralabs/showcase-automation/ +# +workflow_file="seqera-showcase-${ENVIRONMENT}.yml" +echo "Launching ${workflow_file} in ${SHOWCASE_REPO}" +dispatch_time=$(date -u +%Y-%m-%dT%H:%M:%SZ) +gh workflow run "${workflow_file}" --repo "${SHOWCASE_REPO}" -f launch_container=${launcher} + +# `gh workflow run` does not return the dispatched run, so poll for the newest +# run created at/after the dispatch time (falling back to the workflow runs page) +run_url="" +for attempt in $(seq 1 12); do + sleep 5 + run_url=$(gh run list --repo "${SHOWCASE_REPO}" --workflow "${workflow_file}" --event workflow_dispatch --limit 10 \ + --json url,createdAt --jq "[.[] | select(.createdAt >= \"${dispatch_time}\")] | sort_by(.createdAt) | last | .url // empty" 2>/dev/null || true) + [ -n "$run_url" ] && break +done +[ -z "$run_url" ] && run_url="https://github.com/${SHOWCASE_REPO}/actions/workflows/${workflow_file}" +echo "Showcase run: $run_url" + +# expose results to the calling workflow (step outputs) and the run page (summary) +if [ -n "$GITHUB_OUTPUT" ]; then + { + echo "environment=${ENVIRONMENT}" + echo "launcher_image=${launcher}" + echo "showcase_url=${run_url}" + } >> "$GITHUB_OUTPUT" +fi +if [ -n "$GITHUB_STEP_SUMMARY" ]; then + { + echo "### e2e showcase launched against \`${ENVIRONMENT}\`" + echo "- Run: [${run_url}](${run_url})" + echo "- Launcher image: \`${launcher}\`" + echo "> CI only launches the test — open the run above to confirm it passed." + } >> "$GITHUB_STEP_SUMMARY" +fi diff --git a/release.sh b/.github/scripts/release.sh similarity index 100% rename from release.sh rename to .github/scripts/release.sh diff --git a/test-ci.sh b/.github/scripts/test-ci.sh similarity index 100% rename from test-ci.sh rename to .github/scripts/test-ci.sh diff --git a/test-e2e/.gitignore b/.github/test-e2e/.gitignore similarity index 100% rename from test-e2e/.gitignore rename to .github/test-e2e/.gitignore diff --git a/test-e2e/Dockerfile b/.github/test-e2e/Dockerfile similarity index 100% rename from test-e2e/Dockerfile rename to .github/test-e2e/Dockerfile diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 336d639af3..5e53de2012 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,19 @@ on: pull_request: types: [opened, reopened, synchronize] workflow_dispatch: + inputs: + e2e: + description: 'Launch the e2e showcase test after build (same as the [e2e ...] commit tag)' + type: choice + options: + - 'none' + - 'staging' + - 'production' + default: 'none' + release: + description: 'Run the release process after build + tests (same as the [release] commit tag)' + type: boolean + default: false permissions: contents: read @@ -164,7 +177,7 @@ jobs: export GOOGLE_APPLICATION_CREDENTIALS="$RUNNER_TEMP/google_credentials.json" fi make clean assemble install - bash test-ci.sh + bash .github/scripts/test-ci.sh env: TEST_JDK: ${{ matrix.java_version }} TEST_MODE: ${{ matrix.test_mode }} @@ -198,59 +211,32 @@ jobs: validation-tests.tar.gz integration-tests.tar.gz + # Launch the e2e showcase test once the build is green. Triggered by the + # [e2e stage]/[e2e prod] commit tag, or the workflow_dispatch `e2e` input. + # All logic lives in the reusable .github/workflows/e2e.yml workflow. test-e2e: - if: ${{ contains(needs.build.outputs.commit_message,'[e2e stage]') || contains(needs.build.outputs.commit_message,'[e2e prod]') }} - needs: build - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 1 - submodules: true - persist-credentials: false - - - name: Setup Java 17 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 - with: - java-version: 17 - distribution: 'temurin' - architecture: x64 - cache: gradle - - - name: Setup env - env: - NEEDS_BUILD_OUTPUTS_COMMIT_MESSAGE: ${{ needs.build.outputs.commit_message }} - run: | - wget -q -O wave https://github.com/seqeralabs/wave-cli/releases/download/v1.4.1/wave-1.4.1-linux-x86_64 - chmod +x wave - mv wave /usr/local/bin/ - # Use heredoc-style delimiter so multi-line / quote-containing - # commit messages survive intact (run.sh greps for `[e2e prod]`). - { - echo "COMMIT_MESSAGE<> "$GITHUB_ENV" - - - name : Docker Login to Seqera public CR - uses : docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 - with : - registry : "public.cr.seqera.io" - username : "public-cr-admin" - password : ${{ secrets.SEQERA_PUBLIC_CR_PASSWORD }} - - - name: Launch tests - run: | - cd test-e2e - bash run.sh - env: - GITHUB_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} - GRADLE_OPTS: '-Dorg.gradle.daemon=false' + needs: build + if: >- + contains(needs.build.outputs.commit_message, '[e2e stage]') || + contains(needs.build.outputs.commit_message, '[e2e prod]') || + ( github.event_name == 'workflow_dispatch' && inputs.e2e != 'none' ) + uses: ./.github/workflows/e2e.yml + with: + # inputs.e2e is the dispatch choice ('' on push/PR, so it falls through) + environment: ${{ inputs.e2e || (contains(needs.build.outputs.commit_message, '[e2e prod]') && 'production' || 'staging') }} + secrets: + SEQERA_PUBLIC_CR_PASSWORD: ${{ secrets.SEQERA_PUBLIC_CR_PASSWORD }} + AUTOMATION_GITHUB_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} + # Run the release once build is green and tests pass. Triggered by the + # [release] commit tag, or the workflow_dispatch `release` input. release: - if: ${{ always() && contains(needs.build.outputs.commit_message, '[release]') && needs.build.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') }} + if: >- + always() && + needs.build.result == 'success' && + (needs.test.result == 'success' || needs.test.result == 'skipped') && + ( contains(needs.build.outputs.commit_message, '[release]') || + (github.event_name == 'workflow_dispatch' && inputs.release) ) needs: [build, test] runs-on: ubuntu-latest timeout-minutes: 10 @@ -301,7 +287,7 @@ jobs: echo "Starting release process..." echo "npr.apiUrl=$NPR_API_URL" >> gradle.properties echo "npr.apiKey=$NPR_API_KEY" >> gradle.properties - bash release.sh + bash .github/scripts/release.sh env: GRADLE_OPTS: '-Dorg.gradle.daemon=false' AWS_JAVA_V1_DISABLE_DEPRECATION_ANNOUNCEMENT: 'true' @@ -316,3 +302,61 @@ jobs: NPR_API_KEY: ${{ secrets.NPR_API_KEY }} # GitHub secrets GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Always-on consolidated summary so the run page shows, at a glance, what ran, + # whether it passed, and (for e2e) the showcase run URL to follow up on. + summary: + if: ${{ always() }} + needs: [build, test, test-e2e, release] + runs-on: ubuntu-latest + steps: + - name: Write CI summary + env: + BUILD_RESULT: ${{ needs.build.result }} + TEST_RESULT: ${{ needs.test.result }} + E2E_RESULT: ${{ needs.test-e2e.result }} + RELEASE_RESULT: ${{ needs.release.result }} + E2E_ENV: ${{ needs.test-e2e.outputs.environment }} + E2E_LAUNCHER: ${{ needs.test-e2e.outputs.launcher_image }} + E2E_URL: ${{ needs.test-e2e.outputs.showcase_url }} + run: | + icon() { + case "$1" in + success) echo "✅ success" ;; + failure) echo "❌ failure" ;; + cancelled) echo "⚠️ cancelled" ;; + skipped|"") echo "⏭️ skipped" ;; + *) echo "$1" ;; + esac + } + + # overall verdict: failure wins (short-circuits), then cancelled, else passed + overall="✅ passed" + for r in "$BUILD_RESULT" "$TEST_RESULT" "$E2E_RESULT" "$RELEASE_RESULT"; do + case "$r" in + failure) overall="❌ failed"; break ;; + cancelled) overall="⚠️ cancelled" ;; + esac + done + + { + echo "## Nextflow CI — ${overall}" + echo "" + echo "| Job | Result |" + echo "|---|---|" + echo "| Build + unit tests | $(icon "$BUILD_RESULT") |" + echo "| Integration tests | $(icon "$TEST_RESULT") |" + echo "| e2e showcase | $(icon "$E2E_RESULT") |" + echo "| Release | $(icon "$RELEASE_RESULT") |" + echo "" + } >> "$GITHUB_STEP_SUMMARY" + + if [ "$E2E_RESULT" = "success" ] && [ -n "$E2E_URL" ]; then + { + echo "### e2e showcase launched against \`${E2E_ENV}\`" + echo "- Run: [${E2E_URL}](${E2E_URL}) (launcher \`${E2E_LAUNCHER}\`)" + echo "> CI only launches the test — open the run to confirm it passed." + } >> "$GITHUB_STEP_SUMMARY" + elif [ "$E2E_RESULT" = "failure" ]; then + echo "### e2e showcase failed to launch — check the \`test-e2e\` job logs" >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index faf2c01a90..0000000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Claude PR Assistant - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened] - pull_request_review: - types: [submitted] - -concurrency: - group: claude-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} - cancel-in-progress: false - -jobs: - claude-code-action: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && contains(github.event.issue.body, '@claude')) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Run Claude PR Action - uses: anthropics/claude-code-action@f4fb5c6cdccc1ee7af63692f5d08d56efaa64cc8 # v1.0.121 - with: - anthropic_api_key: ${{ secrets.NEXTFLOW_ANTHROPIC_API_KEY }} - timeout_minutes: "60" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000000..7e285e3c1d --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,172 @@ +name: Nextflow e2e +# Launches the Seqera Platform "showcase" end-to-end test. Reusable workflow, +# triggered two ways: +# - workflow_call : from build.yml (commit [e2e ...] tag / dispatch input) +# and from pr-commands.yml (a maintainer's /e2e-* comment). +# - workflow_dispatch: run e2e standalone (no build/test gate) for a chosen env. +# +# When invoked from a comment, github.event.comment.* is inherited from the +# caller, so the environment is parsed from the comment and the result is posted +# back to the PR. Otherwise the environment comes from the `environment` input. +# +# What it does: builds a snapshot Nextflow launcher container, then dispatches +# seqera-showcase-.yml in seqeralabs/showcase-automation and surfaces the +# resulting run URL (see .github/scripts/e2e-launch.sh). + +on: + workflow_call: + inputs: + environment: + description: 'Platform environment to test against (production|staging). Omit to parse a /e2e-* PR comment.' + type: string + required: false + default: '' + secrets: + SEQERA_PUBLIC_CR_PASSWORD: + description: 'Seqera public container registry password (docker login)' + required: true + AUTOMATION_GITHUB_TOKEN: + description: 'Token used to dispatch & read the showcase-automation run' + required: true + outputs: + environment: + description: 'Environment the e2e test ran against' + value: ${{ jobs.e2e.outputs.environment }} + launcher_image: + description: 'Wave launcher image used for the test' + value: ${{ jobs.e2e.outputs.launcher_image }} + showcase_url: + description: 'URL of the dispatched showcase-automation run' + value: ${{ jobs.e2e.outputs.showcase_url }} + workflow_dispatch: + inputs: + environment: + description: 'Platform environment to test against' + type: choice + options: + - staging + - production + default: staging + +permissions: + contents: read + +jobs: + e2e: + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + pull-requests: write + outputs: + environment: ${{ steps.launch.outputs.environment }} + launcher_image: ${{ steps.launch.outputs.launcher_image }} + showcase_url: ${{ steps.launch.outputs.showcase_url }} + steps: + - name: Resolve environment + id: resolve + env: + EVENT_NAME: ${{ github.event_name }} + INPUT_ENVIRONMENT: ${{ inputs.environment }} + # Passed via env (not inlined) to avoid script injection from comments. + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + # Prefer the explicit input (build.yml / workflow_dispatch); note that + # github.event_name is the *caller's* event, so a comment-triggered call + # arrives as 'issue_comment' here, not 'workflow_call'. + environment="$INPUT_ENVIRONMENT" + if [ -z "$environment" ] && [ "$EVENT_NAME" = "issue_comment" ]; then + # match a line that *starts* with /e2e-prod or /e2e-stage + if printf '%s\n' "$COMMENT_BODY" | grep -qE '^[[:space:]]*/e2e-prod\b'; then + environment="production" + elif printf '%s\n' "$COMMENT_BODY" | grep -qE '^[[:space:]]*/e2e-stage\b'; then + environment="staging" + fi + fi + if [ -z "$environment" ]; then + echo "No e2e command matched — nothing to do." + echo "should_run=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "Resolved environment: $environment" + echo "environment=$environment" >> "$GITHUB_OUTPUT" + echo "should_run=true" >> "$GITHUB_OUTPUT" + + - name: Acknowledge comment + if: ${{ github.event_name == 'issue_comment' && steps.resolve.outputs.should_run == 'true' }} + continue-on-error: true # cosmetic reaction; never fail the run over it + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + COMMENT_ID: ${{ github.event.comment.id }} + run: | + gh api --method POST "repos/$REPO/issues/comments/$COMMENT_ID/reactions" -f content=eyes + + - name: Resolve PR head + id: pr + if: ${{ github.event_name == 'issue_comment' && steps.resolve.outputs.should_run == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + run: | + sha=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid') + repo=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRepositoryOwner,headRepository --jq '.headRepositoryOwner.login + "/" + .headRepository.name') + echo "sha=$sha" >> "$GITHUB_OUTPUT" + echo "repo=$repo" >> "$GITHUB_OUTPUT" + + - name: Checkout + if: ${{ steps.resolve.outputs.should_run == 'true' }} + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + submodules: true + persist-credentials: false + repository: ${{ steps.pr.outputs.repo || github.repository }} + ref: ${{ steps.pr.outputs.sha || github.ref }} + + - name: Setup Java 17 + if: ${{ steps.resolve.outputs.should_run == 'true' }} + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + java-version: 17 + distribution: 'temurin' + architecture: x64 + cache: gradle + + - name: Install Wave CLI + if: ${{ steps.resolve.outputs.should_run == 'true' }} + run: | + wget -q -O wave https://github.com/seqeralabs/wave-cli/releases/download/v1.4.1/wave-1.4.1-linux-x86_64 + chmod +x wave + mv wave /usr/local/bin/ + + - name: Docker Login to Seqera public CR + if: ${{ steps.resolve.outputs.should_run == 'true' }} + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: "public.cr.seqera.io" + username: "public-cr-admin" + password: ${{ secrets.SEQERA_PUBLIC_CR_PASSWORD }} + + - name: Launch e2e test + id: launch + if: ${{ steps.resolve.outputs.should_run == 'true' }} + run: bash .github/scripts/e2e-launch.sh + env: + ENVIRONMENT: ${{ steps.resolve.outputs.environment }} + GITHUB_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} + GRADLE_OPTS: '-Dorg.gradle.daemon=false' + + - name: Post result to pull request + if: ${{ github.event_name == 'issue_comment' && steps.resolve.outputs.should_run == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + E2E_ENV: ${{ steps.launch.outputs.environment }} + LAUNCHER_IMAGE: ${{ steps.launch.outputs.launcher_image }} + SHOWCASE_URL: ${{ steps.launch.outputs.showcase_url }} + run: | + gh pr comment "$PR_NUMBER" --repo "$REPO" --body \ + "🚀 e2e showcase launched against \`${E2E_ENV}\`: [showcase run](${SHOWCASE_URL}) (launcher \`${LAUNCHER_IMAGE}\`). CI only launches the test — open the run to confirm it passed." diff --git a/.github/workflows/pr-commands.yml b/.github/workflows/pr-commands.yml new file mode 100644 index 0000000000..e20e6bb875 --- /dev/null +++ b/.github/workflows/pr-commands.yml @@ -0,0 +1,67 @@ +name: PR commands +# Single entry point for comment/review-driven commands, so each comment fires +# one workflow run (not one per command). Routes to either: +# - Claude : a comment / review / issue mentioning @claude +# - e2e : a maintainer comments a line starting with /e2e-prod or /e2e-stage +# on a pull request (delegates to the reusable e2e.yml workflow). + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened] + pull_request_review: + types: [submitted] + +concurrency: + group: pr-commands-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && contains(github.event.issue.body, '@claude')) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Run Claude PR Action + uses: anthropics/claude-code-action@f4fb5c6cdccc1ee7af63692f5d08d56efaa64cc8 # v1.0.121 + with: + anthropic_api_key: ${{ secrets.NEXTFLOW_ANTHROPIC_API_KEY }} + timeout_minutes: "60" + + # Maintainer /e2e-prod or /e2e-stage on a PR. The reusable workflow inherits + # github.event.comment.* and parses the environment from the comment itself. + # NOTE: the maintainer gate is the security boundary — the e2e workflow builds + # and runs the PR head (incl. forks) with publish-capable secrets, so only + # trigger on PRs whose code you've reviewed. + e2e: + if: >- + github.event_name == 'issue_comment' && github.event.issue.pull_request && + ( contains(github.event.comment.body, '/e2e-prod') || contains(github.event.comment.body, '/e2e-stage') ) && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) + permissions: + contents: read + pull-requests: write + uses: ./.github/workflows/e2e.yml + secrets: + SEQERA_PUBLIC_CR_PASSWORD: ${{ secrets.SEQERA_PUBLIC_CR_PASSWORD }} + AUTOMATION_GITHUB_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} diff --git a/packing.gradle b/packing.gradle index dac4a5d247..9c5bf5f871 100644 --- a/packing.gradle +++ b/packing.gradle @@ -171,7 +171,7 @@ task installLauncher(type: Copy, dependsOn: ['pack']) { task installScratch(type: Copy, dependsOn: ['pack']) { from "$releaseDir/nextflow-$version-one.jar" - into "${rootProject.projectDir}/test-e2e/.nextflow/framework/$version/" + into "${rootProject.projectDir}/.github/test-e2e/.nextflow/framework/$version/" } /* diff --git a/test-e2e/run.sh b/test-e2e/run.sh deleted file mode 100644 index 5200f6bc28..0000000000 --- a/test-e2e/run.sh +++ /dev/null @@ -1,92 +0,0 @@ -# -# Copyright 2013-2026, Seqera Labs -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# cleanup -rm -rf .nextflow && mkdir .nextflow -# copy nextflow dependencies -(cd .. -export NXF_PLUGINS_DIR=$PWD/build/plugins -make releaseInfo assemble installScratch -) - -# copy nextflow plugins -cp -r ../build/plugins .nextflow/ -# copy nextflow launcher script -cp ../nextflow . && chmod +x nextflow -cp ../modules/nextflow/src/main/resources/META-INF/build-info.properties . -source build-info.properties - -if [ -z "$version" ]; then - echo "Error: version is empty or missing"; exit 1 -fi -if [ -z "$build" ]; then - echo "Error: build is empty or missing"; exit 1 -fi -if [ -z "$commitId" ]; then - echo "Error: commitId is empty or missing"; exit 1 -fi - -echo "version : $version" -echo "build : $build" -echo "commit id: $commitId" - -# -# build a scratch container image with assembled nextflow runtime and plugins -# -tag=${version}-${commitId} -base=${base:-'public.cr.seqera.io/platform/nf-launcher:j17-base'} -repository=${repository:-'public.cr.seqera.io/snapshots/nextflow-scratch'} -image=${repository}:${tag} - -docker buildx build \ - --platform linux/amd64 \ - --push \ - --progress=plain \ - --tag ${image} \ - --build-arg TARGETPLATFORM=linux/amd64 \ - . -echo "Nextflow snapshots launcher image $image" - -# -# Create an ephemeral container with the scratch image and base Platform launcher image -# -launcher=$(wave -i ${base} --include ${image} --platform linux/amd64 --config-env NXF_HOME=/.nextflow --config-env NXF_SYNTAX_PARSER=v1) -echo "Running Platform tests using image launcher: $launcher" - -# determining the e2e test environment checking the $COMMIT_MESSAGE -# that is set by GitHub action workflow. If it does not exist fallback -# to the commit message in the git rpeo -if [ -z "$COMMIT_MESSAGE" ]; then - COMMIT_MESSAGE=$(git show -s --format='%s') - echo "Commit message [from git]: $COMMIT_MESSAGE" -else - echo "Commit message [from gha]: $COMMIT_MESSAGE" -fi -if echo "$COMMIT_MESSAGE" | grep -q "\[e2e prod\]"; then - ENVIRONMENT="production" -else - ENVIRONMENT="staging" -fi - -# -# Finally launch the showcase automation -# see https://github.com/seqeralabs/showcase-automation/ -echo "Launching seqera-showcase-${ENVIRONMENT}" -gh workflow run \ - seqera-showcase-${ENVIRONMENT}.yml \ - --repo seqeralabs/showcase-automation \ - -f launch_container=${launcher} - From f0f823908a047deabc3e906c2db642354bfeee50 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Sat, 30 May 2026 23:23:35 +0200 Subject: [PATCH 2/2] Limit workflow_dispatch release to the push-trigger branches A dispatched release (release: true) could previously run from any branch selected in the Actions UI. Gate it to the same branches the push trigger allows (master, test*, dev*, STABLE-*) so a release can't be launched from an arbitrary feature branch. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Phil Ewels --- .github/workflows/build.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e53de2012..e6160e74d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -229,14 +229,20 @@ jobs: AUTOMATION_GITHUB_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} # Run the release once build is green and tests pass. Triggered by the - # [release] commit tag, or the workflow_dispatch `release` input. + # [release] commit tag, or the workflow_dispatch `release` input. The dispatch + # path is limited to the same branches the push trigger allows, so a release + # can't be kicked off from an arbitrary feature branch. release: if: >- always() && needs.build.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') && ( contains(needs.build.outputs.commit_message, '[release]') || - (github.event_name == 'workflow_dispatch' && inputs.release) ) + ( github.event_name == 'workflow_dispatch' && inputs.release && + ( github.ref_name == 'master' || + startsWith(github.ref_name, 'test') || + startsWith(github.ref_name, 'dev') || + startsWith(github.ref_name, 'STABLE-') ) ) ) needs: [build, test] runs-on: ubuntu-latest timeout-minutes: 10