Files
gh-christianlouis-inboxconv…/.github/workflows/ci.yml
T
dependabot[bot] 2c26c156f6 chore(ci): bump docker/build-push-action from 5 to 7
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 07:21:18 +00:00

337 lines
11 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: CI
on:
push:
branches: [main, develop]
tags:
- 'v*'
pull_request:
branches: [main, develop]
schedule:
- cron: '0 0 * * 0' # Weekly on Sunday (security scans)
workflow_dispatch:
env:
GHCR_REGISTRY: ghcr.io
PRIVATE_REGISTRY: registry.cklnet.com
K8S_STATE_REPO: christianlouis/k8s-cluster-state
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# ── Phase 1: Lint ──────────────────────────────────────────────────────
lint:
name: Lint
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install Python linting tools
run: |
python -m pip install --upgrade pip
pip install black ruff mypy
pip install -r backend/requirements.txt
- name: Check code formatting with Black
run: black --check backend/
- name: Lint with Ruff
run: ruff check backend/
- name: Type check with mypy
run: mypy backend/app --ignore-missing-imports
continue-on-error: true
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20.19.0'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
run: npm ci
working-directory: frontend
- name: Lint frontend with ESLint
run: npm run lint
working-directory: frontend
# ── Phase 2: Test ──────────────────────────────────────────────────────
test:
name: Test
needs: lint
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: inbox_converge_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r backend/requirements.txt
pip install pytest pytest-asyncio pytest-cov httpx
- name: Run tests with coverage
env:
DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/inbox_converge_test
REDIS_URL: redis://localhost:6379/0
SECRET_KEY: test-secret-key-for-ci-cd-at-least-32-chars
ENCRYPTION_KEY: test-encryption-key-for-ci-cd-at-least-32-chars
run: |
cd backend
pytest tests/ -v --cov=app --cov-report=xml --cov-report=term
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20.19.0'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
run: npm ci
working-directory: frontend
- name: Run frontend tests with coverage
run: npm run test:ci
working-directory: frontend
- name: Upload backend coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./backend/coverage.xml
flags: backend
name: backend-coverage
fail_ci_if_error: false
- name: Upload frontend coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./frontend/coverage/lcov.info
flags: frontend
name: frontend-coverage
fail_ci_if_error: false
# ── Phase 3: Security ──────────────────────────────────────────────────
security:
name: Security
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install bandit pip-audit
pip install -r backend/requirements.txt
- name: Run Bandit security scan
run: bandit -r backend/app -ll
continue-on-error: true
- name: Check dependencies for known vulnerabilities
run: pip-audit -r backend/requirements.txt
continue-on-error: true
# ── Phase 4: Build ─────────────────────────────────────────────────────
build:
name: Build & Push Docker Image
needs: security
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
include:
- context: ./backend
image_name: inboxconverge/backend
- context: ./frontend
image_name: inboxconverge/frontend
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Requires PRIVATE_REGISTRY_USERNAME and PRIVATE_REGISTRY_PASSWORD secrets
- name: Log in to private registry
uses: docker/login-action@v3
with:
registry: ${{ env.PRIVATE_REGISTRY }}
username: ${{ secrets.PRIVATE_REGISTRY_USERNAME }}
password: ${{ secrets.PRIVATE_REGISTRY_PASSWORD }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.GHCR_REGISTRY }}/${{ github.repository_owner }}/${{ matrix.image_name }}
${{ env.PRIVATE_REGISTRY }}/${{ matrix.image_name }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
id: build-push
uses: docker/build-push-action@v7
with:
context: ${{ matrix.context }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
build-args: |
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
# ── Phase 5: GitOps update preprod k8s manifest ─────────────────────────
update-k8s-manifest:
name: Update Preprod K8s Manifest
runs-on: ubuntu-latest
needs: [build]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
permissions:
contents: read
steps:
- name: Compute image tags
id: tag
run: |
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
echo "backend_image=${{ env.PRIVATE_REGISTRY }}/inboxconverge/backend:sha-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "frontend_image=${{ env.PRIVATE_REGISTRY }}/inboxconverge/frontend:sha-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
- name: Check if GH_PAT is configured and has repo access
id: pat-check
env:
GH_PAT: ${{ secrets.GH_PAT }}
run: |
if [ -z "$GH_PAT" ]; then
echo "::warning::GH_PAT secret is not configured. Skipping k8s manifest update."
echo "available=false" >> "$GITHUB_OUTPUT"
else
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--oauth2-bearer "$GH_PAT" \
"https://api.github.com/repos/${{ env.K8S_STATE_REPO }}")
if [ "$HTTP_CODE" = "200" ]; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::GH_PAT does not have access to ${{ env.K8S_STATE_REPO }} (HTTP $HTTP_CODE). Skipping k8s manifest update."
echo "available=false" >> "$GITHUB_OUTPUT"
fi
fi
- name: Checkout k8s-cluster-state
if: steps.pat-check.outputs.available == 'true'
uses: actions/checkout@v4
with:
repository: ${{ env.K8S_STATE_REPO }}
token: ${{ secrets.GH_PAT }}
path: k8s-cluster-state
ref: main
- name: Update backend image tag in preprod manifest
if: steps.pat-check.outputs.available == 'true'
uses: mikefarah/yq@v4.44.6
env:
IMAGE: ${{ steps.tag.outputs.backend_image }}
with:
cmd: |
yq -i '(.. | select(tag == "!!str") | select(test("^registry\\.cklnet\\.com/inboxconverge/backend:"))) = strenv(IMAGE)' \
k8s-cluster-state/apps/gmail-puller/preprod/gmail-puller-stack.yaml
- name: Update frontend image tag in preprod manifest
if: steps.pat-check.outputs.available == 'true'
uses: mikefarah/yq@v4.44.6
env:
IMAGE: ${{ steps.tag.outputs.frontend_image }}
with:
cmd: |
yq -i '(.. | select(tag == "!!str") | select(test("^registry\\.cklnet\\.com/inboxconverge/frontend:"))) = strenv(IMAGE)' \
k8s-cluster-state/apps/gmail-puller/preprod/gmail-puller-stack.yaml
- name: Commit and push manifest update
if: steps.pat-check.outputs.available == 'true'
run: |
cd k8s-cluster-state
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add apps/gmail-puller/preprod/gmail-puller-stack.yaml
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "chore(preprod): update inboxconverge images to ${{ steps.tag.outputs.short_sha }}"
git push
fi