Skip to content

Merge pull request #1462 from Open-Source-Legal/claude/cross-content-… #4672

Merge pull request #1462 from Open-Source-Legal/claude/cross-content-…

Merge pull request #1462 from Open-Source-Legal/claude/cross-content-… #4672

Workflow file for this run

name: Backend CI
# Enable Buildkit and let compose use it to speed up image building
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL: True
defaults:
run:
working-directory: ./
on:
pull_request:
branches: [ "master", "main", "v*" ]
paths-ignore: [ "docs/**" ]
push:
branches: [ "master", "main", "v*" ]
paths-ignore: [ "docs/**" ]
concurrency:
group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
changes:
# Path filtering only gates pull_request events; pushes to protected
# branches always run downstream regardless of which files changed.
# dorny/paths-filter@v4 additionally needs a checked-out repo on push
# events (it shells out to `git branch --show-current`), so scoping
# this job to pull_request avoids the "fatal: not a git repository"
# red X that would otherwise appear on every push to main.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
# Fail open: if this job errors (e.g., transient GitHub API failure),
# downstream jobs should still run rather than being silently skipped.
continue-on-error: true
permissions:
contents: read
pull-requests: read
outputs:
backend: ${{ steps.filter.outputs.backend }}
steps:
# No checkout needed: dorny/paths-filter@v4 reads diffs from the
# pull_request event payload.
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
backend:
- 'opencontractserver/**'
- 'config/**'
- '*.py'
- 'requirements/**'
- 'test.yml'
- 'compose/**'
- 'Dockerfile*'
- '.pre-commit-config.yaml'
- 'setup.cfg'
- 'mypy.ini'
- 'pyproject.toml'
- '.github/workflows/backend.yml'
linter:
needs: changes
# Run on every push, and on PRs unless the filter explicitly reports
# no backend changes ('false'). `always()` is required because the
# `changes` job is skipped on push events (see its `if:` above), and
# a skipped `needs` target would otherwise cascade-skip this job.
# The `!= 'false'` form also preserves the fail-open behaviour on
# PRs where `changes` errors transiently (outputs.backend is '').
if: always() && (github.event_name == 'push' || needs.changes.outputs.backend != 'false')
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout Code Repository
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: pip
cache-dependency-path: |
requirements/base.txt
requirements/local.txt
- name: Install dependencies
run: pip install -r requirements/local.txt
- name: Run pre-commit
run: pre-commit run --all-files
- name: Run mypy
# Runs from requirements/local.txt (not via pre-commit) so CI is
# authoritative even when contributors skip the hook. Keep the
# mypy pin in requirements/local.txt in sync with the `rev:` of
# the mirrors-mypy hook in .pre-commit-config.yaml — a drift
# means the hook and CI can disagree silently. See
# docs/typing/README.md.
run: python -m mypy --config-file mypy.ini opencontractserver config
pytest:
needs: [changes, linter]
# Same fail-open semantics as the linter gate above.
if: always() && needs.linter.result == 'success' && (github.event_name == 'push' || needs.changes.outputs.backend != 'false')
runs-on: yuge
timeout-minutes: 180
permissions:
contents: read
actions: read
steps:
- name: Checkout Code Repository
uses: actions/checkout@v6
- name: Store Codecov Env Flags
continue-on-error: true
run: |
ci_env=`bash <(curl -s https://codecov.io/env)`
echo "$ci_env"
- name: Build the Stack
run: docker compose -f test.yml build
- name: Run DB Migrations
run: docker compose -f test.yml run --rm django python manage.py migrate
- name: Collect Static Files
run: docker compose -f test.yml run --rm django python manage.py collectstatic
- name: Verify Docker Containers
run: |
docker compose -f test.yml ps
- name: Capture Docker Compose Logs
if: failure()
run: |
docker compose -f test.yml logs --no-color > docker-compose-logs.txt
- name: Upload Docker Compose Logs
if: failure()
uses: actions/upload-artifact@v7
with:
name: docker-compose-logs
path: docker-compose-logs.txt
- name: Check Container Health
run: |
echo "=== Docker containers status ==="
docker compose -f test.yml ps
echo "=== Container logs (last 20 lines each) ==="
for container in $(docker compose -f test.yml ps -q); do
name=$(docker inspect -f '{{.Name}}' $container | sed 's/^\///')
echo "--- Logs for $name ---"
docker logs --tail 20 $container 2>&1 || true
done
echo "=== Memory usage ==="
docker stats --no-stream
free -h
- name: Build Pytest Coverage File
timeout-minutes: 100
run: |
# Run the full test suite with coverage using parallel workers
# pytest-cov with pytest-xdist handles coverage merging automatically
docker compose -f test.yml run django pytest --cov --cov-report=xml -n auto --dist loadscope -v
- name: Verify Coverage File Exists
run: |
# Verify coverage.xml exists in the working directory
ls -la coverage.xml
- name: Upload Coverage Reports to Codecov
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
flags: backend
name: backend-coverage
fail_ci_if_error: false
- name: Tear down the Stack
run: docker compose -f test.yml down