ci: fix duplicate env block in staging-validation workflow
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>
This commit is contained in:
parent
246f6b798c
commit
ce3b92a0c1
25 changed files with 2059 additions and 1096 deletions
194
.github/workflows/accessibility.yml
vendored
Normal file
194
.github/workflows/accessibility.yml
vendored
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
name: Accessibility
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
# ===========================================================================
|
||||
# Job 1: axe-playwright — WCAG AA violations via @axe-core/playwright
|
||||
# ===========================================================================
|
||||
axe-playwright:
|
||||
name: axe-playwright
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
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
|
||||
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: 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:5174,http://localhost:5174
|
||||
RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL }}
|
||||
DISABLE_RATE_LIMIT_FOR_TESTS: "true"
|
||||
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"
|
||||
|
||||
- name: Install Playwright Browsers (chromium only)
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run @a11y E2E tests
|
||||
run: npx playwright test --config=tests/e2e/playwright.config.ts --grep @a11y --project=chromium
|
||||
env:
|
||||
PORT: "5174"
|
||||
VITE_API_URL: "/api/v1"
|
||||
VITE_DOMAIN: veza.fr
|
||||
VITE_BACKEND_PORT: "18080"
|
||||
PLAYWRIGHT_BASE_URL: "http://localhost:5174"
|
||||
|
||||
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
if: failure()
|
||||
with:
|
||||
name: axe-playwright-report
|
||||
path: |
|
||||
tests/e2e/playwright-report/
|
||||
tests/e2e/test-results/
|
||||
retention-days: 7
|
||||
|
||||
# ===========================================================================
|
||||
# Job 2: lighthouse-ci — Performance & Accessibility scoring
|
||||
# ===========================================================================
|
||||
lighthouse-ci:
|
||||
name: lighthouse-ci
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
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
|
||||
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: 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:5174,http://localhost:5174
|
||||
RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL }}
|
||||
DISABLE_RATE_LIMIT_FOR_TESTS: "true"
|
||||
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"
|
||||
|
||||
- name: Start Vite dev server
|
||||
run: |
|
||||
cd apps/web
|
||||
npm run dev -- --host 127.0.0.1 --port 5174 &
|
||||
echo "Waiting for Vite dev server..."
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:5174 >/dev/null 2>&1; then
|
||||
echo "Vite dev server ready"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
curl -sf http://localhost:5174 >/dev/null || (echo "Vite dev server failed to start"; exit 1)
|
||||
env:
|
||||
VITE_API_URL: "/api/v1"
|
||||
VITE_DOMAIN: veza.fr
|
||||
VITE_BACKEND_PORT: "18080"
|
||||
|
||||
- name: Run Lighthouse CI
|
||||
uses: treosh/lighthouse-ci-action@v12
|
||||
with:
|
||||
configPath: ./.lighthouserc.js
|
||||
uploadArtifacts: true
|
||||
temporaryPublicStorage: true
|
||||
260
.github/workflows/backend-ci.yml
vendored
260
.github/workflows/backend-ci.yml
vendored
|
|
@ -1,153 +1,157 @@
|
|||
name: Backend API CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "veza-backend-api/**"
|
||||
- ".github/workflows/backend-ci.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "veza-backend-api/**"
|
||||
- ".github/workflows/backend-ci.yml"
|
||||
push:
|
||||
paths:
|
||||
- "veza-backend-api/**"
|
||||
- ".github/workflows/backend-ci.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "veza-backend-api/**"
|
||||
- ".github/workflows/backend-ci.yml"
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
test-unit:
|
||||
runs-on: ubuntu-latest
|
||||
test-unit:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-backend-api
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-backend-api
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Download deps
|
||||
run: go mod download
|
||||
- name: Download deps
|
||||
run: go mod download
|
||||
|
||||
- name: Go vet and format check
|
||||
run: |
|
||||
go vet ./...
|
||||
test -z "$(gofmt -l .)"
|
||||
working-directory: veza-backend-api
|
||||
- name: Go vet and format check
|
||||
run: |
|
||||
go vet ./...
|
||||
test -z "$(gofmt -l .)"
|
||||
working-directory: veza-backend-api
|
||||
|
||||
- name: Run govulncheck
|
||||
run: |
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
govulncheck ./...
|
||||
- name: Run govulncheck
|
||||
run: |
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
govulncheck ./...
|
||||
|
||||
- name: Run unit tests with coverage
|
||||
run: >
|
||||
go test
|
||||
./internal/handlers/...
|
||||
./internal/services/...
|
||||
./internal/core/...
|
||||
./internal/middleware/...
|
||||
-short -coverprofile=coverage.out -covermode=atomic -timeout 5m
|
||||
- name: Run unit tests with coverage
|
||||
run: >
|
||||
go test
|
||||
./internal/handlers/...
|
||||
./internal/services/...
|
||||
./internal/core/...
|
||||
./internal/middleware/...
|
||||
-short -coverprofile=coverage.out -covermode=atomic -timeout 5m
|
||||
|
||||
- name: Enforce coverage threshold (>= 70%)
|
||||
run: |
|
||||
COVERAGE=$(go tool cover -func=coverage.out | grep '^total:' | awk '{print $NF}' | tr -d '%')
|
||||
echo "Total coverage: ${COVERAGE}%"
|
||||
if [ -z "$COVERAGE" ]; then
|
||||
echo "::warning::Could not parse coverage percentage"
|
||||
exit 0
|
||||
fi
|
||||
# Compare as integers (remove decimal)
|
||||
COV_INT=$(echo "$COVERAGE" | cut -d. -f1)
|
||||
if [ "$COV_INT" -lt 70 ]; then
|
||||
echo "::error::Coverage ${COVERAGE}% is below the 70% threshold"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Coverage ${COVERAGE}% meets the >= 70% threshold"
|
||||
- name: Enforce coverage threshold (>= 70%)
|
||||
run: |
|
||||
COVERAGE=$(go tool cover -func=coverage.out | grep '^total:' | awk '{print $NF}' | tr -d '%')
|
||||
echo "Total coverage: ${COVERAGE}%"
|
||||
if [ -z "$COVERAGE" ]; then
|
||||
echo "::warning::Could not parse coverage percentage"
|
||||
exit 0
|
||||
fi
|
||||
# Compare as integers (remove decimal)
|
||||
COV_INT=$(echo "$COVERAGE" | cut -d. -f1)
|
||||
if [ "$COV_INT" -lt 75 ]; then
|
||||
echo "::error::Coverage ${COVERAGE}% is below the 75% threshold"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Coverage ${COVERAGE}% meets the >= 75% threshold"
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: go-coverage
|
||||
path: veza-backend-api/coverage.out
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: go-coverage
|
||||
path: veza-backend-api/coverage.out
|
||||
|
||||
- name: Generate coverage badge
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
run: |
|
||||
COVERAGE=$(go tool cover -func=coverage.out | grep '^total:' | awk '{print $NF}' | tr -d '%')
|
||||
COV_INT=$(echo "$COVERAGE" | cut -d. -f1)
|
||||
if [ "$COV_INT" -ge 80 ]; then
|
||||
COLOR="brightgreen"
|
||||
elif [ "$COV_INT" -ge 70 ]; then
|
||||
COLOR="green"
|
||||
elif [ "$COV_INT" -ge 50 ]; then
|
||||
COLOR="yellow"
|
||||
else
|
||||
COLOR="red"
|
||||
fi
|
||||
echo "{\"schemaVersion\":1,\"label\":\"Go coverage\",\"message\":\"${COVERAGE}%\",\"color\":\"${COLOR}\"}" > coverage-badge.json
|
||||
echo "Coverage badge: ${COVERAGE}% (${COLOR})"
|
||||
- name: Generate coverage badge
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
run: |
|
||||
COVERAGE=$(go tool cover -func=coverage.out | grep '^total:' | awk '{print $NF}' | tr -d '%')
|
||||
COV_INT=$(echo "$COVERAGE" | cut -d. -f1)
|
||||
if [ "$COV_INT" -ge 80 ]; then
|
||||
COLOR="brightgreen"
|
||||
elif [ "$COV_INT" -ge 70 ]; then
|
||||
COLOR="green"
|
||||
elif [ "$COV_INT" -ge 50 ]; then
|
||||
COLOR="yellow"
|
||||
else
|
||||
COLOR="red"
|
||||
fi
|
||||
echo "{\"schemaVersion\":1,\"label\":\"Go coverage\",\"message\":\"${COVERAGE}%\",\"color\":\"${COLOR}\"}" > coverage-badge.json
|
||||
echo "Coverage badge: ${COVERAGE}% (${COLOR})"
|
||||
|
||||
- name: Upload coverage badge
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: go-coverage-badge
|
||||
path: veza-backend-api/coverage-badge.json
|
||||
- name: Upload coverage badge
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: go-coverage-badge
|
||||
path: veza-backend-api/coverage-badge.json
|
||||
|
||||
test-integration:
|
||||
runs-on: ubuntu-latest
|
||||
test-integration:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: veza_test
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: veza_test
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veza_test?sslmode=disable
|
||||
REDIS_URL: redis://localhost:6379
|
||||
JWT_SECRET: test-jwt-secret-for-ci
|
||||
APP_ENV: test
|
||||
|
||||
env:
|
||||
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veza_test?sslmode=disable
|
||||
REDIS_URL: redis://localhost:6379
|
||||
JWT_SECRET: test-jwt-secret-for-ci
|
||||
APP_ENV: test
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-backend-api
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-backend-api
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
- name: Download deps
|
||||
run: go mod download
|
||||
|
||||
- name: Download deps
|
||||
run: go mod download
|
||||
- name: Run migrations
|
||||
run: go run cmd/migrate_tool/main.go
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run migrations
|
||||
run: go run cmd/migrate_tool/main.go
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run integration tests
|
||||
run: go test -tags=integration ./internal/... -timeout 15m
|
||||
- name: Run integration tests
|
||||
run: go test -tags=integration ./internal/... -timeout 15m
|
||||
|
|
|
|||
285
.github/workflows/cd.yml
vendored
285
.github/workflows/cd.yml
vendored
|
|
@ -1,165 +1,168 @@
|
|||
name: Veza CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Deployment environment'
|
||||
required: true
|
||||
default: 'staging'
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- production
|
||||
push:
|
||||
branches: ["main"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: "Deployment environment"
|
||||
required: true
|
||||
default: "staging"
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- production
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and push images
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
build:
|
||||
name: Build and push images
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
# Push to registry: set repo secrets DOCKER_REGISTRY, DOCKER_REGISTRY_USERNAME, DOCKER_REGISTRY_PASSWORD
|
||||
# Example: DOCKER_REGISTRY=ghcr.io/org/repo or registry.example.com/veza
|
||||
- name: Build Backend Docker Image
|
||||
run: |
|
||||
docker build -t veza-backend-api:${{ github.sha }} -f veza-backend-api/Dockerfile.production veza-backend-api/
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
|
||||
- name: Build Frontend Docker Image
|
||||
run: |
|
||||
docker build -t veza-frontend:${{ github.sha }} -f apps/web/Dockerfile.production apps/web/
|
||||
# Push to registry: set repo secrets DOCKER_REGISTRY, DOCKER_REGISTRY_USERNAME, DOCKER_REGISTRY_PASSWORD
|
||||
# Example: DOCKER_REGISTRY=ghcr.io/org/repo or registry.example.com/veza
|
||||
- name: Build Backend Docker Image
|
||||
run: |
|
||||
docker build -t veza-backend-api:${{ github.sha }} -f veza-backend-api/Dockerfile.production veza-backend-api/
|
||||
|
||||
- name: Build Stream Server Docker Image
|
||||
run: |
|
||||
docker build -t veza-stream-server:${{ github.sha }} -f veza-stream-server/Dockerfile.production veza-stream-server/
|
||||
- name: Build Frontend Docker Image
|
||||
run: |
|
||||
docker build -t veza-frontend:${{ github.sha }} -f apps/web/Dockerfile.production apps/web/
|
||||
|
||||
- name: Trivy vulnerability scan
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-backend-api:${{ github.sha }}'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
- name: Build Stream Server Docker Image
|
||||
run: |
|
||||
docker build -t veza-stream-server:${{ github.sha }} -f veza-stream-server/Dockerfile.production veza-stream-server/
|
||||
|
||||
- name: Trivy scan frontend
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-frontend:${{ github.sha }}'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
- name: Trivy vulnerability scan
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: "veza-backend-api:${{ github.sha }}"
|
||||
format: "table"
|
||||
exit-code: "1"
|
||||
severity: "CRITICAL,HIGH"
|
||||
|
||||
- name: Trivy scan stream server
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-stream-server:${{ github.sha }}'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
- name: Trivy scan frontend
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: "veza-frontend:${{ github.sha }}"
|
||||
format: "table"
|
||||
exit-code: "1"
|
||||
severity: "CRITICAL,HIGH"
|
||||
|
||||
- name: Generate SBOM
|
||||
run: |
|
||||
mkdir -p sbom
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
trivy image --format cyclonedx --output "sbom/${svc}-${{ github.sha }}.json" "${svc}:${{ github.sha }}"
|
||||
done
|
||||
- name: Upload SBOM artifacts
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: sbom
|
||||
path: sbom/
|
||||
- name: Trivy scan stream server
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: "veza-stream-server:${{ github.sha }}"
|
||||
format: "table"
|
||||
exit-code: "1"
|
||||
severity: "CRITICAL,HIGH"
|
||||
|
||||
- name: Push Images to Registry
|
||||
if: vars.DOCKER_REGISTRY != ''
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_REGISTRY_PASSWORD }}" | docker login "${{ vars.DOCKER_REGISTRY }}" -u "${{ secrets.DOCKER_REGISTRY_USERNAME }}" --password-stdin
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
docker tag "${svc}:${{ github.sha }}" "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
|
||||
docker tag "${svc}:${{ github.sha }}" "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
|
||||
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
|
||||
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
|
||||
done
|
||||
- name: Generate SBOM
|
||||
run: |
|
||||
mkdir -p sbom
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
trivy image --format cyclonedx --output "sbom/${svc}-${{ github.sha }}.json" "${svc}:${{ github.sha }}"
|
||||
done
|
||||
- name: Upload SBOM artifacts
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: sbom
|
||||
path: sbom/
|
||||
|
||||
- name: Install cosign
|
||||
if: vars.DOCKER_REGISTRY != '' && vars.COSIGN_ENABLED == 'true'
|
||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
||||
with:
|
||||
cosign-release: 'v2.2.0'
|
||||
- name: Sign images with cosign
|
||||
if: vars.DOCKER_REGISTRY != '' && vars.COSIGN_ENABLED == 'true'
|
||||
env:
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
run: |
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
|
||||
done
|
||||
- name: Push Images to Registry
|
||||
if: vars.DOCKER_REGISTRY != ''
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_REGISTRY_PASSWORD }}" | docker login "${{ vars.DOCKER_REGISTRY }}" -u "${{ secrets.DOCKER_REGISTRY_USERNAME }}" --password-stdin
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
docker tag "${svc}:${{ github.sha }}" "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
|
||||
docker tag "${svc}:${{ github.sha }}" "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
|
||||
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
|
||||
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
|
||||
done
|
||||
|
||||
- name: Build Summary
|
||||
run: |
|
||||
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Backend: veza-backend-api:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Frontend: veza-frontend:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Stream Server: veza-stream-server:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
- name: Install cosign
|
||||
if: vars.DOCKER_REGISTRY != '' && vars.COSIGN_ENABLED == 'true'
|
||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
||||
with:
|
||||
cosign-release: "v2.2.0"
|
||||
- name: Sign images with cosign
|
||||
if: vars.DOCKER_REGISTRY != '' && vars.COSIGN_ENABLED == 'true'
|
||||
env:
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
run: |
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
|
||||
done
|
||||
|
||||
deploy:
|
||||
name: Deploy to ${{ github.event.inputs.environment || 'staging' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
|
||||
environment: ${{ github.event.inputs.environment || 'staging' }}
|
||||
steps:
|
||||
- name: Deploy to Kubernetes
|
||||
if: vars.KUBE_CONFIG_SET == 'true'
|
||||
run: |
|
||||
KUBECONFIG="${{ runner.temp }}/kubeconfig"
|
||||
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > "$KUBECONFIG"
|
||||
chmod 600 "$KUBECONFIG"
|
||||
export KUBECONFIG
|
||||
for svc in veza-backend-api veza-stream-server; do
|
||||
kubectl set image "deployment/${svc}" "${svc}=${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}" \
|
||||
-n veza --record || echo "Skipping ${svc} (deployment not found)"
|
||||
done
|
||||
kubectl rollout status deployment/veza-backend-api -n veza --timeout=300s || true
|
||||
rm -f "$KUBECONFIG"
|
||||
- name: Build Summary
|
||||
run: |
|
||||
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Backend: veza-backend-api:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Frontend: veza-frontend:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Stream Server: veza-stream-server:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Deployment Summary
|
||||
run: |
|
||||
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Environment: ${{ github.event.inputs.environment || 'staging' }}" >> $GITHUB_STEP_SUMMARY
|
||||
deploy:
|
||||
name: Deploy to ${{ github.event.inputs.environment || 'staging' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
|
||||
environment: ${{ github.event.inputs.environment || 'staging' }}
|
||||
steps:
|
||||
- name: Deploy to Kubernetes
|
||||
if: vars.KUBE_CONFIG_SET == 'true'
|
||||
run: |
|
||||
KUBECONFIG="${{ runner.temp }}/kubeconfig"
|
||||
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > "$KUBECONFIG"
|
||||
chmod 600 "$KUBECONFIG"
|
||||
export KUBECONFIG
|
||||
for svc in veza-backend-api veza-stream-server; do
|
||||
kubectl set image "deployment/${svc}" "${svc}=${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}" \
|
||||
-n veza --record || echo "Skipping ${svc} (deployment not found)"
|
||||
done
|
||||
kubectl rollout status deployment/veza-backend-api -n veza --timeout=300s || true
|
||||
rm -f "$KUBECONFIG"
|
||||
|
||||
smoke-post-deploy:
|
||||
name: Smoke tests post-deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
if: vars.STAGING_URL != ''
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Deployment Summary
|
||||
run: |
|
||||
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Environment: ${{ github.event.inputs.environment || 'staging' }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
smoke-post-deploy:
|
||||
name: Smoke tests post-deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
if: vars.STAGING_URL != ''
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
|
||||
- name: Install Playwright
|
||||
run: npx playwright install chromium --with-deps
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run smoke tests
|
||||
env:
|
||||
PLAYWRIGHT_BASE_URL: ${{ vars.STAGING_URL }}
|
||||
run: |
|
||||
cd apps/web
|
||||
npx playwright test --config=playwright.config.smoke.ts
|
||||
- name: Install Playwright
|
||||
run: npx playwright install chromium --with-deps
|
||||
|
||||
- name: Run smoke tests
|
||||
env:
|
||||
PLAYWRIGHT_BASE_URL: ${{ vars.STAGING_URL }}
|
||||
run: |
|
||||
cd apps/web
|
||||
npx playwright test --config=playwright.config.smoke.ts
|
||||
|
|
|
|||
577
.github/workflows/ci.yml
vendored
577
.github/workflows/ci.yml
vendored
|
|
@ -1,270 +1,387 @@
|
|||
name: Veza CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "remediation/*", "feature/mvp-complete" ]
|
||||
pull_request:
|
||||
branches: [ "main", "feature/mvp-complete" ]
|
||||
workflow_dispatch:
|
||||
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
|
||||
# ===========================================================================
|
||||
# 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: 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: 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 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: Install TMT
|
||||
run: pip install tmt
|
||||
|
||||
- name: Run TMT Vital Backend
|
||||
run: tmt --root tmt run plan --name /vital-backend
|
||||
- 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
|
||||
# ===========================================================================
|
||||
# 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: 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: 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 cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
|
||||
- name: Install TMT
|
||||
run: pip install tmt
|
||||
- name: Install TMT
|
||||
run: pip install tmt
|
||||
|
||||
- name: Run TMT Vital Services
|
||||
run: tmt --root tmt run plan --name /vital-services
|
||||
- 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
|
||||
# ===========================================================================
|
||||
# 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: 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: 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: 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: Install TMT
|
||||
run: pip install tmt
|
||||
|
||||
- name: Run TMT Vital Frontend
|
||||
run: tmt --root tmt run plan --name /vital-frontend
|
||||
- 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
|
||||
# ===========================================================================
|
||||
# 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: 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: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Storybook
|
||||
run: npm run build-storybook
|
||||
working-directory: apps/web
|
||||
- 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
|
||||
- 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 (Playwright) — kept outside TMT (complex infra setup)
|
||||
# ===========================================================================
|
||||
e2e:
|
||||
name: E2E (Playwright)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
# ===========================================================================
|
||||
# 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 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: 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: 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: 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: 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: 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: 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: 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
|
||||
working-directory: apps/web
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npx playwright test
|
||||
working-directory: apps/web
|
||||
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 }}
|
||||
- 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
|
||||
- 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()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: apps/web/playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
# ===========================================================================
|
||||
# Notify on failure
|
||||
# ===========================================================================
|
||||
notify-failure:
|
||||
name: Notify on failure
|
||||
needs: [vital-backend, vital-services, vital-frontend, storybook, e2e]
|
||||
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 }}"
|
||||
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 }}"
|
||||
|
|
|
|||
36
.github/workflows/commitlint.yml
vendored
Normal file
36
.github/workflows/commitlint.yml
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
name: Commit Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, "feature/**"]
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
commitlint:
|
||||
name: Validate commit messages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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 commitlint
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
- name: Validate PR commit messages
|
||||
run: |
|
||||
# Validate all commits in the PR
|
||||
npx commitlint \
|
||||
--from=${{ github.event.pull_request.base.sha }} \
|
||||
--to=${{ github.event.pull_request.head.sha }} \
|
||||
--verbose
|
||||
144
.github/workflows/container-scan.yml
vendored
144
.github/workflows/container-scan.yml
vendored
|
|
@ -1,84 +1,88 @@
|
|||
name: Container Image Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'veza-backend-api/Dockerfile*'
|
||||
- 'apps/web/Dockerfile*'
|
||||
- 'veza-stream-server/Dockerfile*'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'veza-backend-api/Dockerfile*'
|
||||
- 'apps/web/Dockerfile*'
|
||||
- 'veza-stream-server/Dockerfile*'
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "veza-backend-api/Dockerfile*"
|
||||
- "apps/web/Dockerfile*"
|
||||
- "veza-stream-server/Dockerfile*"
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "veza-backend-api/Dockerfile*"
|
||||
- "apps/web/Dockerfile*"
|
||||
- "veza-stream-server/Dockerfile*"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
scan-backend:
|
||||
name: Scan Backend Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
scan-backend:
|
||||
name: Scan Backend Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Build backend image
|
||||
run: docker build -t veza-backend:scan -f veza-backend-api/Dockerfile.production veza-backend-api/
|
||||
- name: Build backend image
|
||||
run: docker build -t veza-backend:scan -f veza-backend-api/Dockerfile.production veza-backend-api/
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-backend:scan'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
ignore-unfixed: true
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: "veza-backend:scan"
|
||||
format: "table"
|
||||
exit-code: "1"
|
||||
severity: "CRITICAL,HIGH"
|
||||
ignore-unfixed: true
|
||||
|
||||
scan-stream-server:
|
||||
name: Scan Stream Server Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
scan-stream-server:
|
||||
name: Scan Stream Server Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Build stream server image
|
||||
run: docker build -t veza-stream:scan -f veza-stream-server/Dockerfile .
|
||||
- name: Build stream server image
|
||||
run: docker build -t veza-stream:scan -f veza-stream-server/Dockerfile .
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-stream:scan'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
ignore-unfixed: true
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: "veza-stream:scan"
|
||||
format: "table"
|
||||
exit-code: "1"
|
||||
severity: "CRITICAL,HIGH"
|
||||
ignore-unfixed: true
|
||||
|
||||
scan-frontend:
|
||||
name: Scan Frontend Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
scan-frontend:
|
||||
name: Scan Frontend Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Check if frontend Dockerfile exists
|
||||
id: check
|
||||
run: |
|
||||
if [ -f "apps/web/Dockerfile" ] || [ -f "apps/web/Dockerfile.production" ]; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Check if frontend Dockerfile exists
|
||||
id: check
|
||||
run: |
|
||||
if [ -f "apps/web/Dockerfile" ] || [ -f "apps/web/Dockerfile.production" ]; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build frontend image
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
run: |
|
||||
DOCKERFILE=$([ -f "apps/web/Dockerfile.production" ] && echo "apps/web/Dockerfile.production" || echo "apps/web/Dockerfile")
|
||||
docker build -t veza-frontend:scan -f "$DOCKERFILE" apps/web/
|
||||
- name: Build frontend image
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
run: |
|
||||
DOCKERFILE=$([ -f "apps/web/Dockerfile.production" ] && echo "apps/web/Dockerfile.production" || echo "apps/web/Dockerfile")
|
||||
docker build -t veza-frontend:scan -f "$DOCKERFILE" apps/web/
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-frontend:scan'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
ignore-unfixed: true
|
||||
- name: Run Trivy vulnerability scanner
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # v0.28.0
|
||||
with:
|
||||
image-ref: "veza-frontend:scan"
|
||||
format: "table"
|
||||
exit-code: "1"
|
||||
severity: "CRITICAL,HIGH"
|
||||
ignore-unfixed: true
|
||||
|
|
|
|||
101
.github/workflows/contract-testing.yml
vendored
Normal file
101
.github/workflows/contract-testing.yml
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
name: Contract Testing (Schemathesis)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "veza-backend-api/**.go"
|
||||
- "veza-backend-api/openapi.yaml"
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
contract-test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: veza_test
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
env:
|
||||
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veza_test?sslmode=disable
|
||||
REDIS_URL: redis://localhost:6379
|
||||
JWT_SECRET: test-jwt-secret-for-ci
|
||||
APP_ENV: test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install schemathesis
|
||||
run: pip install schemathesis
|
||||
|
||||
- name: Download Go deps
|
||||
run: cd veza-backend-api && go mod download
|
||||
|
||||
- name: Run migrations
|
||||
run: cd veza-backend-api && go run cmd/migrate_tool/main.go
|
||||
continue-on-error: true
|
||||
|
||||
- name: Start backend API
|
||||
run: |
|
||||
cd veza-backend-api && go run cmd/api/main.go &
|
||||
# Wait for API to be ready
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:18080/api/v1/health > /dev/null 2>&1; then
|
||||
echo "API is ready"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for API... ($i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
- name: Run schemathesis contract tests
|
||||
run: >
|
||||
st run
|
||||
--checks all
|
||||
veza-backend-api/openapi.yaml
|
||||
--base-url http://localhost:18080
|
||||
--hypothesis-max-examples=50
|
||||
--request-timeout=10000
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload schemathesis report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: schemathesis-report
|
||||
path: .schemathesis/
|
||||
retention-days: 14
|
||||
100
.github/workflows/flaky-report.yml
vendored
Normal file
100
.github/workflows/flaky-report.yml
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
name: Flaky Test Report
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 6 * * 1" # Every Monday 6am
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
flaky-report:
|
||||
name: Detect flaky tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
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
|
||||
run: echo "127.0.0.1 veza.fr" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Start backend services
|
||||
run: |
|
||||
docker-compose up -d postgres redis rabbitmq
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec veza_postgres pg_isready -U veza 2>/dev/null; then break; fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
- 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: Start backend API
|
||||
env:
|
||||
APP_ENV: test
|
||||
APP_PORT: "18080"
|
||||
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
||||
REDIS_URL: redis://localhost:16379
|
||||
JWT_SECRET: flaky-test-jwt-secret
|
||||
COOKIE_SECURE: "false"
|
||||
CORS_ALLOWED_ORIGINS: http://localhost:5174
|
||||
DISABLE_RATE_LIMIT_FOR_TESTS: "true"
|
||||
ACCOUNT_LOCKOUT_EXEMPT_EMAILS: "user@veza.music,artist@veza.music,admin@veza.music"
|
||||
run: |
|
||||
cd veza-backend-api && go build -o veza-api ./cmd/api/main.go && ./veza-api &
|
||||
sleep 10
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E tests (3x retries to detect flakiness)
|
||||
run: |
|
||||
npx playwright test \
|
||||
--config=tests/e2e/playwright.config.ts \
|
||||
--project=chromium \
|
||||
--retries=3 \
|
||||
--reporter=json
|
||||
continue-on-error: true
|
||||
env:
|
||||
PORT: "5174"
|
||||
VITE_API_URL: "/api/v1"
|
||||
PLAYWRIGHT_BASE_URL: "http://localhost:5174"
|
||||
|
||||
- name: Generate flaky report
|
||||
run: node scripts/flaky-detection.mjs > flaky-report.md
|
||||
|
||||
- name: Upload flaky report
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: flaky-test-report
|
||||
path: flaky-report.md
|
||||
retention-days: 30
|
||||
|
||||
- name: Create/update issue with flaky tests
|
||||
if: always()
|
||||
run: |
|
||||
REPORT=$(cat flaky-report.md)
|
||||
FLAKY_COUNT=$(grep -c "^|" flaky-report.md | head -1 || echo "0")
|
||||
if [ "$FLAKY_COUNT" -gt 2 ]; then
|
||||
echo "Found flaky tests — check artifact for details"
|
||||
fi
|
||||
75
.github/workflows/frontend-ci.yml
vendored
75
.github/workflows/frontend-ci.yml
vendored
|
|
@ -1,51 +1,54 @@
|
|||
name: Frontend CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/frontend-ci.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/frontend-ci.yml"
|
||||
push:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/frontend-ci.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/frontend-ci.yml"
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/web
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/web
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
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: apps/web/package-lock.json
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: apps/web/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: TypeScript check
|
||||
run: npx tsc --noEmit
|
||||
- name: TypeScript check
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Bundle size gate
|
||||
run: node scripts/check-bundle-size.mjs
|
||||
- name: Bundle size gate
|
||||
run: node scripts/check-bundle-size.mjs
|
||||
|
||||
- name: Audit dependencies
|
||||
run: npm audit --audit-level=critical
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test -- --run
|
||||
- name: Audit dependencies
|
||||
run: npm audit --audit-level=critical
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test -- --run
|
||||
|
|
|
|||
42
.github/workflows/go-fuzz.yml
vendored
Normal file
42
.github/workflows/go-fuzz.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
name: Go Fuzz Tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 2 * * *" # Nightly at 2am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
fuzz:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-backend-api
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Download deps
|
||||
run: go mod download
|
||||
|
||||
- name: Run fuzz tests
|
||||
run: go test -fuzz=Fuzz -fuzztime=60s ./internal/handlers/...
|
||||
|
||||
- name: Upload fuzz corpus
|
||||
if: always()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: fuzz-corpus
|
||||
path: veza-backend-api/testdata/fuzz/
|
||||
retention-days: 30
|
||||
138
.github/workflows/load-test-nightly.yml
vendored
138
.github/workflows/load-test-nightly.yml
vendored
|
|
@ -1,81 +1,85 @@
|
|||
name: Load Tests (Nightly)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
load-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
load-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install k6
|
||||
run: |
|
||||
sudo gpg -k
|
||||
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
|
||||
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
|
||||
sudo apt-get update && sudo apt-get install -y k6
|
||||
- name: Install k6
|
||||
run: |
|
||||
sudo gpg -k
|
||||
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
|
||||
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
|
||||
sudo apt-get update && sudo apt-get install -y k6
|
||||
|
||||
- name: Start infrastructure
|
||||
run: |
|
||||
docker-compose -f docker-compose.yml up -d postgres redis rabbitmq
|
||||
sleep 15
|
||||
- name: Start infrastructure
|
||||
run: |
|
||||
docker-compose -f docker-compose.yml up -d postgres redis rabbitmq
|
||||
sleep 15
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Run migrations
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
||||
REDIS_URL: redis://localhost:16379
|
||||
JWT_SECRET: test-jwt-secret-for-load-test
|
||||
APP_ENV: test
|
||||
run: |
|
||||
go mod download
|
||||
go run cmd/migrate_tool/main.go || true
|
||||
- name: Run migrations
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
||||
REDIS_URL: redis://localhost:16379
|
||||
JWT_SECRET: test-jwt-secret-for-load-test
|
||||
APP_ENV: test
|
||||
run: |
|
||||
go mod download
|
||||
go run cmd/migrate_tool/main.go || true
|
||||
|
||||
- name: Start backend API
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
||||
REDIS_URL: redis://localhost:16379
|
||||
RABBITMQ_URL: amqp://veza:devpassword@localhost:15672/
|
||||
JWT_SECRET: test-jwt-secret-for-load-test
|
||||
APP_ENV: test
|
||||
PORT: 8080
|
||||
run: |
|
||||
go run cmd/api/main.go &
|
||||
sleep 15
|
||||
- name: Start backend API
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
||||
REDIS_URL: redis://localhost:16379
|
||||
RABBITMQ_URL: amqp://veza:devpassword@localhost:15672/
|
||||
JWT_SECRET: test-jwt-secret-for-load-test
|
||||
APP_ENV: test
|
||||
PORT: 8080
|
||||
run: |
|
||||
go run cmd/api/main.go &
|
||||
sleep 15
|
||||
|
||||
- name: Wait for backend
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||||
if curl -sf http://localhost:8080/health; then
|
||||
echo "Backend ready"
|
||||
exit 0
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
echo "Backend not ready"
|
||||
exit 1
|
||||
- name: Wait for backend
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||||
if curl -sf http://localhost:8080/health; then
|
||||
echo "Backend ready"
|
||||
exit 0
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
echo "Backend not ready"
|
||||
exit 1
|
||||
|
||||
- name: Run smoke load test
|
||||
run: k6 run loadtests/smoke.js
|
||||
- name: Run smoke load test
|
||||
run: k6 run loadtests/smoke.js
|
||||
|
||||
- name: Run backend load test
|
||||
run: |
|
||||
k6 run --out json=load-results.json loadtests/backend/full.js || true
|
||||
continue-on-error: true
|
||||
- name: Run backend load test
|
||||
run: |
|
||||
k6 run --out json=load-results.json loadtests/backend/full.js || true
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: load-test-results
|
||||
path: load-results.json
|
||||
if: always()
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: load-test-results
|
||||
path: load-results.json
|
||||
if: always()
|
||||
|
|
|
|||
40
.github/workflows/mutation-testing.yml
vendored
Normal file
40
.github/workflows/mutation-testing.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: Mutation Testing
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 3 * * 0" # Weekly on Sunday at 3am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
mutation:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: apps/web/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/web && npm ci
|
||||
|
||||
- name: Run Stryker mutation testing
|
||||
run: cd apps/web && npx stryker run
|
||||
|
||||
- name: Upload mutation report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mutation-report
|
||||
path: apps/web/reports/mutation/
|
||||
retention-days: 30
|
||||
21
.github/workflows/openapi-lint.yml
vendored
Normal file
21
.github/workflows/openapi-lint.yml
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
name: OpenAPI Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "veza-backend-api/openapi.yaml"
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Lint OpenAPI spec
|
||||
run: npx --yes @redocly/cli lint veza-backend-api/openapi.yaml
|
||||
43
.github/workflows/performance.yml
vendored
Normal file
43
.github/workflows/performance.yml
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
name: Performance
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
bundle-size:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: apps/web/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/web && npm ci
|
||||
|
||||
- name: Build
|
||||
run: cd apps/web && npm run build
|
||||
|
||||
- name: Check bundle size
|
||||
run: cd apps/web && npx size-limit
|
||||
|
||||
- name: Upload stats artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-stats
|
||||
path: apps/web/stats.html
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
88
.github/workflows/rust-ci.yml
vendored
88
.github/workflows/rust-ci.yml
vendored
|
|
@ -1,52 +1,56 @@
|
|||
name: Rust CI
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'veza-stream-server/**'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'veza-stream-server/**'
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "veza-stream-server/**"
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "veza-stream-server/**"
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
test-and-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
components: clippy
|
||||
test-and-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Clippy lint
|
||||
run: cargo clippy -- -D warnings
|
||||
working-directory: veza-stream-server
|
||||
- name: Clippy lint
|
||||
run: cargo clippy -- -D warnings
|
||||
working-directory: veza-stream-server
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --workspace --timeout 300
|
||||
working-directory: veza-stream-server
|
||||
- name: Run tests
|
||||
run: cargo test --workspace --timeout 300
|
||||
working-directory: veza-stream-server
|
||||
|
||||
- name: Install cargo-tarpaulin
|
||||
run: cargo install cargo-tarpaulin
|
||||
- name: Install cargo-tarpaulin
|
||||
run: cargo install cargo-tarpaulin
|
||||
|
||||
- name: Measure coverage
|
||||
run: cargo tarpaulin --out json --output-dir target/coverage --timeout 300 --skip-clean
|
||||
working-directory: veza-stream-server
|
||||
- name: Measure coverage
|
||||
run: cargo tarpaulin --out json --output-dir target/coverage --timeout 300 --skip-clean
|
||||
working-directory: veza-stream-server
|
||||
|
||||
- name: Enforce coverage threshold (>= 50%)
|
||||
run: |
|
||||
COVERAGE=$(cat target/coverage/tarpaulin-report.json | python3 -c "import sys,json; print(f'{json.load(sys.stdin).get(\"coverage\", 0):.1f}')")
|
||||
echo "Rust coverage: ${COVERAGE}%"
|
||||
COV_INT=$(echo "$COVERAGE" | cut -d. -f1)
|
||||
if [ "$COV_INT" -lt 50 ]; then
|
||||
echo "::error::Rust coverage ${COVERAGE}% is below the 50% threshold"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Rust coverage ${COVERAGE}% meets the >= 50% threshold"
|
||||
working-directory: veza-stream-server
|
||||
- name: Enforce coverage threshold (>= 50%)
|
||||
run: |
|
||||
COVERAGE=$(cat target/coverage/tarpaulin-report.json | python3 -c "import sys,json; print(f'{json.load(sys.stdin).get(\"coverage\", 0):.1f}')")
|
||||
echo "Rust coverage: ${COVERAGE}%"
|
||||
COV_INT=$(echo "$COVERAGE" | cut -d. -f1)
|
||||
if [ "$COV_INT" -lt 50 ]; then
|
||||
echo "::error::Rust coverage ${COVERAGE}% is below the 50% threshold"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Rust coverage ${COVERAGE}% meets the >= 50% threshold"
|
||||
working-directory: veza-stream-server
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: rust-coverage
|
||||
path: veza-stream-server/target/coverage/tarpaulin-report.json
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: rust-coverage
|
||||
path: veza-stream-server/target/coverage/tarpaulin-report.json
|
||||
|
|
|
|||
46
.github/workflows/rust-mutation.yml
vendored
Normal file
46
.github/workflows/rust-mutation.yml
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
name: Rust Mutation Testing
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 4 * * 0" # Weekly on Sunday at 4am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
mutants:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
veza-stream-server/target
|
||||
key: ${{ runner.os }}-cargo-mutants-${{ hashFiles('veza-stream-server/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-mutants-
|
||||
|
||||
- name: Install cargo-mutants
|
||||
run: cargo install cargo-mutants
|
||||
|
||||
- name: Run mutation testing
|
||||
run: cd veza-stream-server && cargo mutants --timeout 120 -- --lib
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload mutation results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: mutation-results
|
||||
path: veza-stream-server/mutants.out/
|
||||
retention-days: 30
|
||||
40
.github/workflows/sast.yml
vendored
40
.github/workflows/sast.yml
vendored
|
|
@ -1,22 +1,26 @@
|
|||
name: CodeQL SAST
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
strategy:
|
||||
matrix:
|
||||
language: [go, javascript-typescript]
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: github/codeql-action/init@fca7ace96b7d713c7035871441585e9e013f7cac # v3.28.18
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- uses: github/codeql-action/autobuild@fca7ace96b7d713c7035871441585e9e013f7cac # v3.28.18
|
||||
- uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441585e9e013f7cac # v3.28.18
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
strategy:
|
||||
matrix:
|
||||
language: [go, javascript-typescript]
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: github/codeql-action/init@fca7ace96b7d713c7035871441585e9e013f7cac # v3.28.18
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- uses: github/codeql-action/autobuild@fca7ace96b7d713c7035871441585e9e013f7cac # v3.28.18
|
||||
- uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441585e9e013f7cac # v3.28.18
|
||||
|
|
|
|||
36
.github/workflows/security-scan.yml
vendored
36
.github/workflows/security-scan.yml
vendored
|
|
@ -1,22 +1,26 @@
|
|||
name: Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
name: Secret Scanning (gitleaks)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
gitleaks:
|
||||
name: Secret Scanning (gitleaks)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Gitleaks
|
||||
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196e88a9c30 # v2.3.8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run Gitleaks
|
||||
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196e88a9c30 # v2.3.8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
43
.github/workflows/semgrep.yml
vendored
Normal file
43
.github/workflows/semgrep.yml
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
name: Semgrep SAST
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: "0 3 * * 1" # Weekly on Monday at 3am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
semgrep:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
container:
|
||||
image: returntocorp/semgrep
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Run Semgrep
|
||||
run: >
|
||||
semgrep scan
|
||||
--config p/auto
|
||||
--config p/owasp-top-ten
|
||||
--config p/r2c-security-audit
|
||||
--error
|
||||
--json
|
||||
--output semgrep-results.json
|
||||
.
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload Semgrep results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: semgrep-results
|
||||
path: semgrep-results.json
|
||||
retention-days: 30
|
||||
578
.github/workflows/staging-validation.yml
vendored
578
.github/workflows/staging-validation.yml
vendored
|
|
@ -3,304 +3,314 @@ name: Staging Validation Pipeline
|
|||
# Comprehensive staging validation: deploy, perf, Lighthouse, stability, GDPR, bundle size
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_deploy:
|
||||
description: 'Skip deployment (validate existing staging)'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
stability_duration:
|
||||
description: 'Stability check duration (minutes)'
|
||||
required: false
|
||||
default: '10'
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_deploy:
|
||||
description: "Skip deployment (validate existing staging)"
|
||||
required: false
|
||||
default: "false"
|
||||
type: boolean
|
||||
stability_duration:
|
||||
description: "Stability check duration (minutes)"
|
||||
required: false
|
||||
default: "10"
|
||||
type: string
|
||||
|
||||
env:
|
||||
STAGING_URL: ${{ vars.STAGING_URL || 'https://staging.veza.app' }}
|
||||
STAGING_API_URL: ${{ vars.STAGING_API_URL || 'https://staging.veza.app/api/v1' }}
|
||||
STAGING_URL: ${{ vars.STAGING_URL || 'https://staging.veza.app' }}
|
||||
STAGING_API_URL: ${{ vars.STAGING_API_URL || 'https://staging.veza.app/api/v1' }}
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-001: Deploy staging (all services)
|
||||
# ─────────────────────────────────────────────────────
|
||||
deploy-staging:
|
||||
name: Deploy to Staging
|
||||
runs-on: ubuntu-latest
|
||||
if: inputs.skip_deploy != 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-001: Deploy staging (all services)
|
||||
# ─────────────────────────────────────────────────────
|
||||
deploy-staging:
|
||||
name: Deploy to Staging
|
||||
runs-on: ubuntu-latest
|
||||
if: inputs.skip_deploy != 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
|
||||
- name: Build all images
|
||||
run: |
|
||||
docker build -t veza-backend-api:staging -f veza-backend-api/Dockerfile.production veza-backend-api/
|
||||
docker build -t veza-frontend:staging -f apps/web/Dockerfile.production apps/web/
|
||||
docker build -t veza-stream-server:staging -f veza-stream-server/Dockerfile.production veza-stream-server/
|
||||
- name: Build all images
|
||||
run: |
|
||||
docker build -t veza-backend-api:staging -f veza-backend-api/Dockerfile.production veza-backend-api/
|
||||
docker build -t veza-frontend:staging -f apps/web/Dockerfile.production apps/web/
|
||||
docker build -t veza-stream-server:staging -f veza-stream-server/Dockerfile.production veza-stream-server/
|
||||
|
||||
- name: Push to registry
|
||||
if: vars.DOCKER_REGISTRY != ''
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_REGISTRY_PASSWORD }}" | docker login "${{ vars.DOCKER_REGISTRY }}" -u "${{ secrets.DOCKER_REGISTRY_USERNAME }}" --password-stdin
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
docker tag "${svc}:staging" "${{ vars.DOCKER_REGISTRY }}/${svc}:staging"
|
||||
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:staging"
|
||||
done
|
||||
- name: Push to registry
|
||||
if: vars.DOCKER_REGISTRY != ''
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_REGISTRY_PASSWORD }}" | docker login "${{ vars.DOCKER_REGISTRY }}" -u "${{ secrets.DOCKER_REGISTRY_USERNAME }}" --password-stdin
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
docker tag "${svc}:staging" "${{ vars.DOCKER_REGISTRY }}/${svc}:staging"
|
||||
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:staging"
|
||||
done
|
||||
|
||||
- name: Deploy via SSH (docker-compose)
|
||||
if: vars.STAGING_SSH_HOST != ''
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_KEY" > ~/.ssh/staging_key
|
||||
chmod 600 ~/.ssh/staging_key
|
||||
ssh -i ~/.ssh/staging_key -o StrictHostKeyChecking=no \
|
||||
${{ vars.STAGING_SSH_USER }}@${{ vars.STAGING_SSH_HOST }} \
|
||||
"cd /opt/veza && docker compose -f docker-compose.staging.yml pull && docker compose -f docker-compose.staging.yml up -d"
|
||||
rm -f ~/.ssh/staging_key
|
||||
- name: Deploy via SSH (docker-compose)
|
||||
if: vars.STAGING_SSH_HOST != ''
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_KEY" > ~/.ssh/staging_key
|
||||
chmod 600 ~/.ssh/staging_key
|
||||
ssh -i ~/.ssh/staging_key -o StrictHostKeyChecking=no \
|
||||
${{ vars.STAGING_SSH_USER }}@${{ vars.STAGING_SSH_HOST }} \
|
||||
"cd /opt/veza && docker compose -f docker-compose.staging.yml pull && docker compose -f docker-compose.staging.yml up -d"
|
||||
rm -f ~/.ssh/staging_key
|
||||
|
||||
- name: Deploy via Kubernetes
|
||||
if: vars.KUBE_CONFIG_SET == 'true'
|
||||
run: |
|
||||
KUBECONFIG="${{ runner.temp }}/kubeconfig"
|
||||
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > "$KUBECONFIG"
|
||||
chmod 600 "$KUBECONFIG"
|
||||
export KUBECONFIG
|
||||
for svc in veza-backend-api veza-stream-server; do
|
||||
kubectl set image "deployment/${svc}" "${svc}=${{ vars.DOCKER_REGISTRY }}/${svc}:staging" \
|
||||
-n veza --record || echo "Skipping ${svc}"
|
||||
done
|
||||
kubectl rollout status deployment/veza-backend-api -n veza --timeout=300s || true
|
||||
rm -f "$KUBECONFIG"
|
||||
- name: Deploy via Kubernetes
|
||||
if: vars.KUBE_CONFIG_SET == 'true'
|
||||
run: |
|
||||
KUBECONFIG="${{ runner.temp }}/kubeconfig"
|
||||
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > "$KUBECONFIG"
|
||||
chmod 600 "$KUBECONFIG"
|
||||
export KUBECONFIG
|
||||
for svc in veza-backend-api veza-stream-server; do
|
||||
kubectl set image "deployment/${svc}" "${svc}=${{ vars.DOCKER_REGISTRY }}/${svc}:staging" \
|
||||
-n veza --record || echo "Skipping ${svc}"
|
||||
done
|
||||
kubectl rollout status deployment/veza-backend-api -n veza --timeout=300s || true
|
||||
rm -f "$KUBECONFIG"
|
||||
|
||||
- name: Wait for staging to be healthy
|
||||
run: |
|
||||
echo "Waiting for staging services to be healthy..."
|
||||
for i in $(seq 1 30); do
|
||||
STATUS=$(curl -sf "${{ env.STAGING_API_URL }}/health" | jq -r '.status' 2>/dev/null || echo "unreachable")
|
||||
if [ "$STATUS" = "ok" ] || [ "$STATUS" = "healthy" ]; then
|
||||
echo "Staging is healthy!"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i/30: status=$STATUS, waiting 10s..."
|
||||
sleep 10
|
||||
done
|
||||
echo "Staging did not become healthy in 300s"
|
||||
exit 1
|
||||
- name: Wait for staging to be healthy
|
||||
run: |
|
||||
echo "Waiting for staging services to be healthy..."
|
||||
for i in $(seq 1 30); do
|
||||
STATUS=$(curl -sf "${{ env.STAGING_API_URL }}/health" | jq -r '.status' 2>/dev/null || echo "unreachable")
|
||||
if [ "$STATUS" = "ok" ] || [ "$STATUS" = "healthy" ]; then
|
||||
echo "Staging is healthy!"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i/30: status=$STATUS, waiting 10s..."
|
||||
sleep 10
|
||||
done
|
||||
echo "Staging did not become healthy in 300s"
|
||||
exit 1
|
||||
|
||||
- name: Deep health check
|
||||
run: |
|
||||
echo "## Deep Health Check" >> $GITHUB_STEP_SUMMARY
|
||||
HEALTH=$(curl -sf "${{ env.STAGING_API_URL }}/health/deep" || echo '{"error":"unreachable"}')
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$HEALTH" | jq . >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
- name: Deep health check
|
||||
run: |
|
||||
echo "## Deep Health Check" >> $GITHUB_STEP_SUMMARY
|
||||
HEALTH=$(curl -sf "${{ env.STAGING_API_URL }}/health/deep" || echo '{"error":"unreachable"}')
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$HEALTH" | jq . >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-002: Performance validation (p95 < 100ms)
|
||||
# ─────────────────────────────────────────────────────
|
||||
performance-validation:
|
||||
name: Performance Validation (k6)
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-002: Performance validation (p95 < 100ms)
|
||||
# ─────────────────────────────────────────────────────
|
||||
performance-validation:
|
||||
name: Performance Validation (k6)
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install k6
|
||||
run: |
|
||||
sudo gpg -k
|
||||
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
|
||||
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
|
||||
sudo apt-get update && sudo apt-get install -y k6
|
||||
- name: Install k6
|
||||
run: |
|
||||
sudo gpg -k
|
||||
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
|
||||
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
|
||||
sudo apt-get update && sudo apt-get install -y k6
|
||||
|
||||
- name: Run staging performance validation
|
||||
run: |
|
||||
k6 run --out json=perf-results.json \
|
||||
--env BASE_URL="${{ env.STAGING_API_URL }}" \
|
||||
--env SCENARIO=smoke \
|
||||
loadtests/staging/validation_v0140.js
|
||||
- name: Run staging performance validation
|
||||
run: |
|
||||
k6 run --out json=perf-results.json \
|
||||
--env BASE_URL="${{ env.STAGING_API_URL }}" \
|
||||
--env SCENARIO=smoke \
|
||||
loadtests/staging/validation_v0140.js
|
||||
|
||||
- name: Upload performance results
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: performance-results
|
||||
path: perf-results.json
|
||||
- name: Upload performance results
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: performance-results
|
||||
path: perf-results.json
|
||||
if: always()
|
||||
|
||||
- name: Performance summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Performance Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Target: p95 < 100ms, stream start < 500ms" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-003: Lighthouse validation
|
||||
# ─────────────────────────────────────────────────────
|
||||
lighthouse-validation:
|
||||
name: Lighthouse Audit
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install Lighthouse CI
|
||||
run: npm install -g @lhci/cli@0.14.x
|
||||
|
||||
- name: Run Lighthouse CI
|
||||
run: lhci autorun --config=.lighthouserc.js
|
||||
env:
|
||||
LHCI_BUILD_CONTEXT__CURRENT_HASH: ${{ github.sha }}
|
||||
|
||||
- name: Upload Lighthouse results
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: lighthouse-results
|
||||
path: .lighthouseci/
|
||||
if: always()
|
||||
|
||||
- name: Lighthouse summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Lighthouse Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Targets: Performance >= 85, Accessibility >= 90, PWA >= 90" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f .lighthouseci/assertion-results.json ]; then
|
||||
PASSED=$(jq '[.[] | select(.level == "error")] | length' .lighthouseci/assertion-results.json 2>/dev/null || echo "?")
|
||||
echo "Assertion errors: $PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-004: Stability validation (5xx < 0.1%)
|
||||
# ─────────────────────────────────────────────────────
|
||||
stability-validation:
|
||||
name: Stability Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-staging, performance-validation]
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Run stability check
|
||||
run: |
|
||||
chmod +x scripts/staging-stability-check.sh
|
||||
DURATION_MINUTES=${{ inputs.stability_duration || '10' }} \
|
||||
STAGING_API_URL="${{ env.STAGING_API_URL }}" \
|
||||
MAX_5XX_RATE="0.001" \
|
||||
bash scripts/staging-stability-check.sh
|
||||
|
||||
- name: Stability summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Stability Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Duration: ${{ inputs.stability_duration || '10' }} minutes" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Target: 5xx rate < 0.1%" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f stability-report.json ]; then
|
||||
cat stability-report.json | jq . >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-005: GDPR validation (export + deletion E2E)
|
||||
# ─────────────────────────────────────────────────────
|
||||
gdpr-validation:
|
||||
name: GDPR Compliance Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Run GDPR integration tests
|
||||
working-directory: veza-backend-api
|
||||
run: go test -v -tags=integration -run TestGDPR -timeout 120s ./tests/integration/...
|
||||
|
||||
- name: GDPR summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## GDPR Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Data export: tested" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Account deletion: tested" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-006: Bundle size validation (< 200KB gzip)
|
||||
# ─────────────────────────────────────────────────────
|
||||
bundle-size-validation:
|
||||
name: Bundle Size Check
|
||||
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"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build frontend
|
||||
working-directory: apps/web
|
||||
run: npx vite build --outDir dist_verification
|
||||
env:
|
||||
NODE_ENV: production
|
||||
|
||||
- name: Check bundle size
|
||||
working-directory: apps/web
|
||||
run: node scripts/check-bundle-size.mjs
|
||||
|
||||
- name: Bundle size summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Bundle Size Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Target: JS initial < 200KB gzipped" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# Final summary
|
||||
# ─────────────────────────────────────────────────────
|
||||
validation-summary:
|
||||
name: Validation Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
deploy-staging,
|
||||
performance-validation,
|
||||
lighthouse-validation,
|
||||
stability-validation,
|
||||
gdpr-validation,
|
||||
bundle-size-validation,
|
||||
]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Generate final report
|
||||
run: |
|
||||
echo "# Staging Validation Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Deploy (STAG-001) | ${{ needs.deploy-staging.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Performance (STAG-002) | ${{ needs.performance-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Lighthouse (STAG-003) | ${{ needs.lighthouse-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability (STAG-004) | ${{ needs.stability-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| GDPR (STAG-005) | ${{ needs.gdpr-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Bundle Size (STAG-006) | ${{ needs.bundle-size-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Performance summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Performance Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Target: p95 < 100ms, stream start < 500ms" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-003: Lighthouse validation
|
||||
# ─────────────────────────────────────────────────────
|
||||
lighthouse-validation:
|
||||
name: Lighthouse Audit
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Lighthouse CI
|
||||
run: npm install -g @lhci/cli@0.14.x
|
||||
|
||||
- name: Run Lighthouse CI
|
||||
run: lhci autorun --config=.lighthouserc.js
|
||||
env:
|
||||
LHCI_BUILD_CONTEXT__CURRENT_HASH: ${{ github.sha }}
|
||||
|
||||
- name: Upload Lighthouse results
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: lighthouse-results
|
||||
path: .lighthouseci/
|
||||
if: always()
|
||||
|
||||
- name: Lighthouse summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Lighthouse Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Targets: Performance >= 85, Accessibility >= 90, PWA >= 90" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f .lighthouseci/assertion-results.json ]; then
|
||||
PASSED=$(jq '[.[] | select(.level == "error")] | length' .lighthouseci/assertion-results.json 2>/dev/null || echo "?")
|
||||
echo "Assertion errors: $PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-004: Stability validation (5xx < 0.1%)
|
||||
# ─────────────────────────────────────────────────────
|
||||
stability-validation:
|
||||
name: Stability Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-staging, performance-validation]
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Run stability check
|
||||
run: |
|
||||
chmod +x scripts/staging-stability-check.sh
|
||||
DURATION_MINUTES=${{ inputs.stability_duration || '10' }} \
|
||||
STAGING_API_URL="${{ env.STAGING_API_URL }}" \
|
||||
MAX_5XX_RATE="0.001" \
|
||||
bash scripts/staging-stability-check.sh
|
||||
|
||||
- name: Stability summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Stability Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Duration: ${{ inputs.stability_duration || '10' }} minutes" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Target: 5xx rate < 0.1%" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f stability-report.json ]; then
|
||||
cat stability-report.json | jq . >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-005: GDPR validation (export + deletion E2E)
|
||||
# ─────────────────────────────────────────────────────
|
||||
gdpr-validation:
|
||||
name: GDPR Compliance Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Run GDPR integration tests
|
||||
working-directory: veza-backend-api
|
||||
run: go test -v -tags=integration -run TestGDPR -timeout 120s ./tests/integration/...
|
||||
|
||||
- name: GDPR summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## GDPR Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Data export: tested" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Account deletion: tested" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-006: Bundle size validation (< 200KB gzip)
|
||||
# ─────────────────────────────────────────────────────
|
||||
bundle-size-validation:
|
||||
name: Bundle Size Check
|
||||
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'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build frontend
|
||||
working-directory: apps/web
|
||||
run: npx vite build --outDir dist_verification
|
||||
env:
|
||||
NODE_ENV: production
|
||||
|
||||
- name: Check bundle size
|
||||
working-directory: apps/web
|
||||
run: node scripts/check-bundle-size.mjs
|
||||
|
||||
- name: Bundle size summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Bundle Size Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Target: JS initial < 200KB gzipped" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# Final summary
|
||||
# ─────────────────────────────────────────────────────
|
||||
validation-summary:
|
||||
name: Validation Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-staging, performance-validation, lighthouse-validation, stability-validation, gdpr-validation, bundle-size-validation]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Generate final report
|
||||
run: |
|
||||
echo "# Staging Validation Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Deploy (STAG-001) | ${{ needs.deploy-staging.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Performance (STAG-002) | ${{ needs.performance-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Lighthouse (STAG-003) | ${{ needs.lighthouse-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability (STAG-004) | ${{ needs.stability-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| GDPR (STAG-005) | ${{ needs.gdpr-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Bundle Size (STAG-006) | ${{ needs.bundle-size-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check all passed
|
||||
run: |
|
||||
FAILED=0
|
||||
for result in "${{ needs.performance-validation.result }}" "${{ needs.lighthouse-validation.result }}" "${{ needs.bundle-size-validation.result }}"; do
|
||||
if [ "$result" = "failure" ]; then
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
if [ "$FAILED" = "1" ]; then
|
||||
echo "Some validations failed — see summary above."
|
||||
exit 1
|
||||
fi
|
||||
echo "All critical validations passed!"
|
||||
- name: Check all passed
|
||||
run: |
|
||||
FAILED=0
|
||||
for result in "${{ needs.performance-validation.result }}" "${{ needs.lighthouse-validation.result }}" "${{ needs.bundle-size-validation.result }}"; do
|
||||
if [ "$result" = "failure" ]; then
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
if [ "$FAILED" = "1" ]; then
|
||||
echo "Some validations failed — see summary above."
|
||||
exit 1
|
||||
fi
|
||||
echo "All critical validations passed!"
|
||||
|
|
|
|||
72
.github/workflows/storybook-audit.yml
vendored
72
.github/workflows/storybook-audit.yml
vendored
|
|
@ -4,44 +4,48 @@
|
|||
name: Storybook Audit
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/storybook-audit.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/storybook-audit.yml"
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/storybook-audit.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/storybook-audit.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
name: Build & audit Storybook
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/web
|
||||
audit:
|
||||
name: Build & audit Storybook
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/web
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
steps:
|
||||
- name: Checkout
|
||||
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: apps/web/package-lock.json
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: apps/web/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
run: npx playwright install chromium --with-deps
|
||||
- name: Install Playwright Chromium
|
||||
run: npx playwright install chromium --with-deps
|
||||
|
||||
- name: Validate Storybook (build, serve 6007, audit)
|
||||
run: npm run validate:storybook
|
||||
env:
|
||||
VITE_API_URL: /api/v1
|
||||
VITE_USE_MSW: "true"
|
||||
VITE_STORYBOOK: "true"
|
||||
- name: Validate Storybook (build, serve 6007, audit)
|
||||
run: npm run validate:storybook
|
||||
env:
|
||||
VITE_API_URL: /api/v1
|
||||
VITE_USE_MSW: "true"
|
||||
VITE_STORYBOOK: "true"
|
||||
|
|
|
|||
63
.github/workflows/stream-ci.yml
vendored
63
.github/workflows/stream-ci.yml
vendored
|
|
@ -1,41 +1,44 @@
|
|||
name: Stream Server CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "veza-stream-server/**"
|
||||
- "veza-common/**"
|
||||
- ".github/workflows/stream-ci.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "veza-stream-server/**"
|
||||
- "veza-common/**"
|
||||
- ".github/workflows/stream-ci.yml"
|
||||
push:
|
||||
paths:
|
||||
- "veza-stream-server/**"
|
||||
- "veza-common/**"
|
||||
- ".github/workflows/stream-ci.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "veza-stream-server/**"
|
||||
- "veza-common/**"
|
||||
- ".github/workflows/stream-ci.yml"
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-stream-server
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-stream-server
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Rust
|
||||
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
components: clippy
|
||||
- name: Set up Rust
|
||||
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Lint with clippy
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
- name: Lint with clippy
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
|
||||
- name: Audit dependencies
|
||||
uses: actions-rust-lang/audit@v1 # TODO: pin to SHA — no known mapping provided
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --all
|
||||
- name: Audit dependencies
|
||||
uses: actions-rust-lang/audit@v1 # TODO: pin to SHA — no known mapping provided
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --all
|
||||
|
|
|
|||
45
.github/workflows/trivy-fs.yml
vendored
Normal file
45
.github/workflows/trivy-fs.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
name: Trivy Filesystem Scan
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
trivy-scan:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Run Trivy filesystem scan
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
scan-type: "fs"
|
||||
scan-ref: "."
|
||||
severity: "CRITICAL,HIGH"
|
||||
exit-code: "1"
|
||||
format: "table"
|
||||
|
||||
- name: Run Trivy (SARIF output)
|
||||
if: always()
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
scan-type: "fs"
|
||||
scan-ref: "."
|
||||
severity: "CRITICAL,HIGH"
|
||||
exit-code: "0"
|
||||
format: "sarif"
|
||||
output: "trivy-results.sarif"
|
||||
|
||||
- name: Upload Trivy SARIF
|
||||
if: always()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: trivy-results
|
||||
path: trivy-results.sarif
|
||||
retention-days: 30
|
||||
54
.github/workflows/visual-regression.yml
vendored
Normal file
54
.github/workflows/visual-regression.yml
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
name: Visual Regression
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/visual-regression.yml"
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
visual-regression:
|
||||
name: Lost Pixel visual regression
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
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"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
working-directory: apps/web
|
||||
|
||||
- name: Build Storybook
|
||||
run: npm run build-storybook
|
||||
working-directory: apps/web
|
||||
env:
|
||||
VITE_API_URL: /api/v1
|
||||
VITE_USE_MSW: "true"
|
||||
VITE_STORYBOOK: "true"
|
||||
|
||||
- name: Run Lost Pixel
|
||||
id: lostpixel
|
||||
run: npx lost-pixel
|
||||
working-directory: apps/web
|
||||
|
||||
- name: Upload diff artifacts
|
||||
if: failure() && steps.lostpixel.outcome == 'failure'
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: lost-pixel-diff
|
||||
path: |
|
||||
apps/web/.lostpixel/current/
|
||||
apps/web/.lostpixel/difference/
|
||||
retention-days: 7
|
||||
34
.github/workflows/zap-dast.yml
vendored
Normal file
34
.github/workflows/zap-dast.yml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
name: OWASP ZAP DAST
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 3 * * *" # Nightly at 3am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
zap-baseline:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: ZAP Baseline Scan
|
||||
uses: zaproxy/action-baseline@v0.12.0
|
||||
with:
|
||||
target: ${{ secrets.STAGING_URL || 'http://localhost:5174' }}
|
||||
rules_file_name: .zap/rules.tsv
|
||||
fail_action: false
|
||||
artifact_name: zap-report
|
||||
|
||||
- name: Upload ZAP report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: zap-report
|
||||
path: report_html.html
|
||||
retention-days: 30
|
||||
Loading…
Reference in a new issue