From 76840d8a570eadc0f6e64d822ff577e89eab9cbd Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Thu, 28 May 2026 11:36:07 +0100 Subject: [PATCH] Add CI DB migration smoke test against v2.0/v2.5/v2.10 updates (#6453) --- .github/config/.files.yaml | 2 + .github/workflows/build.yml | 14 ++ .github/workflows/db-migration-test.yml | 93 +++++++++ .gitignore | 4 + app/proprietary/.gitignore | 2 + .../resources/db-migration-fixtures/README.md | 110 ++++++++++ .../stirling-pdf-v2.0.0.mv.db | Bin 0 -> 45056 bytes .../stirling-pdf-v2.10.0.mv.db | Bin 0 -> 73728 bytes .../stirling-pdf-v2.5.0.mv.db | Bin 0 -> 57344 bytes scripts/db-migration/run-migration-test.sh | 194 ++++++++++++++++++ 10 files changed, 419 insertions(+) create mode 100644 .github/workflows/db-migration-test.yml create mode 100644 app/proprietary/src/test/resources/db-migration-fixtures/README.md create mode 100644 app/proprietary/src/test/resources/db-migration-fixtures/stirling-pdf-v2.0.0.mv.db create mode 100644 app/proprietary/src/test/resources/db-migration-fixtures/stirling-pdf-v2.10.0.mv.db create mode 100644 app/proprietary/src/test/resources/db-migration-fixtures/stirling-pdf-v2.5.0.mv.db create mode 100644 scripts/db-migration/run-migration-test.sh diff --git a/.github/config/.files.yaml b/.github/config/.files.yaml index 8066cc362..5343246c2 100644 --- a/.github/config/.files.yaml +++ b/.github/config/.files.yaml @@ -38,6 +38,8 @@ project: &project - frontend/** - docker/** - scripts/RestartHelper.java + - scripts/db-migration/** + - .github/workflows/db-migration-test.yml frontend: &frontend - frontend/** diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 05029d2aa..ac47fe134 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,6 +68,18 @@ jobs: uses: ./.github/workflows/backend-build.yml secrets: inherit + db-migration-test: + # Boots the current bootJar against H2 fixtures captured from past + # releases (v2.0.0 / v2.5.0 / v2.10.0) and verifies admin login still + # works after Hibernate's ddl-auto=update migrates the schema. Gated on + # the `project` filter so doc-only PRs skip this ~5-minute job. + if: needs.files-changed.outputs.project == 'true' + needs: [files-changed] + permissions: + contents: read + uses: ./.github/workflows/db-migration-test.yml + secrets: inherit + check-generateOpenApiDocs: if: needs.files-changed.outputs.openapi == 'true' needs: [files-changed] @@ -184,6 +196,7 @@ jobs: needs: - files-changed - build + - db-migration-test - check-generateOpenApiDocs - frontend-validation - playwright-e2e @@ -208,6 +221,7 @@ jobs: RESULTS: | files-changed=${{ needs.files-changed.result }} build=${{ needs.build.result }} + db-migration-test=${{ needs.db-migration-test.result }} check-generateOpenApiDocs=${{ needs.check-generateOpenApiDocs.result }} frontend-validation=${{ needs.frontend-validation.result }} playwright-e2e=${{ needs.playwright-e2e.result }} diff --git a/.github/workflows/db-migration-test.yml b/.github/workflows/db-migration-test.yml new file mode 100644 index 000000000..2a386d1a5 --- /dev/null +++ b/.github/workflows/db-migration-test.yml @@ -0,0 +1,93 @@ +name: DB migration smoke test + +# Boots the current Stirling-PDF JAR against H2 fixtures captured from past +# releases (v2.0.0 / v2.5.0 / v2.10.0) and verifies admin login still works. +# Catches schema changes that would break existing user databases under +# Hibernate's `ddl-auto=update` upgrade path. + +on: + workflow_call: + +permissions: + contents: read + +jobs: + pick: + uses: ./.github/workflows/_runner-pick.yml + + migration-test: + needs: pick + runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-8' }} + timeout-minutes: 30 + env: + DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 + 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 dependency artifacts + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches/modules-2/files-2.1 + ~/.gradle/caches/modules-2/metadata-2.* + key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + with: + gradle-version: 9.3.1 + cache-disabled: true + + # No `-PnoSpotless` here yet because the upstream cache layer matches the + # backend build's; reuse keeps cold-cache cost identical. + - name: Build Stirling-PDF JAR + env: + MAVEN_USER: ${{ secrets.MAVEN_USER }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }} + run: ./gradlew :stirling-pdf:bootJar -PnoSpotless --no-daemon + + - name: Locate built JAR + id: jar + run: | + jar=$(find app/core/build/libs -maxdepth 1 -name 'Stirling-PDF*.jar' -o -name 'stirling-pdf*.jar' 2>/dev/null \ + | grep -vE '(-plain|-sources)\.jar$' | head -n 1) + if [[ -z "$jar" ]]; then + echo "::error::No JAR under app/core/build/libs" + ls -lah app/core/build/libs || true + exit 1 + fi + # Absolute path - the migration script pushd's into a temp workdir + # before invoking java, which would dangle a relative path. + jar=$(realpath "$jar") + echo "path=$jar" >> "$GITHUB_OUTPUT" + echo "Built JAR: $jar" + + - name: Run migration smoke test + env: + STIRLING_JAR: ${{ steps.jar.outputs.path }} + run: bash scripts/db-migration/run-migration-test.sh + + - name: Upload app logs on failure + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: db-migration-app-logs + # Path matches the preserved workdir in run-migration-test.sh - + # only failing fixtures leave a directory behind. + path: /tmp/stirling-migration-failed-*/app.log + retention-days: 7 + if-no-files-found: warn diff --git a/.gitignore b/.gitignore index c67ec629c..f4558af4c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,10 @@ customFiles/ configs/ watchedFolders/ clientWebUI/ +# Scratch dir used by local fixture-regeneration runs (see +# app/proprietary/src/test/resources/db-migration-fixtures/README.md). +# Holds downloaded JARs and disposable workdirs. Never committed. +.alpha-local/ !cucumber/ !cucumber/exampleFiles/ !cucumber/exampleFiles/example_html.zip diff --git a/app/proprietary/.gitignore b/app/proprietary/.gitignore index 5156eb74a..1a5610dc1 100644 --- a/app/proprietary/.gitignore +++ b/app/proprietary/.gitignore @@ -123,6 +123,8 @@ SwaggerDoc.json *.tar.gz *.rar *.db +# Whitelist the H2 fixtures that feed the version-migration CI smoke test. +!src/test/resources/db-migration-fixtures/*.mv.db /build /app/proprietary/build/ diff --git a/app/proprietary/src/test/resources/db-migration-fixtures/README.md b/app/proprietary/src/test/resources/db-migration-fixtures/README.md new file mode 100644 index 000000000..25450cf7f --- /dev/null +++ b/app/proprietary/src/test/resources/db-migration-fixtures/README.md @@ -0,0 +1,110 @@ +# DB migration test fixtures + +These `.mv.db` files are H2 databases captured from past Stirling-PDF releases. +They feed the CI smoke test that verifies a fresh build can still boot and +authenticate against a database created by an older version. + +| File | Source release | Tables | Notes | +|---|---|---|---| +| `stirling-pdf-v2.0.0.mv.db` | [v2.0.0](https://github.com/Stirling-Tools/Stirling-PDF/releases/tag/v2.0.0) | users, authorities, teams, sessions, audit_events, persistent_logins, invite_tokens, user_license_settings, user_settings | Pre-storage/workflow schema. | +| `stirling-pdf-v2.5.0.mv.db` | [v2.5.0](https://github.com/Stirling-Tools/Stirling-PDF/releases/tag/v2.5.0) | same as v2.0.0 | Schema unchanged from v2.0.0; intentionally kept as a separate fixture to exercise the "skip every other minor" upgrade path. | +| `stirling-pdf-v2.10.0.mv.db` | [v2.10.0](https://github.com/Stirling-Tools/Stirling-PDF/releases/tag/v2.10.0) | v2.5.0 tables + file_shares, file_share_accesses, stored_files, stored_file_blobs, storage_cleanup_entries, user_server_certificates, workflow_sessions, workflow_participants, participant_notifications | Adds the file-sharing and workflow signing schema. | + +All three were generated against H2 `2.3.232` and use the same on-disk file +format, so the runtime driver can open any of them without conversion. + +## What's in each fixture + +* `admin` user with the default password `stirling` (BCrypt `$2a$10$...`). +* The internal API user `STIRLING-PDF-BACKEND-API-USER`. +* `ROLE_ADMIN` authority row for the admin user. +* `Default` and `Internal` teams. +* `user_license_settings` row (singleton). + +`audit_events`, `sessions`, and `user_settings` are empty in the OSS-flavored +fixtures: those tables are written only on Enterprise builds (audit) or +require an HTTP-session-creating flow (sessions / settings) that the OSS form +login no longer exposes. The migration test only depends on the admin user +existing, so leaving these empty is intentional. + +## What the CI test checks + +`.github/workflows/db-migration-test.yml` runs `scripts/db-migration/run-migration-test.sh`, +which for each fixture: + +1. Copies the fixture into `configs/stirling-pdf-DB-2.3.232.mv.db` of a clean + working directory. +2. Boots the current `:stirling-pdf:bootJar` against it on a free port. +3. Waits for Spring to start (no `SchemaManagementException` in the log). +4. POSTs `{"username":"admin","password":"stirling"}` to `/api/v1/auth/login` + and asserts the response is `200 OK`. + +A red CI on this job means a schema change in the PR is not backwards +compatible with an existing user database. Common causes: + +* Adding a non-nullable column without a default. +* Renaming a column (Hibernate's `update` strategy adds the new column and + leaves the old one orphaned with the data still in it). +* Changing a column type in an incompatible way. +* Dropping or renaming a foreign-key target. + +## Regenerating fixtures + +There's no automated regenerator script - fixtures are rare to refresh and the +manual steps are short. For each version you want to capture: + +```bash +# 1. Download the JAR for that release (requires `gh` authenticated against +# github.com/Stirling-Tools/Stirling-PDF). +gh release download v2.10.0 \ + --repo Stirling-Tools/Stirling-PDF \ + --pattern 'Stirling-PDF-with-login.jar' \ + --output /tmp/stirling-v2.10.0.jar + +# 2. Boot the JAR in a clean working directory. DB_CLOSE_ON_EXIT=TRUE is +# the only override that matters - it makes the H2 file flush on JVM exit +# even if you Ctrl-C instead of going through a graceful shutdown. +workdir=$(mktemp -d) +mkdir -p "$workdir/configs" +cd "$workdir" +java -jar /tmp/stirling-v2.10.0.jar \ + --server.port=8089 \ + --spring.datasource.url='jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE;MODE=PostgreSQL' \ + & + +# 3. Wait until http://localhost:8089/login responds, then log in once to +# materialize whatever rows the app writes on first boot. +curl -sf -X POST -H 'Content-Type: application/json' \ + -d '{"username":"admin","password":"stirling"}' \ + http://localhost:8089/api/v1/auth/login + +# 4. Shut it down (any kill works - DB_CLOSE_ON_EXIT=TRUE handles the flush). +kill -TERM %1 && wait %1 + +# 5. Copy the .mv.db here, renamed for the version. +cp "$workdir/configs/stirling-pdf-DB-2.3.232.mv.db" \ + app/proprietary/src/test/resources/db-migration-fixtures/stirling-pdf-v2.10.0.mv.db +``` + +Requirements: Java 21+ (the historical JARs target Java 17 / 21). + +## Adding a new fixture + +When a new minor release ships, repeat the steps above for the new tag and +add a row to the table at the top of this file. Keep the historical fixtures - +the test gets stronger with each schema generation it covers. + +## Inspecting a fixture by hand + +The H2 driver bundled with the build ships an interactive shell: + +```bash +h2_jar=$(find ~/.gradle/caches/modules-2 -name 'h2-2.3.232.jar' | head -1) +cd app/proprietary/src/test/resources/db-migration-fixtures +java -cp "$h2_jar" org.h2.tools.Shell \ + -url 'jdbc:h2:file:./stirling-pdf-v2.10.0;ACCESS_MODE_DATA=r;MODE=PostgreSQL' \ + -user sa +``` + +`ACCESS_MODE_DATA=r` keeps the inspection read-only so you can't accidentally +mutate a committed fixture. diff --git a/app/proprietary/src/test/resources/db-migration-fixtures/stirling-pdf-v2.0.0.mv.db b/app/proprietary/src/test/resources/db-migration-fixtures/stirling-pdf-v2.0.0.mv.db new file mode 100644 index 0000000000000000000000000000000000000000..5402eb037553ed9c1a4ba06908d9d9c14e9ad597 GIT binary patch literal 45056 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 literal 0 HcmV?d00001 diff --git a/app/proprietary/src/test/resources/db-migration-fixtures/stirling-pdf-v2.10.0.mv.db b/app/proprietary/src/test/resources/db-migration-fixtures/stirling-pdf-v2.10.0.mv.db new file mode 100644 index 0000000000000000000000000000000000000000..56c00405567d6bb6e6ca8ff48519f844e5e2eb95 GIT binary patch literal 73728 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 } +fail() { printf '[migration-test][FAIL] %s\n' "$*" >&2; exit 1; } + +find_jar() { + local candidate + if [[ -n "$STIRLING_JAR" ]]; then + [[ -f "$STIRLING_JAR" ]] || fail "STIRLING_JAR='$STIRLING_JAR' not found" + candidate="$STIRLING_JAR" + else + candidate=$(find "$REPO_ROOT/app/core/build/libs" -maxdepth 1 -name 'Stirling-PDF*.jar' -o -name 'stirling-pdf*.jar' 2>/dev/null \ + | grep -vE '(-plain|-sources)\.jar$' | head -n 1 || true) + [[ -n "$candidate" ]] || fail "No JAR under app/core/build/libs - run './gradlew :stirling-pdf:bootJar' first" + fi + # Resolve to an absolute path: test_fixture pushd's into a temp workdir + # before launching java, so a relative path here would dangle. + realpath "$candidate" +} + +free_port() { + python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1]); s.close()' +} + +wait_for_url() { + local url="$1" deadline=$(( $(date +%s) + STARTUP_TIMEOUT_SEC )) + while (( $(date +%s) < deadline )); do + local code + code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 "$url" || true) + # Any HTTP response (200, 302, 401, 403, 404) means Spring is serving. + if [[ "$code" =~ ^[1-5][0-9][0-9]$ ]]; then return 0; fi + sleep 2 + done + return 1 +} + +test_fixture() { + local fixture_path="$1" + local label + label=$(basename "$fixture_path" .mv.db) + log "=== $label ===" + + local jar; jar=$(find_jar) + local workdir; workdir=$(mktemp -d) + local configsdir="$workdir/configs" + mkdir -p "$configsdir" + cp "$fixture_path" "$configsdir/stirling-pdf-DB-2.3.232.mv.db" + + local port; port=$(free_port) + local base_url="http://127.0.0.1:$port" + local log_file="$workdir/app.log" + + log " jar=$jar" + log " workdir=$workdir" + log " port=$port" + + # Force DB_CLOSE_ON_EXIT=TRUE so the H2 file flushes via shutdown hook even + # on a hard kill. show-sql off (output is enormous) but schema-tool INFO + # is on so any migration failure is visible in CI logs. + # + # pushd into the workdir so the JVM's `.` resolves to the isolated + # workdir - InstallationPathConfig reads `./configs/settings.yml` relative + # to cwd, and we want to make sure we hit the fixture's configs/ and not + # whatever happens to live at the runner's working directory. + pushd "$workdir" >/dev/null + java -Xmx1g -jar "$jar" \ + "--server.port=$port" \ + "--spring.datasource.url=jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE;MODE=PostgreSQL" \ + "--spring.jpa.show-sql=false" \ + "--logging.level.root=WARN" \ + "--logging.level.stirling=INFO" \ + "--logging.level.org.hibernate.tool.schema=INFO" \ + > "$log_file" 2>&1 & + local pid=$! + popd >/dev/null + + local rc=0 + if ! wait_for_url "$base_url/login"; then + log " app did not start within $STARTUP_TIMEOUT_SEC sec; last 60 lines of stdout:" + tail -n 60 "$log_file" >&2 + rc=1 + else + log " app started" + + if grep -E -i 'SchemaManagementException|GenerationTarget encountered exception|liquibase|Flyway.*FAILED' "$log_file" >&2; then + log " Hibernate reported schema migration errors (see log above)" + rc=1 + fi + + if (( rc == 0 )); then + local login_body; login_body=$(printf '{"username":"%s","password":"%s"}' "$ADMIN_USERNAME" "$ADMIN_PASSWORD") + local resp_file="$workdir/login.resp" + local code + code=$(curl -s -o "$resp_file" -w '%{http_code}' \ + --max-time 30 \ + -H 'Content-Type: application/json' \ + -d "$login_body" \ + "$base_url/api/v1/auth/login" || echo "000") + log " POST /api/v1/auth/login -> HTTP $code" + if [[ "$code" != "200" ]]; then + log " response body:" + head -c 500 "$resp_file" >&2 || true; echo >&2 + log " last 40 lines of app log:" + tail -n 40 "$log_file" >&2 + rc=1 + else + # Cheap sanity check on a second endpoint -- proves the app is + # not just stubbing the login response. + local status_code + status_code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$base_url/api/v1/info/status" || echo "000") + log " GET /api/v1/info/status -> HTTP $status_code" + if [[ "$status_code" != "200" ]]; then + log " sanity check failed: /api/v1/info/status returned non-200" + rc=1 + else + log " PASS: $label migrated and admin login succeeded" + fi + fi + fi + fi + + # H2 flushes via DB_CLOSE_ON_EXIT=TRUE so a forced kill is safe. + kill -TERM "$pid" 2>/dev/null || true + local i + for i in $(seq 1 15); do + kill -0 "$pid" 2>/dev/null || break + sleep 1 + done + kill -KILL "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + + if (( rc == 0 )); then + rm -rf "$workdir" 2>/dev/null || true + else + # Preserve workdir so CI can upload the app log as an artifact for + # post-mortem. Move it to a stable name so the upload path is fixed. + local preserved + preserved="${MIGRATION_TEST_LOG_DIR:-/tmp}/stirling-migration-failed-$label" + rm -rf "$preserved" + mv "$workdir" "$preserved" 2>/dev/null || true + log " preserved failing workdir at $preserved" + fi + + return $rc +} + +main() { + [[ -d "$FIXTURE_DIR" ]] || fail "Fixture dir not found: $FIXTURE_DIR" + local fixtures + mapfile -t fixtures < <(find "$FIXTURE_DIR" -maxdepth 1 -name '*.mv.db' | sort) + [[ ${#fixtures[@]} -gt 0 ]] || fail "No fixtures under $FIXTURE_DIR" + + local failed=0 + local failed_names=() + for f in "${fixtures[@]}"; do + # set -e would abort the whole run on the first failure; we want to + # report every fixture's status, so guard with ||. + if ! test_fixture "$f"; then + failed=$(( failed + 1 )) + failed_names+=("$(basename "$f" .mv.db)") + fi + done + + if (( failed > 0 )); then + log "${failed}/${#fixtures[@]} fixture(s) failed: ${failed_names[*]}" + exit 1 + fi + log "All ${#fixtures[@]} fixtures migrated cleanly." +} + +main "$@"