63d065011b
- Updated timestamps and AST hashes in manifest.json for various backend and frontend files to reflect recent changes. - Added new files related to MCC categories and optional hold reasons in the manifest. - Modified the README in the infra directory to correct the linting tool from 'mypy' to 'ty' for type checking in the backend project.
188 lines
7.7 KiB
YAML
188 lines
7.7 KiB
YAML
name: Gitea Actions CI
|
|
|
|
on:
|
|
push:
|
|
branches: [master, main]
|
|
pull_request:
|
|
|
|
# Single persistent vm-runner with host-network job containers: serialize runs
|
|
# so PostgreSQL/Redis service ports never collide across concurrent runs.
|
|
concurrency:
|
|
group: gitea-actions-ci
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
# Jobs activate only when their locked dependency manifests exist.
|
|
detect:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
backend: ${{ steps.dirs.outputs.backend }}
|
|
frontend: ${{ steps.dirs.outputs.frontend }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- id: dirs
|
|
run: |
|
|
echo "backend=$([ -f backend/uv.lock ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
echo "frontend=$([ -f frontend/pnpm-lock.yaml ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
|
|
backend:
|
|
name: Backend (lint / type / test / build)
|
|
needs: detect
|
|
if: needs.detect.outputs.backend == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: backend
|
|
services:
|
|
postgres:
|
|
image: postgres:17-alpine
|
|
env:
|
|
POSTGRES_USER: postgres
|
|
POSTGRES_PASSWORD: ${{ secrets.CI_POSTGRES_PASSWORD }}
|
|
POSTGRES_DB: finance_test
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd "pg_isready -U postgres"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 12
|
|
redis:
|
|
image: redis:7-alpine
|
|
ports:
|
|
- 6379:6379
|
|
options: >-
|
|
--health-cmd "redis-cli ping"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 12
|
|
env:
|
|
DATABASE_URL: postgresql+psycopg://postgres:${{ secrets.CI_POSTGRES_PASSWORD }}@localhost:5432/finance_test
|
|
REDIS_URL: redis://localhost:6379/0
|
|
CELERY_BROKER_URL: redis://localhost:6379/1
|
|
IMPORTS_KEK_B64: ${{ secrets.CI_IMPORTS_KEK_B64 }}
|
|
CI_RLS_DATABASE_PASSWORD: ${{ secrets.CI_RLS_DATABASE_PASSWORD }}
|
|
MINIO_ROOT_PASSWORD: ${{ secrets.CI_MINIO_ROOT_PASSWORD }}
|
|
RLS_DATABASE_URL: postgresql+psycopg://finance_app_test:${{ secrets.CI_RLS_DATABASE_PASSWORD }}@localhost:5432/finance_test
|
|
S3_ENDPOINT_URL: http://localhost:9000
|
|
S3_REGION: us-east-1
|
|
S3_ACCESS_KEY_ID: minioadmin
|
|
S3_SECRET_ACCESS_KEY: ${{ secrets.CI_MINIO_ROOT_PASSWORD }}
|
|
S3_BUCKET_STATEMENTS: finance-statements
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: astral-sh/setup-uv@v5
|
|
with:
|
|
python-version: "3.14"
|
|
enable-cache: true
|
|
cache-python: true
|
|
cache-dependency-glob: backend/uv.lock
|
|
- name: Install dependencies
|
|
run: uv sync --locked
|
|
- name: Lint (ruff check)
|
|
run: uv run ruff check .
|
|
- name: Format (ruff format)
|
|
run: uv run ruff format --check .
|
|
- name: Type check (ty)
|
|
run: uv run ty check
|
|
- name: Create restricted app role and migrate
|
|
run: |
|
|
uv run python -c "import os; from psycopg import sql; from sqlalchemy import create_engine,text; e=create_engine(os.environ['DATABASE_URL']); c=e.connect(); raw=c.connection.driver_connection; role=sql.SQL('CREATE ROLE finance_app_test LOGIN PASSWORD {} NOSUPERUSER NOBYPASSRLS').format(sql.Literal(os.environ['CI_RLS_DATABASE_PASSWORD'])).as_string(raw); c.exec_driver_sql(role); c.execute(text('GRANT CONNECT ON DATABASE finance_test TO finance_app_test')); c.execute(text('GRANT USAGE ON SCHEMA public TO finance_app_test')); c.execute(text('ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO finance_app_test')); c.commit(); c.close(); e.dispose()"
|
|
uv run alembic upgrade head
|
|
# docker.io denies minio/minio from this network (run 246); quay.io serves it.
|
|
# Unique container name keeps parallel/retried runs off each other.
|
|
- name: Start MinIO (S3-compatible test storage)
|
|
run: |
|
|
docker run -d --name minio-ci-${{ github.run_id }}-${{ github.run_attempt }} --network host \
|
|
-e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD \
|
|
quay.io/minio/minio:latest server /data
|
|
for i in $(seq 1 30); do
|
|
if curl -sf http://localhost:9000/minio/health/live > /dev/null; then
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
docker logs minio-ci-${{ github.run_id }}-${{ github.run_attempt }}
|
|
echo "MinIO did not become healthy" && exit 1
|
|
- name: Test (pytest)
|
|
run: uv run pytest -ra
|
|
- name: Build provider-neutral image
|
|
run: docker build -f ../infra/docker/Dockerfile.backend -t finance-api:ci-${{ github.run_id }}-${{ github.run_attempt }} .
|
|
- name: Cleanup Docker resources
|
|
if: always()
|
|
run: |
|
|
docker rm -f minio-ci-${{ github.run_id }}-${{ github.run_attempt }} || true
|
|
docker rmi -f finance-api:ci-${{ github.run_id }}-${{ github.run_attempt }} || true
|
|
|
|
frontend:
|
|
name: Frontend (lint / type / test / build)
|
|
needs: detect
|
|
if: needs.detect.outputs.frontend == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: frontend
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
# pnpm 10 is required: frontend/pnpm-workspace.yaml is config-only
|
|
# (allowBuilds) and pnpm 9 rejects it with "packages field missing or empty".
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
version: 10
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
cache-dependency-path: frontend/pnpm-lock.yaml
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
- name: Lint
|
|
run: pnpm lint
|
|
- name: Type check
|
|
run: pnpm typecheck
|
|
- name: Test (vitest, non-watch)
|
|
run: pnpm test
|
|
- name: Build
|
|
run: pnpm build
|
|
|
|
compose:
|
|
name: Compose & nginx validation
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
# Unique project name per run: this runner reuses the host, and the
|
|
# compose file pins "name: finance", which would collide across runs.
|
|
COMPOSE_PROJECT_NAME: finance-ci-${{ github.run_id }}-${{ github.run_attempt }}
|
|
# Backend job binds 5432/6379/9000 on the shared host network in the
|
|
# same run, so compose services use non-overlapping host ports.
|
|
POSTGRES_HOST_PORT: "15432"
|
|
REDIS_HOST_PORT: "16379"
|
|
MINIO_API_HOST_PORT: "19000"
|
|
MINIO_CONSOLE_HOST_PORT: "19001"
|
|
MAILPIT_SMTP_HOST_PORT: "11025"
|
|
MAILPIT_UI_HOST_PORT: "18025"
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Prepare env from example (placeholders only)
|
|
run: cp infra/.env.example infra/.env
|
|
- name: Validate compose file
|
|
run: docker compose -f infra/docker-compose.yml config -q
|
|
# docker.io denies minio/minio from this network (run 246); pre-pull from
|
|
# quay.io and retag so compose finds the image locally and never pulls
|
|
# the blocked reference.
|
|
- name: Pre-pull MinIO via quay.io
|
|
run: |
|
|
docker pull quay.io/minio/minio:latest
|
|
docker tag quay.io/minio/minio:latest minio/minio:latest
|
|
- name: Boot infra services and wait for healthchecks
|
|
run: docker compose -f infra/docker-compose.yml up -d --wait --wait-timeout 180 postgres redis minio mailpit
|
|
- name: Show service status
|
|
run: docker compose -f infra/docker-compose.yml ps
|
|
- name: Validate nginx config
|
|
run: |
|
|
docker run --rm --network none --add-host api:127.0.0.1 \
|
|
-v "$PWD/infra/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro" \
|
|
nginx:1.27-alpine nginx -t
|
|
- name: Teardown
|
|
if: always()
|
|
run: docker compose -f infra/docker-compose.yml down -v --remove-orphans
|