* feat: add release workflow to build and push container images to GHCR
Triggers on GitHub Release publish. Builds three container images
(consentos-api, consentos-scanner, consentos-admin-ui) and pushes
them to ghcr.io/consentos/ tagged with the semver release version
(e.g. v1.0.0, 1.0), plus ``latest``.
Release flow:
1. Merge PRs to master.
2. Tag: ``git tag v1.0.0 && git push origin v1.0.0``
3. Create a GitHub Release from the tag.
4. Workflow fires, images land on GHCR.
5. Deploy by pointing Helm values or docker-compose at the tag.
Uses ``docker/metadata-action`` for tag derivation and
``docker/build-push-action`` for the builds. Auth uses the
default ``GITHUB_TOKEN`` with ``packages: write`` — no extra
secrets needed.
The admin-ui image uses the repo root as the build context (same
as ``docker-compose.prod.yml``) so the Dockerfile can pull in
``apps/banner/`` alongside ``apps/admin-ui/`` and bundle the
banner output at the nginx root.
* chore: auto-graduate changelog on release + CI path filters
CI workflow (``ci.yml``):
- Uses ``dorny/paths-filter`` to detect which apps changed. Each
job group (api, scanner, banner, admin-ui) now has an
``if: needs.changes.outputs.<app> == 'true'`` guard so it only
runs when files under its ``apps/<app>/`` directory were
modified. A docs-only or infra-only PR no longer triggers the
full lint + test + build matrix.
260 lines
7.6 KiB
YAML
260 lines
7.6 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [master]
|
|
pull_request:
|
|
branches: [master]
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# ── Detect which apps changed ──────────────────────────────────────
|
|
changes:
|
|
name: Detect changes
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
api: ${{ steps.filter.outputs.api }}
|
|
scanner: ${{ steps.filter.outputs.scanner }}
|
|
banner: ${{ steps.filter.outputs.banner }}
|
|
admin-ui: ${{ steps.filter.outputs.admin-ui }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dorny/paths-filter@v3
|
|
id: filter
|
|
with:
|
|
filters: |
|
|
api:
|
|
- 'apps/api/**'
|
|
scanner:
|
|
- 'apps/scanner/**'
|
|
banner:
|
|
- 'apps/banner/**'
|
|
admin-ui:
|
|
- 'apps/admin-ui/**'
|
|
|
|
# ── API ────────────────────────────────────────────────────────────
|
|
api-lint:
|
|
name: API Lint
|
|
needs: changes
|
|
if: needs.changes.outputs.api == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/api
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
cache: pip
|
|
- run: pip install ruff
|
|
- run: ruff check src/ tests/
|
|
- run: ruff format --check src/ tests/
|
|
|
|
api-test:
|
|
name: API Tests
|
|
needs: changes
|
|
if: needs.changes.outputs.api == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/api
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
env:
|
|
POSTGRES_USER: consentos_test
|
|
POSTGRES_PASSWORD: consentos_test
|
|
POSTGRES_DB: consentos_test
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
redis:
|
|
image: redis:7-alpine
|
|
ports:
|
|
- 6379:6379
|
|
options: >-
|
|
--health-cmd "redis-cli ping"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
env:
|
|
DATABASE_URL: postgresql+asyncpg://consentos_test:consentos_test@localhost:5432/consentos_test
|
|
REDIS_URL: redis://localhost:6379/0
|
|
JWT_SECRET_KEY: ci-test-secret-key-not-for-production
|
|
ENVIRONMENT: test
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
cache: pip
|
|
- run: pip install -e ".[dev]"
|
|
- name: Run migrations
|
|
run: alembic upgrade head
|
|
env:
|
|
DATABASE_URL: postgresql://consentos_test:consentos_test@localhost:5432/consentos_test
|
|
- run: pytest --cov=src --cov-report=term-missing -v
|
|
|
|
# ── Scanner ────────────────────────────────────────────────────────
|
|
scanner-lint:
|
|
name: Scanner Lint
|
|
needs: changes
|
|
if: needs.changes.outputs.scanner == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/scanner
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
cache: pip
|
|
- run: pip install ruff
|
|
- run: ruff check src/ tests/
|
|
- run: ruff format --check src/ tests/
|
|
|
|
scanner-test:
|
|
name: Scanner Tests
|
|
needs: changes
|
|
if: needs.changes.outputs.scanner == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/scanner
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
cache: pip
|
|
- run: pip install -e ".[dev]"
|
|
- run: pytest --cov=src --cov-report=term-missing -v
|
|
|
|
# ── Banner ─────────────────────────────────────────────────────────
|
|
banner-lint:
|
|
name: Banner Lint & Typecheck
|
|
needs: changes
|
|
if: needs.changes.outputs.banner == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/banner
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
cache: npm
|
|
cache-dependency-path: apps/banner/package-lock.json
|
|
- run: npm ci
|
|
- run: npx tsc --noEmit
|
|
|
|
banner-test:
|
|
name: Banner Tests
|
|
needs: changes
|
|
if: needs.changes.outputs.banner == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/banner
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
cache: npm
|
|
cache-dependency-path: apps/banner/package-lock.json
|
|
- run: npm ci
|
|
- run: npx vitest run --reporter=verbose
|
|
|
|
banner-build:
|
|
name: Banner Build
|
|
needs: [banner-test, banner-lint]
|
|
if: needs.changes.outputs.banner == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/banner
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
cache: npm
|
|
cache-dependency-path: apps/banner/package-lock.json
|
|
- run: npm ci
|
|
- run: npm run build
|
|
- name: Check bundle sizes
|
|
run: |
|
|
echo "=== Bundle sizes ==="
|
|
ls -lh dist/consent-loader.js
|
|
ls -lh dist/consent-bundle.js
|
|
LOADER_SIZE=$(stat -c%s dist/consent-loader.js)
|
|
if [ "$LOADER_SIZE" -gt 20480 ]; then
|
|
echo "::warning::consent-loader.js is ${LOADER_SIZE} bytes (>20KB) — consider optimising"
|
|
fi
|
|
|
|
# ── Admin UI ───────────────────────────────────────────────────────
|
|
admin-ui-lint:
|
|
name: Admin UI Typecheck
|
|
needs: changes
|
|
if: needs.changes.outputs.admin-ui == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/admin-ui
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
cache: npm
|
|
cache-dependency-path: apps/admin-ui/package-lock.json
|
|
- run: npm ci
|
|
- run: npx tsc -b
|
|
|
|
admin-ui-test:
|
|
name: Admin UI Tests
|
|
needs: changes
|
|
if: needs.changes.outputs.admin-ui == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/admin-ui
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
cache: npm
|
|
cache-dependency-path: apps/admin-ui/package-lock.json
|
|
- run: npm ci
|
|
- run: npx vitest run --reporter=verbose
|
|
|
|
admin-ui-build:
|
|
name: Admin UI Build
|
|
needs: [admin-ui-test, admin-ui-lint]
|
|
if: needs.changes.outputs.admin-ui == 'true'
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: apps/admin-ui
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
cache: npm
|
|
cache-dependency-path: apps/admin-ui/package-lock.json
|
|
- run: npm ci
|
|
- run: npx vite build
|