diff --git a/.woodpecker/api.yml b/.woodpecker/api.yml deleted file mode 100644 index c283861..0000000 --- a/.woodpecker/api.yml +++ /dev/null @@ -1,232 +0,0 @@ -# API Pipeline - Mosaic Stack -# Quality gates, build, and Docker publish for @mosaic/api -# -# Triggers on: apps/api/**, packages/**, root configs -# Security chain: source audit + Trivy container scan - -when: - - event: [push, pull_request, manual] - path: - include: - - "apps/api/**" - - "packages/**" - - "pnpm-lock.yaml" - - "pnpm-workspace.yaml" - - "turbo.json" - - "package.json" - - ".woodpecker/api.yml" - - ".trivyignore" - -variables: - - &node_image "node:24-alpine" - - &install_deps | - corepack enable - 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: - # === Quality Gates === - - install: - image: *node_image - commands: - - *install_deps - - security-audit: - image: *node_image - commands: - - *use_deps - - pnpm audit --audit-level=high - depends_on: - - install - - prisma-generate: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - commands: - - *use_deps - - pnpm --filter "@mosaic/api" prisma:generate - depends_on: - - install - - lint: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo lint --filter=@mosaic/api - depends_on: - - prisma-generate - - typecheck: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo typecheck --filter=@mosaic/api - depends_on: - - prisma-generate - - 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: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db?schema=public" - ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - 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' - depends_on: - - prisma-migrate - - # === Build === - - build: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - NODE_ENV: "production" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo build --filter=@mosaic/api - depends_on: - - lint - - typecheck - - test - - security-audit - - # === Docker Build & Push === - - 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 - - # === Container Security Scan === - - 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" - elif [ "$$CI_COMMIT_BRANCH" = "main" ]; then - SCAN_TAG="latest" - 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 - - # === Package Linking === - - 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" - when: - - branch: [main] - event: [push, manual, tag] - depends_on: - - security-trivy-api diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml new file mode 100644 index 0000000..9f0c34b --- /dev/null +++ b/.woodpecker/ci.yml @@ -0,0 +1,337 @@ +# 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 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 diff --git a/.woodpecker/orchestrator.yml b/.woodpecker/orchestrator.yml deleted file mode 100644 index 903f9c8..0000000 --- a/.woodpecker/orchestrator.yml +++ /dev/null @@ -1,202 +0,0 @@ -# Orchestrator Pipeline - Mosaic Stack -# Quality gates, build, and Docker publish for @mosaic/orchestrator -# -# Triggers on: apps/orchestrator/**, packages/**, root configs -# Security chain: source audit + Trivy container scan - -when: - - event: [push, pull_request, manual] - path: - include: - - "apps/orchestrator/**" - - "packages/**" - - "pnpm-lock.yaml" - - "pnpm-workspace.yaml" - - "turbo.json" - - "package.json" - - ".woodpecker/orchestrator.yml" - - ".trivyignore" - -variables: - - &node_image "node:24-alpine" - - &install_deps | - corepack enable - 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 - -steps: - # === Quality Gates === - - install: - image: *node_image - commands: - - *install_deps - - security-audit: - image: *node_image - commands: - - *use_deps - - pnpm audit --audit-level=high - depends_on: - - install - - lint: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo lint --filter=@mosaic/orchestrator - depends_on: - - install - - typecheck: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo typecheck --filter=@mosaic/orchestrator - depends_on: - - install - - test: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo test --filter=@mosaic/orchestrator - depends_on: - - install - - # === Build === - - build: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - NODE_ENV: "production" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo build --filter=@mosaic/orchestrator - depends_on: - - lint - - typecheck - - test - - security-audit - - # === Docker Build & Push === - - 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 - - # === Container Security Scan === - - 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" - elif [ "$$CI_COMMIT_BRANCH" = "main" ]; then - SCAN_TAG="latest" - 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 - - # === Package Linking === - - 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-orchestrator" - when: - - branch: [main] - event: [push, manual, tag] - depends_on: - - security-trivy-orchestrator diff --git a/.woodpecker/web.yml b/.woodpecker/web.yml deleted file mode 100644 index c43a22a..0000000 --- a/.woodpecker/web.yml +++ /dev/null @@ -1,202 +0,0 @@ -# Web Pipeline - Mosaic Stack -# Quality gates, build, and Docker publish for @mosaic/web -# -# Triggers on: apps/web/**, packages/**, root configs -# Security chain: source audit + Trivy container scan - -when: - - event: [push, pull_request, manual] - path: - include: - - "apps/web/**" - - "packages/**" - - "pnpm-lock.yaml" - - "pnpm-workspace.yaml" - - "turbo.json" - - "package.json" - - ".woodpecker/web.yml" - - ".trivyignore" - -variables: - - &node_image "node:24-alpine" - - &install_deps | - corepack enable - 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 - -steps: - # === Quality Gates === - - install: - image: *node_image - commands: - - *install_deps - - security-audit: - image: *node_image - commands: - - *use_deps - - pnpm audit --audit-level=high - depends_on: - - install - - lint: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo lint --filter=@mosaic/web - depends_on: - - install - - typecheck: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo typecheck --filter=@mosaic/web - depends_on: - - install - - test: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo test --filter=@mosaic/web - depends_on: - - install - - # === Build === - - build: - image: *node_image - environment: - SKIP_ENV_VALIDATION: "true" - NODE_ENV: "production" - <<: *turbo_env - commands: - - *use_deps - - pnpm turbo build --filter=@mosaic/web - depends_on: - - lint - - typecheck - - test - - security-audit - - # === Docker Build & Push === - - 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 Scan === - - 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" - elif [ "$$CI_COMMIT_BRANCH" = "main" ]; then - SCAN_TAG="latest" - 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 === - - 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-web" - when: - - branch: [main] - event: [push, manual, tag] - depends_on: - - security-trivy-web