Merge SSL env vars into existing env block instead of creating a duplicate (YAML doesn't allow duplicate top-level keys). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
387 lines
16 KiB
YAML
387 lines
16 KiB
YAML
name: Veza CI/CD
|
|
|
|
on:
|
|
push:
|
|
branches: ["main", "remediation/*", "feature/mvp-complete"]
|
|
pull_request:
|
|
branches: ["main", "feature/mvp-complete"]
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
GIT_SSL_NO_VERIFY: "true"
|
|
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
|
|
|
jobs:
|
|
# ===========================================================================
|
|
# TMT Vital — Backend (Go)
|
|
# ===========================================================================
|
|
vital-backend:
|
|
name: TMT Vital — Backend (Go)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Check VERSION matches git tag
|
|
run: |
|
|
current_tag=$(git describe --tags --exact-match 2>/dev/null || true)
|
|
if [ -n "$current_tag" ]; then
|
|
version_file=$(cat VERSION)
|
|
tag_version=${current_tag#v}
|
|
if [ "$version_file" != "$tag_version" ]; then
|
|
echo "VERSION mismatch: VERSION=$version_file, current tag=$current_tag"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
|
with:
|
|
go-version: "1.24"
|
|
cache: true
|
|
|
|
- name: Install Go tools
|
|
run: |
|
|
go install golang.org/x/vuln/cmd/govulncheck@latest
|
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
|
|
|
- name: Install TMT
|
|
run: pip install tmt
|
|
|
|
- name: Run TMT Vital Backend
|
|
run: tmt --root tmt run plan --name /vital-backend
|
|
|
|
# ===========================================================================
|
|
# TMT Vital — Rust Services (Stream)
|
|
# ===========================================================================
|
|
vital-services:
|
|
name: TMT Vital — Rust Services
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- name: Set up Rust
|
|
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
|
with:
|
|
components: rustfmt, clippy
|
|
|
|
- name: Cache Cargo registry
|
|
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
target
|
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
|
|
- name: Install cargo-audit
|
|
run: cargo install cargo-audit
|
|
|
|
- name: Install TMT
|
|
run: pip install tmt
|
|
|
|
- name: Run TMT Vital Services
|
|
run: tmt --root tmt run plan --name /vital-services
|
|
|
|
# ===========================================================================
|
|
# TMT Vital — Frontend (Web)
|
|
# ===========================================================================
|
|
vital-frontend:
|
|
name: TMT Vital — Frontend (Web)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- name: Use Node.js
|
|
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
|
with:
|
|
node-version: "20"
|
|
cache: "npm"
|
|
cache-dependency-path: package-lock.json
|
|
|
|
- name: Install Dependencies
|
|
run: npm ci
|
|
|
|
- name: Cache Generated Types
|
|
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
|
with:
|
|
path: apps/web/src/types/generated
|
|
key: ${{ runner.os }}-generated-types-${{ hashFiles('veza-backend-api/openapi.yaml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-generated-types-
|
|
|
|
- name: Install TMT
|
|
run: pip install tmt
|
|
|
|
- name: Run TMT Vital Frontend
|
|
run: tmt --root tmt run plan --name /vital-frontend
|
|
|
|
# ===========================================================================
|
|
# Storybook Audit (kept outside TMT — tier 3 candidate)
|
|
# ===========================================================================
|
|
storybook:
|
|
name: Storybook Audit
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
|
with:
|
|
node-version: "20"
|
|
cache: "npm"
|
|
cache-dependency-path: package-lock.json
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Build Storybook
|
|
run: npm run build-storybook
|
|
working-directory: apps/web
|
|
|
|
- name: Serve Storybook and run audit
|
|
run: |
|
|
npx serve -s storybook-static -l 6007 &
|
|
for i in $(seq 1 30); do
|
|
if curl -sf http://localhost:6007 >/dev/null; then
|
|
echo "Storybook ready"
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
curl -sf http://localhost:6007 >/dev/null || (echo "Storybook failed to start"; exit 1)
|
|
npm run test:storybook
|
|
working-directory: apps/web
|
|
|
|
# ===========================================================================
|
|
# E2E Critical (Playwright) — fast smoke, blocks PR
|
|
# ===========================================================================
|
|
e2e-critical:
|
|
name: E2E Critical (@critical)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
|
with:
|
|
node-version: "20"
|
|
cache: "npm"
|
|
cache-dependency-path: package-lock.json
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
|
with:
|
|
go-version: "1.24"
|
|
cache-dependency-path: veza-backend-api/go.sum
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Add veza.fr to hosts (for Vite proxy)
|
|
run: echo "127.0.0.1 veza.fr" | sudo tee -a /etc/hosts
|
|
|
|
- name: Start backend services (Postgres, Redis, RabbitMQ)
|
|
run: |
|
|
docker-compose up -d postgres redis rabbitmq
|
|
echo "Waiting for Postgres..."
|
|
for i in $(seq 1 30); do
|
|
if docker exec veza_postgres pg_isready -U veza 2>/dev/null; then
|
|
echo "Postgres ready"
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
docker-compose ps
|
|
|
|
- name: Run database migrations
|
|
env:
|
|
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
|
run: |
|
|
cd veza-backend-api
|
|
go run cmd/migrate_tool/main.go
|
|
|
|
- name: Create E2E test user
|
|
env:
|
|
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@localhost:15432/veza?sslmode=disable
|
|
TEST_EMAIL: e2e@test.com
|
|
TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}
|
|
TEST_USERNAME: e2e
|
|
run: |
|
|
cd veza-backend-api
|
|
go run cmd/tools/create_test_user/main.go
|
|
|
|
- name: Start backend API
|
|
env:
|
|
APP_ENV: development
|
|
APP_PORT: "18080"
|
|
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@localhost:15432/veza?sslmode=disable
|
|
REDIS_URL: redis://localhost:16379
|
|
JWT_SECRET: ${{ secrets.E2E_JWT_SECRET }}
|
|
COOKIE_SECURE: "false"
|
|
CORS_ALLOWED_ORIGINS: http://veza.fr:5173,http://veza.fr:5174,http://localhost:5173,http://localhost:5174
|
|
RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL }}
|
|
DISABLE_RATE_LIMIT_FOR_TESTS: "true"
|
|
ACCOUNT_LOCKOUT_EXEMPT_EMAILS: "e2e@test.com"
|
|
run: |
|
|
cd veza-backend-api
|
|
go build -o veza-api ./cmd/api/main.go
|
|
./veza-api &
|
|
sleep 10
|
|
curl -sf http://localhost:18080/api/v1/health > /tmp/health.json || (echo "Backend health check failed"; exit 1)
|
|
jq -e '.status == "ok"' /tmp/health.json || (echo "Health response invalid"; exit 1)
|
|
echo "Health check OK (status, DB/Redis connectivity verified)"
|
|
|
|
- name: Install Playwright Browsers
|
|
run: npx playwright install --with-deps chromium
|
|
|
|
- name: Run @critical E2E tests
|
|
run: npx playwright test --config=tests/e2e/playwright.config.ts --grep @critical --project=chromium
|
|
env:
|
|
PORT: "5174"
|
|
VITE_API_URL: "/api/v1"
|
|
VITE_DOMAIN: veza.fr
|
|
VITE_BACKEND_PORT: "18080"
|
|
PLAYWRIGHT_BASE_URL: "http://localhost:5174"
|
|
TEST_EMAIL: e2e@test.com
|
|
TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}
|
|
|
|
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
|
if: failure()
|
|
with:
|
|
name: playwright-critical-report
|
|
path: tests/e2e/playwright-report/
|
|
retention-days: 7
|
|
|
|
# ===========================================================================
|
|
# E2E Full (Playwright) — sharded across 4 runners, all browsers
|
|
# ===========================================================================
|
|
e2e-full:
|
|
name: E2E Full (shard ${{ matrix.shard }})
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shard: [1/4, 2/4, 3/4, 4/4]
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
|
with:
|
|
node-version: "20"
|
|
cache: "npm"
|
|
cache-dependency-path: package-lock.json
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
|
with:
|
|
go-version: "1.24"
|
|
cache-dependency-path: veza-backend-api/go.sum
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Add veza.fr to hosts (for Vite proxy)
|
|
run: echo "127.0.0.1 veza.fr" | sudo tee -a /etc/hosts
|
|
|
|
- name: Start backend services (Postgres, Redis, RabbitMQ)
|
|
run: |
|
|
docker-compose up -d postgres redis rabbitmq
|
|
echo "Waiting for Postgres..."
|
|
for i in $(seq 1 30); do
|
|
if docker exec veza_postgres pg_isready -U veza 2>/dev/null; then
|
|
echo "Postgres ready"
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
docker-compose ps
|
|
|
|
- name: Run database migrations
|
|
env:
|
|
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
|
run: |
|
|
cd veza-backend-api
|
|
go run cmd/migrate_tool/main.go
|
|
|
|
- name: Create E2E test user
|
|
env:
|
|
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@localhost:15432/veza?sslmode=disable
|
|
TEST_EMAIL: e2e@test.com
|
|
TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}
|
|
TEST_USERNAME: e2e
|
|
run: |
|
|
cd veza-backend-api
|
|
go run cmd/tools/create_test_user/main.go
|
|
|
|
- name: Start backend API
|
|
env:
|
|
APP_ENV: development
|
|
APP_PORT: "18080"
|
|
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@localhost:15432/veza?sslmode=disable
|
|
REDIS_URL: redis://localhost:16379
|
|
JWT_SECRET: ${{ secrets.E2E_JWT_SECRET }}
|
|
COOKIE_SECURE: "false"
|
|
CORS_ALLOWED_ORIGINS: http://veza.fr:5173,http://veza.fr:5174,http://localhost:5173,http://localhost:5174
|
|
RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL }}
|
|
DISABLE_RATE_LIMIT_FOR_TESTS: "true"
|
|
ACCOUNT_LOCKOUT_EXEMPT_EMAILS: "e2e@test.com"
|
|
run: |
|
|
cd veza-backend-api
|
|
go build -o veza-api ./cmd/api/main.go
|
|
./veza-api &
|
|
sleep 10
|
|
curl -sf http://localhost:18080/api/v1/health > /tmp/health.json || (echo "Backend health check failed"; exit 1)
|
|
jq -e '.status == "ok"' /tmp/health.json || (echo "Health response invalid"; exit 1)
|
|
echo "Health check OK (status, DB/Redis connectivity verified)"
|
|
|
|
- name: Install Playwright Browsers
|
|
run: npx playwright install --with-deps
|
|
|
|
- name: Run E2E tests (sharded)
|
|
run: npx playwright test --config=tests/e2e/playwright.config.ts --shard=${{ matrix.shard }}
|
|
env:
|
|
PORT: "5174"
|
|
VITE_API_URL: "/api/v1"
|
|
VITE_DOMAIN: veza.fr
|
|
VITE_BACKEND_PORT: "18080"
|
|
PLAYWRIGHT_BASE_URL: "http://localhost:5174"
|
|
TEST_EMAIL: e2e@test.com
|
|
TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}
|
|
|
|
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
|
if: always()
|
|
with:
|
|
name: playwright-report-shard-${{ strategy.job-index }}
|
|
path: |
|
|
tests/e2e/playwright-report/
|
|
tests/e2e/test-results/
|
|
retention-days: 7
|
|
|
|
# ===========================================================================
|
|
# Notify on failure
|
|
# ===========================================================================
|
|
notify-failure:
|
|
name: Notify on failure
|
|
needs:
|
|
[
|
|
vital-backend,
|
|
vital-services,
|
|
vital-frontend,
|
|
storybook,
|
|
e2e-critical,
|
|
e2e-full,
|
|
]
|
|
if: failure()
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Slack notification
|
|
if: secrets.SLACK_WEBHOOK_URL != ''
|
|
run: |
|
|
curl -X POST -H 'Content-type: application/json' \
|
|
--data "{\"text\":\"CI failed on ${{ github.repository }}: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
|
|
"${{ secrets.SLACK_WEBHOOK_URL }}"
|