UN-2202 [FEAT] Add co-owner management for shared resources #7672
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Run tests (unit + integration + e2e) | |
| # Multi-job, one report. unit/integration run as parallel matrix legs on | |
| # testcontainers; e2e builds images and boots the full platform via compose on | |
| # its own runner (images don't travel across jobs). The report job needs all | |
| # three, merges every tier's junit into ONE critical-path evaluation, and posts | |
| # a single sticky PR comment — so e2e coverage (e.g. auth-login) shows up next | |
| # to unit/integration. Tiers never run in one rig invocation: the rig picks one | |
| # infra mode per call, so mixing compose e2e with testcontainers integration | |
| # would point the backend at the compose DB hostname, unreachable from pytest. | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| jobs: | |
| # Path filtering at job level, not trigger paths-ignore: an untriggered | |
| # workflow reports no check run, so a required check waits forever; a job | |
| # skipped via `if:` reports conclusion `skipped`, which passes. Gates only | |
| # unit/integration — e2e runs regardless (docker/** changes affect it). | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| relevant: ${{ steps.filter.outputs.relevant }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Check for changes outside ignored paths | |
| uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 | |
| id: filter | |
| with: | |
| # `every`: a file counts as relevant only if it matches ALL the | |
| # negated globs, i.e. lies outside every ignored dir. | |
| predicate-quantifier: every | |
| filters: | | |
| relevant: | |
| - "!docker/**" | |
| - "!frontend/**" | |
| - "!docs/**" | |
| # Tiers share no state (separate runners, disjoint groups), so they run as | |
| # parallel matrix legs; cross-tier reconciliation happens in `report`. | |
| test: | |
| needs: changes | |
| if: needs.changes.outputs.relevant == 'true' && github.event.pull_request.draft == false | |
| runs-on: ubuntu-latest | |
| strategy: | |
| # One tier failing must not cancel the other; report needs both results. | |
| fail-fast: false | |
| matrix: | |
| tier: [unit, integration] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| fetch-depth: 0 # rig --changed-only needs git history | |
| persist-credentials: false | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 | |
| with: | |
| version: "0.6.14" | |
| python-version: 3.12.9 | |
| - name: Cache tox environments | |
| uses: actions/cache@v5 | |
| with: | |
| path: .tox/ | |
| # Tier in the key: legs build different tox envs, and parallel saves | |
| # to one key race (first save wins). | |
| key: ${{ runner.os }}-tox-uv-${{ matrix.tier }}-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }} | |
| restore-keys: | | |
| ${{ runner.os }}-tox-uv-${{ matrix.tier }}- | |
| ${{ runner.os }}-tox-uv- | |
| - name: Install tox with UV | |
| run: uv tool install tox --with tox-uv | |
| - name: Validate test manifests | |
| # Cheap pre-flight: catches manifest schema errors before tier runs. | |
| run: tox -e rig -- validate | |
| - name: Run ${{ matrix.tier }} tier | |
| # Gap check on PRs too: main is gap-free and PRs test the merge ref, so | |
| # an in-scope gap here is PR-introduced. No baseline restore or | |
| # --update-baseline — the report job is the sole regression authority | |
| # (a per-tier eval would flag other tiers' paths as missing). | |
| run: tox -e ${{ matrix.tier }} -- --fail-on-critical-gap | |
| - name: Suffix coverage data for cross-tier merge | |
| # Every tier emits a bare reports/.coverage that would clobber on | |
| # download; suffixed names match the `.coverage.*` glob combine unions. | |
| if: always() | |
| run: | | |
| if [ -f reports/.coverage ]; then | |
| mv reports/.coverage "reports/.coverage.tier-${{ matrix.tier }}" | |
| fi | |
| - name: Upload tier reports artifact | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: reports-${{ matrix.tier }} | |
| path: reports/ | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| # Own runner: builds images then runs e2e against the full compose platform. | |
| # Not gated by `changes` — docker/** edits don't count as "relevant" yet must | |
| # be e2e-tested. | |
| e2e: | |
| if: github.event.pull_request.draft == false | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| # One tag end to end: docker-compose.build.yaml tags images with VERSION, | |
| # the base compose boots ${VERSION}, and the test overlay pins its | |
| # services via UNSTRACT_TEST_VERSION. Keep them identical. | |
| VERSION: ${{ github.sha }} | |
| UNSTRACT_TEST_VERSION: ${{ github.sha }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Login to Docker Hub | |
| # Authenticated pulls avoid the anonymous rate limit shared by all | |
| # runners. Forks have no access to secrets. | |
| if: github.event.pull_request == null || github.event.pull_request.head.repo.fork == false | |
| uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 | |
| with: | |
| version: "0.6.14" | |
| python-version: 3.12.9 | |
| - name: Cache tox environments | |
| uses: actions/cache@v5 | |
| with: | |
| path: .tox/ | |
| key: ${{ runner.os }}-tox-uv-e2e-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }} | |
| restore-keys: | | |
| ${{ runner.os }}-tox-uv-e2e- | |
| ${{ runner.os }}-tox-uv- | |
| - name: Install tox with UV | |
| run: uv tool install tox --with tox-uv | |
| - name: Validate test manifests | |
| # Cheap pre-flight before the multi-minute image build. | |
| run: tox -e rig -- validate | |
| - name: Set up env files | |
| run: ./run-platform.sh -e | |
| - name: Build service images | |
| run: docker compose -f docker/docker-compose.build.yaml build | |
| - name: Run e2e tier (rig boots the platform via compose) | |
| env: | |
| UNSTRACT_E2E_RUNTIME: compose | |
| # Gap check only; report owns baseline/regression across all tiers. | |
| run: tox -e e2e -- --fail-on-critical-gap | |
| - name: Suffix coverage data for cross-tier merge | |
| if: always() | |
| run: | | |
| if [ -f reports/.coverage ]; then | |
| mv reports/.coverage "reports/.coverage.tier-e2e" | |
| fi | |
| - name: Capture docker compose logs on failure | |
| if: failure() | |
| run: | | |
| mkdir -p reports | |
| docker compose -p unstract-test \ | |
| -f docker/docker-compose.yaml \ | |
| -f tests/compose/docker-compose.test.yaml \ | |
| logs --no-color > reports/docker-compose-logs.txt || true | |
| - name: Upload e2e reports artifact | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: reports-e2e | |
| path: reports/ | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| report: | |
| needs: [changes, test, e2e] | |
| # `always()` so a red tier still posts a report. e2e always runs on | |
| # non-draft, so gate only on draft; the fail step below distinguishes a | |
| # legitimate skip (irrelevant paths) from a real tier failure. | |
| if: always() && github.event.pull_request.draft == false | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 | |
| with: | |
| version: "0.6.14" | |
| python-version: 3.12.9 | |
| - name: Cache tox environments | |
| uses: actions/cache@v5 | |
| with: | |
| path: .tox/ | |
| key: ${{ runner.os }}-tox-uv-rig-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }} | |
| restore-keys: | | |
| ${{ runner.os }}-tox-uv-rig- | |
| ${{ runner.os }}-tox-uv- | |
| - name: Install tox with UV | |
| run: uv tool install tox --with tox-uv | |
| - name: Restore main-branch test baseline (for regression detection) | |
| # One unified baseline across all tiers; this job is its sole | |
| # reader+writer. Restore-only here, save below on green main. | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: reports/previous-summary.json | |
| key: unstract-test-baseline-main-${{ github.run_id }} | |
| restore-keys: | | |
| unstract-test-baseline-main- | |
| - name: Download tier reports | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: reports-* | |
| path: reports/ | |
| merge-multiple: true | |
| - name: Combine tiers into one report | |
| # Merges every tier's junit into a single critical-path evaluation + PR | |
| # comment. --update-baseline is main-only and green-only — a PR must not | |
| # write main's baseline, nor a red build a partial one. | |
| run: | | |
| flags="" | |
| if [ "${{ github.ref }}" = "refs/heads/main" ] \ | |
| && [ "${{ needs.test.result }}" = "success" ] \ | |
| && [ "${{ needs.e2e.result }}" = "success" ]; then | |
| flags="--update-baseline" | |
| fi | |
| tox -e rig -- report combine $flags | |
| - name: Save updated baseline | |
| # actions/cache saves only on a miss; the run id keys each main build a | |
| # fresh entry, and restore-keys' prefix pulls the latest next run. | |
| if: github.ref == 'refs/heads/main' | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: reports/previous-summary.json | |
| key: unstract-test-baseline-main-${{ github.run_id }} | |
| - name: Render combined test report to PR | |
| uses: marocchino/sticky-pull-request-comment@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3.0.2 | |
| if: always() && hashFiles('reports/combined-test-report.md') != '' && github.event.pull_request.head.repo.fork == false | |
| with: | |
| header: test-results | |
| recreate: true | |
| path: reports/combined-test-report.md | |
| - name: Output combined report to job summary | |
| if: always() && hashFiles('reports/summary.md') != '' | |
| shell: bash | |
| run: cat reports/summary.md >> $GITHUB_STEP_SUMMARY | |
| - name: Upload combined reports artifact | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: test-reports | |
| path: reports/ | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| - name: Fail if any tier failed | |
| # Single branch-protection check. A skipped `test` means irrelevant | |
| # paths (path-filter skip == pass); e2e always runs, so it must be | |
| # green. Runs after the comment so a red tier still reports. | |
| if: always() | |
| run: | | |
| test_result="${{ needs.test.result }}" | |
| e2e_result="${{ needs.e2e.result }}" | |
| [ "$test_result" = "success" ] || [ "$test_result" = "skipped" ] || exit 1 | |
| [ "$e2e_result" = "success" ] || exit 1 |