339 lines
11 KiB
YAML
339 lines
11 KiB
YAML
# Unified CI Pipeline - Mosaic Stack
|
|
# Single install, parallel quality gates, sequential deploy
|
|
#
|
|
# Replaces: api.yml, orchestrator.yml, web.yml
|
|
# Keeps: coordinator.yml (Python), infra.yml (separate concerns)
|
|
#
|
|
# Flow:
|
|
# install → security-audit
|
|
# → prisma-generate → lint + typecheck (parallel)
|
|
# → prisma-migrate → test
|
|
# → build (after all gates pass)
|
|
# → docker builds (main only, parallel)
|
|
# → trivy scans (main only, parallel)
|
|
# → package linking (main only)
|
|
|
|
when:
|
|
- event: [push, pull_request, manual]
|
|
path:
|
|
include:
|
|
- "apps/api/**"
|
|
- "apps/orchestrator/**"
|
|
- "apps/web/**"
|
|
- "packages/**"
|
|
- "pnpm-lock.yaml"
|
|
- "pnpm-workspace.yaml"
|
|
- "turbo.json"
|
|
- "package.json"
|
|
- ".woodpecker/ci.yml"
|
|
- ".trivyignore"
|
|
|
|
variables:
|
|
- &node_image "node:24-alpine"
|
|
- &install_deps |
|
|
corepack enable
|
|
pnpm config set store-dir /root/.local/share/pnpm/store
|
|
pnpm install --frozen-lockfile
|
|
- &use_deps |
|
|
corepack enable
|
|
- &turbo_env
|
|
TURBO_API:
|
|
from_secret: turbo_api
|
|
TURBO_TOKEN:
|
|
from_secret: turbo_token
|
|
TURBO_TEAM:
|
|
from_secret: turbo_team
|
|
- &kaniko_setup |
|
|
mkdir -p /kaniko/.docker
|
|
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:17.7-alpine3.22
|
|
environment:
|
|
POSTGRES_DB: test_db
|
|
POSTGRES_USER: test_user
|
|
POSTGRES_PASSWORD: test_password
|
|
|
|
steps:
|
|
# ─── Install (once) ─────────────────────────────────────────
|
|
install:
|
|
image: *node_image
|
|
commands:
|
|
- *install_deps
|
|
|
|
# ─── Security Audit (once) ──────────────────────────────────
|
|
security-audit:
|
|
image: *node_image
|
|
commands:
|
|
- *use_deps
|
|
- pnpm audit --audit-level=high
|
|
depends_on:
|
|
- install
|
|
|
|
# ─── Prisma Generate ────────────────────────────────────────
|
|
prisma-generate:
|
|
image: *node_image
|
|
environment:
|
|
SKIP_ENV_VALIDATION: "true"
|
|
commands:
|
|
- *use_deps
|
|
- pnpm --filter "@mosaic/api" prisma:generate
|
|
depends_on:
|
|
- install
|
|
|
|
# ─── Lint (all packages) ────────────────────────────────────
|
|
lint:
|
|
image: *node_image
|
|
environment:
|
|
SKIP_ENV_VALIDATION: "true"
|
|
<<: *turbo_env
|
|
commands:
|
|
- *use_deps
|
|
- pnpm turbo lint
|
|
depends_on:
|
|
- prisma-generate
|
|
|
|
# ─── Typecheck (all packages, parallel with lint) ───────────
|
|
typecheck:
|
|
image: *node_image
|
|
environment:
|
|
SKIP_ENV_VALIDATION: "true"
|
|
<<: *turbo_env
|
|
commands:
|
|
- *use_deps
|
|
- pnpm turbo typecheck
|
|
depends_on:
|
|
- prisma-generate
|
|
|
|
# ─── Prisma Migrate (test DB) ──────────────────────────────
|
|
prisma-migrate:
|
|
image: *node_image
|
|
environment:
|
|
SKIP_ENV_VALIDATION: "true"
|
|
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db?schema=public"
|
|
commands:
|
|
- *use_deps
|
|
- pnpm --filter "@mosaic/api" prisma migrate deploy
|
|
depends_on:
|
|
- prisma-generate
|
|
|
|
# ─── Test (all packages) ───────────────────────────────────
|
|
test:
|
|
image: *node_image
|
|
environment:
|
|
SKIP_ENV_VALIDATION: "true"
|
|
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db?schema=public"
|
|
ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
<<: *turbo_env
|
|
commands:
|
|
- *use_deps
|
|
- pnpm --filter "@mosaic/api" exec vitest run --exclude 'src/auth/auth-rls.integration.spec.ts' --exclude 'src/credentials/user-credential.model.spec.ts' --exclude 'src/job-events/job-events.performance.spec.ts' --exclude 'src/knowledge/services/fulltext-search.spec.ts' --exclude 'src/mosaic-telemetry/mosaic-telemetry.module.spec.ts'
|
|
- pnpm turbo test --filter=@mosaic/orchestrator --filter=@mosaic/web
|
|
depends_on:
|
|
- prisma-migrate
|
|
|
|
# ─── Build (all packages) ──────────────────────────────────
|
|
build:
|
|
image: *node_image
|
|
environment:
|
|
SKIP_ENV_VALIDATION: "true"
|
|
NODE_ENV: "production"
|
|
<<: *turbo_env
|
|
commands:
|
|
- *use_deps
|
|
- pnpm turbo build
|
|
depends_on:
|
|
- lint
|
|
- typecheck
|
|
- test
|
|
- security-audit
|
|
|
|
# ─── Docker Builds (main only, parallel) ───────────────────
|
|
|
|
docker-build-api:
|
|
image: gcr.io/kaniko-project/executor:debug
|
|
environment:
|
|
GITEA_USER:
|
|
from_secret: gitea_username
|
|
GITEA_TOKEN:
|
|
from_secret: gitea_token
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
commands:
|
|
- *kaniko_setup
|
|
- |
|
|
DESTINATIONS=""
|
|
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-api:$CI_COMMIT_TAG"
|
|
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-api:latest"
|
|
fi
|
|
/kaniko/executor --context . --dockerfile apps/api/Dockerfile --snapshot-mode=redo $DESTINATIONS
|
|
when:
|
|
- branch: [main]
|
|
event: [push, manual, tag]
|
|
depends_on:
|
|
- build
|
|
|
|
docker-build-orchestrator:
|
|
image: gcr.io/kaniko-project/executor:debug
|
|
environment:
|
|
GITEA_USER:
|
|
from_secret: gitea_username
|
|
GITEA_TOKEN:
|
|
from_secret: gitea_token
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
commands:
|
|
- *kaniko_setup
|
|
- |
|
|
DESTINATIONS=""
|
|
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-orchestrator:$CI_COMMIT_TAG"
|
|
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-orchestrator:latest"
|
|
fi
|
|
/kaniko/executor --context . --dockerfile apps/orchestrator/Dockerfile --snapshot-mode=redo $DESTINATIONS
|
|
when:
|
|
- branch: [main]
|
|
event: [push, manual, tag]
|
|
depends_on:
|
|
- build
|
|
|
|
docker-build-web:
|
|
image: gcr.io/kaniko-project/executor:debug
|
|
environment:
|
|
GITEA_USER:
|
|
from_secret: gitea_username
|
|
GITEA_TOKEN:
|
|
from_secret: gitea_token
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
commands:
|
|
- *kaniko_setup
|
|
- |
|
|
DESTINATIONS=""
|
|
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-web:$CI_COMMIT_TAG"
|
|
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-web:latest"
|
|
fi
|
|
/kaniko/executor --context . --dockerfile apps/web/Dockerfile --snapshot-mode=redo --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS
|
|
when:
|
|
- branch: [main]
|
|
event: [push, manual, tag]
|
|
depends_on:
|
|
- build
|
|
|
|
# ─── Container Security Scans (main only) ──────────────────
|
|
|
|
security-trivy-api:
|
|
image: aquasec/trivy:latest
|
|
environment:
|
|
GITEA_USER:
|
|
from_secret: gitea_username
|
|
GITEA_TOKEN:
|
|
from_secret: gitea_token
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
commands:
|
|
- |
|
|
if [ -n "$$CI_COMMIT_TAG" ]; then SCAN_TAG="$$CI_COMMIT_TAG"; else SCAN_TAG="latest"; fi
|
|
mkdir -p ~/.docker
|
|
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
|
|
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --ignorefile .trivyignore git.mosaicstack.dev/mosaic/stack-api:$$SCAN_TAG
|
|
when:
|
|
- branch: [main]
|
|
event: [push, manual, tag]
|
|
depends_on:
|
|
- docker-build-api
|
|
|
|
security-trivy-orchestrator:
|
|
image: aquasec/trivy:latest
|
|
environment:
|
|
GITEA_USER:
|
|
from_secret: gitea_username
|
|
GITEA_TOKEN:
|
|
from_secret: gitea_token
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
commands:
|
|
- |
|
|
if [ -n "$$CI_COMMIT_TAG" ]; then SCAN_TAG="$$CI_COMMIT_TAG"; else SCAN_TAG="latest"; fi
|
|
mkdir -p ~/.docker
|
|
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
|
|
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --ignorefile .trivyignore git.mosaicstack.dev/mosaic/stack-orchestrator:$$SCAN_TAG
|
|
when:
|
|
- branch: [main]
|
|
event: [push, manual, tag]
|
|
depends_on:
|
|
- docker-build-orchestrator
|
|
|
|
security-trivy-web:
|
|
image: aquasec/trivy:latest
|
|
environment:
|
|
GITEA_USER:
|
|
from_secret: gitea_username
|
|
GITEA_TOKEN:
|
|
from_secret: gitea_token
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
commands:
|
|
- |
|
|
if [ -n "$$CI_COMMIT_TAG" ]; then SCAN_TAG="$$CI_COMMIT_TAG"; else SCAN_TAG="latest"; fi
|
|
mkdir -p ~/.docker
|
|
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
|
|
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --ignorefile .trivyignore git.mosaicstack.dev/mosaic/stack-web:$$SCAN_TAG
|
|
when:
|
|
- branch: [main]
|
|
event: [push, manual, tag]
|
|
depends_on:
|
|
- docker-build-web
|
|
|
|
# ─── Package Linking (main only, once) ─────────────────────
|
|
|
|
link-packages:
|
|
image: alpine:3
|
|
environment:
|
|
GITEA_TOKEN:
|
|
from_secret: gitea_token
|
|
commands:
|
|
- apk add --no-cache curl
|
|
- sleep 10
|
|
- |
|
|
set -e
|
|
link_package() {
|
|
PKG="$$1"
|
|
echo "Linking $$PKG..."
|
|
for attempt in 1 2 3; do
|
|
STATUS=$$(curl -s -o /tmp/link-response.txt -w "%{http_code}" -X POST \
|
|
-H "Authorization: token $$GITEA_TOKEN" \
|
|
"https://git.mosaicstack.dev/api/v1/packages/mosaic/container/$$PKG/-/link/stack")
|
|
if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ]; then
|
|
echo " Linked $$PKG"
|
|
return 0
|
|
elif [ "$$STATUS" = "400" ]; then
|
|
echo " $$PKG already linked"
|
|
return 0
|
|
elif [ "$$STATUS" = "404" ] && [ $$attempt -lt 3 ]; then
|
|
echo " $$PKG not found yet, retrying in 5s (attempt $$attempt/3)..."
|
|
sleep 5
|
|
else
|
|
echo " FAILED: $$PKG status $$STATUS"
|
|
cat /tmp/link-response.txt
|
|
return 1
|
|
fi
|
|
done
|
|
}
|
|
link_package "stack-api"
|
|
link_package "stack-orchestrator"
|
|
link_package "stack-web"
|
|
when:
|
|
- branch: [main]
|
|
event: [push, manual, tag]
|
|
depends_on:
|
|
- security-trivy-api
|
|
- security-trivy-orchestrator
|
|
- security-trivy-web
|