mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
# Description of Changes This PR refactors Gradle caching across the GitHub Actions workflows to improve cache reuse, reduce dependency resolution overhead, and shorten CI execution times. ### What was changed - Replaced multiple `gradle/actions/setup-gradle` steps with a unified `actions/cache`-based Gradle User Home cache strategy. - Standardized cache paths across workflows to include: - `~/.gradle/caches` - `~/.gradle/wrapper` - Introduced consistent cache keys using: - Runner OS - Runner architecture - JDK version - Hashes of Gradle wrapper, version catalog, Gradle build files, and project build scripts. - Added restore keys to maximize cache hit rates across similar environments. - Added a new **`gradle-cache-prime`** job in the main build workflow that: - Restores or creates the shared Gradle cache. - Resolves backend dependencies before downstream jobs execute. - Makes the populated cache available to subsequent jobs. - Updated workflow dependencies so Gradle-based jobs wait for the cache priming job before execution. - Simplified and unified Gradle cache handling across numerous CI workflows, including backend builds, OpenAPI generation, database migration tests, Docker tests, Tauri builds, Swagger generation, enterprise builds, release workflows, and license generation. - Updated workflow comments to reflect the new caching strategy and shared cache behavior. ### Why the change was made The previous workflows used a mixture of Gradle setup actions and partial dependency caches, leading to duplicated dependency downloads, inconsistent cache behavior, and longer CI runtimes. Consolidating all workflows onto a shared Gradle User Home cache with a dedicated cache priming job improves cache reuse, reduces unnecessary dependency resolution, and makes CI execution more consistent. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details.
375 lines
18 KiB
YAML
375 lines
18 KiB
YAML
name: Enterprise E2E (Playwright)
|
|
|
|
# Enterprise Playwright suite — exercises premium-key gated features (audit,
|
|
# teams, analytics) plus full OAuth + SAML logins via the Keycloak compose
|
|
# stacks under testing/compose. Slow and secret-gated, so it runs in four
|
|
# situations:
|
|
#
|
|
# - PRs that touch proprietary / premium / SSO compose / enterprise tests
|
|
# (driven by build.yml via workflow_call, path-filtered against
|
|
# .github/config/.files.yaml `proprietary`),
|
|
# - every push to main (post-merge safety net),
|
|
# - on a nightly cron schedule (catches Keycloak image drift, license
|
|
# expiry, upstream proprietary changes),
|
|
# - manual workflow_dispatch.
|
|
|
|
on:
|
|
workflow_call:
|
|
push:
|
|
branches: ["main"]
|
|
schedule:
|
|
- cron: "0 4 * * *"
|
|
workflow_dispatch:
|
|
|
|
# No `concurrency:` block here on purpose. When this workflow is called via
|
|
# workflow_call from build.yml, ${{ github.workflow }}/event_name/pr_number
|
|
# resolve to the *caller's* values, producing the same group key as build.yml
|
|
# and causing the workflow_call instantiation to self-cancel — the job
|
|
# silently fails to spawn while `${{ needs.X.result }}` still reports
|
|
# `failure`. Standalone runs (push-to-main, nightly cron, dispatch) don't
|
|
# overlap often enough to need explicit concurrency control.
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
pick:
|
|
uses: ./.github/workflows/_runner-pick.yml
|
|
|
|
playwright-e2e-enterprise:
|
|
needs: pick
|
|
# Skip on fork PRs / untrusted authors: they have no PREMIUM_KEY_ENTERPRISE,
|
|
# so the suite can't boot premium and would fail. See the header comment.
|
|
# GitHub reports the skipped reusable workflow as success.
|
|
if: needs.pick.outputs.is_fork != 'true'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
env:
|
|
PREMIUM_KEY: ${{ secrets.PREMIUM_KEY_ENTERPRISE }}
|
|
PREMIUM_ENABLED: "true"
|
|
SYSTEM_ENABLEANALYTICS: "false"
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
|
with:
|
|
egress-policy: audit
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- name: Set up JDK 25
|
|
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
|
with:
|
|
java-version: "25"
|
|
distribution: "temurin"
|
|
- name: Cache Gradle User Home
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: |
|
|
~/.gradle/caches
|
|
~/.gradle/wrapper
|
|
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
|
|
restore-keys: |
|
|
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
|
|
gradle-${{ runner.os }}-${{ runner.arch }}-
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
cache-dependency-path: frontend/package-lock.json
|
|
- name: Install Task
|
|
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
|
|
- name: Install Playwright (chromium only)
|
|
run: task e2e:install -- chromium
|
|
- name: Build frontend (needed for playwright's vite preview webServer)
|
|
# Enterprise tests target :8080 (Spring Boot's bundled frontend), but
|
|
# playwright's webServer config still launches `vite preview --port 5173`
|
|
# before any test run, which needs dist/ to exist. VITE_BUILD_FOR_PREVIEW
|
|
# forces absolute asset paths so vite preview can serve deep SPA routes.
|
|
env:
|
|
VITE_BUILD_FOR_PREVIEW: "1"
|
|
run: task frontend:build
|
|
|
|
- name: Resolve kubernetes.docker.internal to localhost
|
|
# The compose stacks set KC_HOSTNAME=kubernetes.docker.internal so
|
|
# Keycloak issues redirect URIs against that host. Docker Desktop
|
|
# auto-resolves it; GHA runners don't. Map it to 127.0.0.1 so the
|
|
# browser-driven OAuth flow lands back on Stirling-PDF correctly.
|
|
run: |
|
|
echo "127.0.0.1 kubernetes.docker.internal" | sudo tee -a /etc/hosts
|
|
|
|
# Helper function used by all phases — boots `:stirling-pdf:bootRun`
|
|
# with the React frontend baked in (-PbuildWithFrontend=true) so the
|
|
# SPA serves on :8080 and OAuth/SAML callbacks land on the same host
|
|
# that the browser is interacting with.
|
|
- name: Define helpers
|
|
run: |
|
|
{
|
|
echo 'wait_for_backend() {'
|
|
echo ' start=$SECONDS'
|
|
echo ' for i in $(seq 1 300); do'
|
|
echo ' if curl -fsS http://localhost:8080/api/v1/info/status >/dev/null 2>&1; then'
|
|
echo ' echo "Backend up after $((SECONDS - start))s"; return 0'
|
|
echo ' fi; sleep 2'
|
|
echo ' done'
|
|
echo ' tail -200 /tmp/backend.log || true; return 1'
|
|
echo '}'
|
|
echo 'stop_backend() {'
|
|
echo ' if [ -f /tmp/backend.pid ]; then'
|
|
echo ' kill "$(cat /tmp/backend.pid)" 2>/dev/null || true'
|
|
echo ' rm -f /tmp/backend.pid'
|
|
echo ' fi'
|
|
echo ' pkill -f "gradlew :stirling-pdf:bootRun" 2>/dev/null || true'
|
|
echo ' for i in $(seq 1 30); do'
|
|
echo ' curl -fsS http://localhost:8080/api/v1/info/status >/dev/null 2>&1 || return 0'
|
|
echo ' sleep 1'
|
|
echo ' done'
|
|
echo '}'
|
|
} > /tmp/helpers.sh
|
|
chmod +x /tmp/helpers.sh
|
|
|
|
# ───────── OAuth round-trip ─────────
|
|
- name: Bring up Keycloak (OAuth realm)
|
|
working-directory: testing/compose
|
|
run: docker compose -f docker-compose-keycloak-oauth.yml up -d --no-deps keycloak-oauth-db keycloak-oauth
|
|
- name: Wait for Keycloak (OAuth) ready
|
|
working-directory: testing/compose
|
|
run: |
|
|
for i in $(seq 1 60); do
|
|
bash validate-oauth-test.sh 2>/dev/null && exit 0 || true
|
|
# validate script also pings stirling on :8080 — accept just the
|
|
# keycloak realm as our gate here, stirling boots in the next step
|
|
curl -fsS http://localhost:9080/realms/stirling-oauth >/dev/null 2>&1 && exit 0
|
|
sleep 5
|
|
done
|
|
docker compose -f docker-compose-keycloak-oauth.yml logs --tail=200 keycloak-oauth
|
|
exit 1
|
|
- name: Boot Stirling-PDF (frontend baked in, OAuth env)
|
|
env:
|
|
SECURITY_ENABLELOGIN: "true"
|
|
SECURITY_LOGINMETHOD: "all"
|
|
SECURITY_OAUTH2_ENABLED: "true"
|
|
SECURITY_OAUTH2_AUTOCREATEUSER: "true"
|
|
# Keycloak issues redirect URIs against KC_HOSTNAME, which the
|
|
# compose default sets to kubernetes.docker.internal. Match here
|
|
# (resolves to localhost via /etc/hosts mapping above).
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_ISSUER: "http://kubernetes.docker.internal:9080/realms/stirling-oauth"
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_CLIENTID: "stirling-pdf-client"
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_CLIENTSECRET: "test-client-secret-change-in-production"
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_USEASUSERNAME: "email"
|
|
SECURITY_OAUTH2_CLIENT_KEYCLOAK_SCOPES: "openid,profile,email"
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
nohup ./gradlew :stirling-pdf:bootRun -PbuildWithFrontend=true > /tmp/backend.log 2>&1 &
|
|
echo $! > /tmp/backend.pid
|
|
wait_for_backend
|
|
- name: Run enterprise OAuth Playwright tests
|
|
id: oauth-tests
|
|
env:
|
|
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results-oauth.json
|
|
run: task e2e:enterprise -- --grep "OAuth"
|
|
- name: Stop backend + tear down OAuth Keycloak
|
|
if: always()
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
stop_backend
|
|
(cd testing/compose && docker compose -f docker-compose-keycloak-oauth.yml down -v)
|
|
|
|
# ───────── SAML round-trip ─────────
|
|
- name: Bring up Keycloak (SAML realm)
|
|
working-directory: testing/compose
|
|
run: docker compose -f docker-compose-keycloak-saml.yml up -d --no-deps keycloak-saml-db keycloak-saml
|
|
- name: Wait for Keycloak (SAML) ready
|
|
working-directory: testing/compose
|
|
run: |
|
|
for i in $(seq 1 60); do
|
|
curl -fsS http://localhost:9080/realms/stirling-saml >/dev/null 2>&1 && exit 0
|
|
sleep 5
|
|
done
|
|
docker compose -f docker-compose-keycloak-saml.yml logs --tail=200 keycloak-saml
|
|
exit 1
|
|
- name: Generate SAML SP certs + fetch Keycloak IdP cert
|
|
# The .pem/.crt/.key files are gitignored (test-only certs); the
|
|
# docker-based start-saml-test.sh generates them at runtime, so do
|
|
# the same in CI before bootRun reads them.
|
|
working-directory: testing/compose
|
|
run: |
|
|
openssl req -x509 -newkey rsa:2048 \
|
|
-keyout saml-private-key.key \
|
|
-out saml-public-cert.crt \
|
|
-days 3650 -nodes \
|
|
-subj "/CN=stirling-pdf-saml-sp" >/dev/null 2>&1
|
|
# Fetch Keycloak's SAML signing cert from the realm descriptor
|
|
CERT_BODY=$(curl -sf http://localhost:9080/realms/stirling-saml/protocol/saml/descriptor \
|
|
| awk 'BEGIN{RS="<[^>]*X509Certificate>|</[^>]*X509Certificate>"} NR==2{gsub(/[[:space:]]+/,""); print; exit}')
|
|
{
|
|
echo "-----BEGIN CERTIFICATE-----"
|
|
echo "$CERT_BODY"
|
|
echo "-----END CERTIFICATE-----"
|
|
} > keycloak-saml-cert.pem
|
|
test -s saml-private-key.key
|
|
test -s saml-public-cert.crt
|
|
test -s keycloak-saml-cert.pem
|
|
echo "✓ SAML certs prepared"
|
|
- name: Boot Stirling-PDF (frontend baked in, SAML env)
|
|
env:
|
|
SECURITY_ENABLELOGIN: "true"
|
|
SECURITY_LOGINMETHOD: "all"
|
|
SECURITY_SAML2_ENABLED: "true"
|
|
SECURITY_SAML2_AUTOCREATEUSER: "true"
|
|
SECURITY_SAML2_PROVIDER: "keycloak"
|
|
SECURITY_SAML2_REGISTRATIONID: "keycloak"
|
|
SECURITY_SAML2_IDP_ISSUER: "http://localhost:9080/realms/stirling-saml"
|
|
SECURITY_SAML2_IDP_ENTITYID: "http://localhost:9080/realms/stirling-saml"
|
|
SECURITY_SAML2_IDP_METADATAURI: "http://localhost:9080/realms/stirling-saml/protocol/saml/descriptor"
|
|
SECURITY_SAML2_IDPSINGLELOGINURL: "http://localhost:9080/realms/stirling-saml/protocol/saml"
|
|
SECURITY_SAML2_IDPSINGLELOGOUTURL: "http://localhost:9080/realms/stirling-saml/protocol/saml"
|
|
SECURITY_SAML2_IDP_CERT: "${{ github.workspace }}/testing/compose/keycloak-saml-cert.pem"
|
|
SECURITY_SAML2_PRIVATEKEY: "${{ github.workspace }}/testing/compose/saml-private-key.key"
|
|
SECURITY_SAML2_SP_CERT: "${{ github.workspace }}/testing/compose/saml-public-cert.crt"
|
|
# Realm registers the SP entity as the metadata URL — see
|
|
# keycloak-realm-saml.json `clientId`. Match it here so Keycloak
|
|
# accepts the AuthnRequest issuer.
|
|
SECURITY_SAML2_SP_ENTITYID: "http://localhost:8080/saml2/service-provider-metadata/keycloak"
|
|
SECURITY_SAML2_SP_ACS: "http://localhost:8080/login/saml2/sso/keycloak"
|
|
SECURITY_SAML2_SP_SLS: "http://localhost:8080/logout/saml2/slo"
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
nohup ./gradlew :stirling-pdf:bootRun -PbuildWithFrontend=true > /tmp/backend.log 2>&1 &
|
|
echo $! > /tmp/backend.pid
|
|
wait_for_backend
|
|
- name: Run enterprise SAML Playwright tests
|
|
id: saml-tests
|
|
env:
|
|
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results-saml.json
|
|
run: task e2e:enterprise -- --grep "SAML"
|
|
- name: Stop backend + tear down SAML Keycloak
|
|
if: always()
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
stop_backend
|
|
(cd testing/compose && docker compose -f docker-compose-keycloak-saml.yml down -v)
|
|
|
|
# ───────── License-gated feature tests (no IdP needed) ─────────
|
|
- name: Wipe DB so InitialSecuritySetup re-runs with admin/adminadmin
|
|
# Earlier phases (OAuth, SAML) create the default admin/stirling user.
|
|
# InitialSecuritySetup only honours SECURITY_INITIALLOGIN_* when the
|
|
# admin user doesn't already exist, so the persisted DB has to be
|
|
# cleared between phases for the feature env vars to take effect.
|
|
run: |
|
|
rm -f app/core/configs/stirling-pdf-DB*.mv.db
|
|
rm -rf app/core/configs/backup
|
|
- name: Boot Stirling-PDF (frontend baked in, premium only)
|
|
env:
|
|
SECURITY_INITIALLOGIN_USERNAME: admin
|
|
SECURITY_INITIALLOGIN_PASSWORD: adminadmin
|
|
SECURITY_ENABLELOGIN: "true"
|
|
SECURITY_LOGINMETHOD: "all"
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
nohup ./gradlew :stirling-pdf:bootRun -PbuildWithFrontend=true > /tmp/backend.log 2>&1 &
|
|
echo $! > /tmp/backend.pid
|
|
wait_for_backend
|
|
- name: Run enterprise feature Playwright tests
|
|
id: feature-tests
|
|
env:
|
|
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results-feature.json
|
|
run: task e2e:enterprise -- --grep "Enterprise license"
|
|
- name: Print backend log on failure
|
|
if: failure()
|
|
run: |
|
|
echo "::group::Enterprise backend log"
|
|
tail -500 /tmp/backend.log || true
|
|
echo "::endgroup::"
|
|
- name: Stop backend (final)
|
|
if: always()
|
|
run: |
|
|
source /tmp/helpers.sh
|
|
stop_backend
|
|
- name: Flag flaky tests
|
|
# Runs regardless of the test outcomes: a flaky test (passed on retry)
|
|
# leaves its step green, so this is the only place it surfaces. Merges
|
|
# all three phase reports (some may be absent if an earlier phase hard-
|
|
# failed and skipped the rest). Emits ::warning:: annotations + a job
|
|
# summary; never fails the job.
|
|
if: always()
|
|
working-directory: frontend
|
|
run: >
|
|
npx tsx editor/scripts/report-flaky-tests.mts
|
|
"${{ github.workspace }}/frontend/playwright-report/results-oauth.json"
|
|
"${{ github.workspace }}/frontend/playwright-report/results-saml.json"
|
|
"${{ github.workspace }}/frontend/playwright-report/results-feature.json"
|
|
- name: Upload Playwright report
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: playwright-report-enterprise-${{ github.run_id }}
|
|
path: frontend/playwright-report/
|
|
retention-days: 7
|
|
|
|
# Multi-node regression: builds + seeds the clustered stack (testing/compose/docker-compose-multinode.yml)
|
|
# and runs behave features/multinode. Licence-gated, so it runs after the Playwright job (not in parallel).
|
|
multinode-e2e:
|
|
needs: [pick, playwright-e2e-enterprise]
|
|
# Nightly cron + manual dispatch only (heavy build), fork-gated for the licence secret.
|
|
if: >-
|
|
always() && needs.pick.outputs.is_fork != 'true'
|
|
&& (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
|
runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || format('depot-ubuntu-24.04-{0}', inputs.depot_cores || '8') }}
|
|
timeout-minutes: 60
|
|
env:
|
|
PREMIUM_KEY: ${{ secrets.PREMIUM_KEY_ENTERPRISE }}
|
|
PREMIUM_ENABLED: "true"
|
|
SYSTEM_ENABLEANALYTICS: "false"
|
|
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
|
MN_COMPOSE: docker-compose-multinode.yml
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
|
with:
|
|
egress-policy: audit
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- name: Set up Python
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: "3.12"
|
|
cache: "pip"
|
|
cache-dependency-path: ./testing/cucumber/requirements.txt
|
|
- name: Install behave test deps
|
|
run: |
|
|
pip install --require-hashes --only-binary=:all: -r ./testing/cucumber/requirements.txt
|
|
- name: Build the multi-node image
|
|
working-directory: testing/compose
|
|
run: docker compose -f "$MN_COMPOSE" build
|
|
- name: Bring up the cluster and wait for both nodes healthy
|
|
working-directory: testing/compose
|
|
run: |
|
|
docker compose -f "$MN_COMPOSE" up -d
|
|
for i in $(seq 1 90); do
|
|
h1=$(docker inspect -f '{{.State.Health.Status}}' multinode-stirling-1 2>/dev/null || echo starting)
|
|
h2=$(docker inspect -f '{{.State.Health.Status}}' multinode-stirling-2 2>/dev/null || echo starting)
|
|
if [ "$h1" = healthy ] && [ "$h2" = healthy ]; then echo "both nodes healthy"; exit 0; fi
|
|
sleep 5
|
|
done
|
|
echo "::error::nodes did not become healthy"
|
|
docker compose -f "$MN_COMPOSE" logs --tail=200 stirling-1 stirling-2
|
|
exit 1
|
|
- name: Seed the cluster (teams, users, S3 connection, policy)
|
|
working-directory: testing/compose
|
|
run: docker compose -f "$MN_COMPOSE" --profile seed run --rm seed
|
|
- name: Run multi-node regression (implemented guarantees)
|
|
working-directory: testing/cucumber
|
|
# -e overrides behave.ini's exclusion of features/multinode; ~@known_gap skips any tracked-gap scenarios.
|
|
run: python -m behave features/multinode -e "features/enterprise" --tags="~@known_gap ~@destructive" --no-capture -f plain
|
|
- name: Run multi-node failover (destructive)
|
|
working-directory: testing/cucumber
|
|
run: python -m behave features/multinode -e "features/enterprise" --tags="@destructive ~@known_gap" --no-capture -f plain
|
|
- name: Dump node logs on failure
|
|
if: failure()
|
|
working-directory: testing/compose
|
|
run: docker compose -f "$MN_COMPOSE" logs --tail=400 stirling-1 stirling-2
|
|
- name: Tear down
|
|
if: always()
|
|
working-directory: testing/compose
|
|
run: docker compose -f "$MN_COMPOSE" --profile seed down -v --remove-orphans
|