256deb70c7
Build + Deploy ran in parallel with CI's lint/test/loc, so a deploy could ship even when CI failed. Gate Build + Deploy on CI success via workflow_run, and add per-service change detection so only affected services rebuild and only relevant lint/test jobs run on PRs. - scripts/detect-changes.sh: shared diff helper that emits per-service + aggregate flags from a BASE_SHA diff; falls back to "rebuild all" when the base is missing or unreachable - ci.yaml: detect-changes job runs first; loc-budget, *-lint, *-build, and test-* jobs gate on the relevant outputs - build-push-deploy.yml: triggered via workflow_run on CI completion; diff base is the last-build/main git tag, force-pushed by a new mark-last-build job after each green run (handles multi-commit pushes, force pushes, and the "all skipped" case) - check-loc.sh: exclude Office/binary extensions (xlsm, docx, pptx, zip, tar, gz) so binary docs aren't counted as source - loc-exceptions.txt: grandfather two existing >500 LOC files (tender_handlers.go, DecisionTreeWizard.tsx) as Phase 5+ backlog Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
360 lines
15 KiB
YAML
360 lines
15 KiB
YAML
# Build + push compliance service images to registry.meghsakha.com
|
|
# and trigger orca redeploy after CI passes on main.
|
|
#
|
|
# This workflow is gated on the CI workflow completing successfully.
|
|
# It does not run independently — if CI fails, builds + deploy are skipped.
|
|
# Per-service builds are gated on detect-changes so only services with
|
|
# modified files are rebuilt; trigger-orca runs only if at least one build
|
|
# succeeded and none failed.
|
|
#
|
|
# Requires Gitea Actions secrets:
|
|
# REGISTRY_USERNAME / REGISTRY_PASSWORD — registry.meghsakha.com credentials
|
|
# ORCA_WEBHOOK_SECRET — must match webhooks.json on orca master
|
|
|
|
name: Build + Deploy
|
|
|
|
on:
|
|
workflow_run:
|
|
workflows: ["CI"]
|
|
types: [completed]
|
|
branches: [main]
|
|
|
|
jobs:
|
|
# ── gate: only proceed if CI succeeded ────────────────────────────────────
|
|
ci-passed:
|
|
runs-on: docker
|
|
container: alpine:3.20
|
|
if: github.event.workflow_run.conclusion == 'success'
|
|
steps:
|
|
- name: CI passed, proceeding with build + deploy
|
|
run: echo "CI run ${{ github.event.workflow_run.id }} succeeded on ${{ github.event.workflow_run.head_branch }} @ ${{ github.event.workflow_run.head_sha }}"
|
|
|
|
# ── detect which services changed since the last successful build ────────
|
|
# Diff base = the last-build/main git tag, set by mark-last-build at the
|
|
# end of every successful run. Works across squash merges, multi-commit
|
|
# raw pushes, and force pushes (force pushes leave a stale tag → diff
|
|
# shows symmetric differences → safe over-rebuild). If the tag doesn't
|
|
# exist yet, scripts/detect-changes.sh falls back to rebuilding all.
|
|
detect-changes:
|
|
runs-on: docker
|
|
container: alpine:3.20
|
|
needs: ci-passed
|
|
outputs:
|
|
admin: ${{ steps.diff.outputs.admin }}
|
|
backend: ${{ steps.diff.outputs.backend }}
|
|
sdk: ${{ steps.diff.outputs.sdk }}
|
|
portal: ${{ steps.diff.outputs.portal }}
|
|
tts: ${{ steps.diff.outputs.tts }}
|
|
crawler: ${{ steps.diff.outputs.crawler }}
|
|
dsms_gateway: ${{ steps.diff.outputs.dsms_gateway }}
|
|
dsms_node: ${{ steps.diff.outputs.dsms_node }}
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git bash
|
|
git clone --depth 200 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
git fetch --tags origin || true
|
|
- name: Resolve base SHA from last-build/main tag
|
|
run: |
|
|
BASE=$(git rev-parse --verify refs/tags/last-build/main 2>/dev/null || true)
|
|
echo "Base SHA: ${BASE:-<none, will rebuild all>}"
|
|
# Deepen if base isn't yet in the shallow clone.
|
|
if [ -n "$BASE" ] && ! git rev-parse --verify "${BASE}^{commit}" >/dev/null 2>&1; then
|
|
git fetch --unshallow origin 2>/dev/null \
|
|
|| git fetch --depth=10000 origin 2>/dev/null \
|
|
|| true
|
|
fi
|
|
echo "BASE_SHA=${BASE}" >> "$GITHUB_ENV"
|
|
- name: Detect changes
|
|
id: diff
|
|
run: bash scripts/detect-changes.sh
|
|
|
|
# ── per-service builds run in parallel (only changed services) ────────────
|
|
|
|
build-admin-compliance:
|
|
runs-on: docker
|
|
container: docker:27-cli
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.admin == 'true'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Login
|
|
env:
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: echo "$REGISTRY_PASSWORD" | docker login registry.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
|
|
- name: Build + push
|
|
run: |
|
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
docker build \
|
|
-t registry.meghsakha.com/breakpilot/compliance-admin:latest \
|
|
-t registry.meghsakha.com/breakpilot/compliance-admin:${SHORT_SHA} \
|
|
admin-compliance/
|
|
docker push registry.meghsakha.com/breakpilot/compliance-admin:latest
|
|
docker push registry.meghsakha.com/breakpilot/compliance-admin:${SHORT_SHA}
|
|
|
|
build-backend-compliance:
|
|
runs-on: docker
|
|
container: docker:27-cli
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.backend == 'true'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Login
|
|
env:
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: echo "$REGISTRY_PASSWORD" | docker login registry.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
|
|
- name: Build + push
|
|
run: |
|
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
docker build \
|
|
-t registry.meghsakha.com/breakpilot/compliance-backend:latest \
|
|
-t registry.meghsakha.com/breakpilot/compliance-backend:${SHORT_SHA} \
|
|
backend-compliance/
|
|
docker push registry.meghsakha.com/breakpilot/compliance-backend:latest
|
|
docker push registry.meghsakha.com/breakpilot/compliance-backend:${SHORT_SHA}
|
|
|
|
build-ai-sdk:
|
|
runs-on: docker
|
|
container: docker:27-cli
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.sdk == 'true'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Login
|
|
env:
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: echo "$REGISTRY_PASSWORD" | docker login registry.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
|
|
- name: Build + push
|
|
run: |
|
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
docker build \
|
|
-t registry.meghsakha.com/breakpilot/compliance-sdk:latest \
|
|
-t registry.meghsakha.com/breakpilot/compliance-sdk:${SHORT_SHA} \
|
|
ai-compliance-sdk/
|
|
docker push registry.meghsakha.com/breakpilot/compliance-sdk:latest
|
|
docker push registry.meghsakha.com/breakpilot/compliance-sdk:${SHORT_SHA}
|
|
|
|
build-developer-portal:
|
|
runs-on: docker
|
|
container: docker:27-cli
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.portal == 'true'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Login
|
|
env:
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: echo "$REGISTRY_PASSWORD" | docker login registry.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
|
|
- name: Build + push
|
|
run: |
|
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
docker build \
|
|
-t registry.meghsakha.com/breakpilot/compliance-portal:latest \
|
|
-t registry.meghsakha.com/breakpilot/compliance-portal:${SHORT_SHA} \
|
|
developer-portal/
|
|
docker push registry.meghsakha.com/breakpilot/compliance-portal:latest
|
|
docker push registry.meghsakha.com/breakpilot/compliance-portal:${SHORT_SHA}
|
|
|
|
build-tts:
|
|
runs-on: docker
|
|
container: docker:27-cli
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.tts == 'true'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Login
|
|
env:
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: echo "$REGISTRY_PASSWORD" | docker login registry.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
|
|
- name: Build + push
|
|
run: |
|
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
docker build \
|
|
-t registry.meghsakha.com/breakpilot/compliance-tts:latest \
|
|
-t registry.meghsakha.com/breakpilot/compliance-tts:${SHORT_SHA} \
|
|
compliance-tts-service/
|
|
docker push registry.meghsakha.com/breakpilot/compliance-tts:latest
|
|
docker push registry.meghsakha.com/breakpilot/compliance-tts:${SHORT_SHA}
|
|
|
|
build-document-crawler:
|
|
runs-on: docker
|
|
container: docker:27-cli
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.crawler == 'true'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Login
|
|
env:
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: echo "$REGISTRY_PASSWORD" | docker login registry.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
|
|
- name: Build + push
|
|
run: |
|
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
docker build \
|
|
-t registry.meghsakha.com/breakpilot/compliance-crawler:latest \
|
|
-t registry.meghsakha.com/breakpilot/compliance-crawler:${SHORT_SHA} \
|
|
document-crawler/
|
|
docker push registry.meghsakha.com/breakpilot/compliance-crawler:latest
|
|
docker push registry.meghsakha.com/breakpilot/compliance-crawler:${SHORT_SHA}
|
|
|
|
build-dsms-gateway:
|
|
runs-on: docker
|
|
container: docker:27-cli
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.dsms_gateway == 'true'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Login
|
|
env:
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: echo "$REGISTRY_PASSWORD" | docker login registry.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
|
|
- name: Build + push
|
|
run: |
|
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
docker build \
|
|
-t registry.meghsakha.com/breakpilot/compliance-dsms-gateway:latest \
|
|
-t registry.meghsakha.com/breakpilot/compliance-dsms-gateway:${SHORT_SHA} \
|
|
dsms-gateway/
|
|
docker push registry.meghsakha.com/breakpilot/compliance-dsms-gateway:latest
|
|
docker push registry.meghsakha.com/breakpilot/compliance-dsms-gateway:${SHORT_SHA}
|
|
|
|
build-dsms-node:
|
|
runs-on: docker
|
|
container: docker:27-cli
|
|
needs: detect-changes
|
|
if: needs.detect-changes.outputs.dsms_node == 'true'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Login
|
|
env:
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: echo "$REGISTRY_PASSWORD" | docker login registry.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
|
|
- name: Build + push
|
|
run: |
|
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
docker build --platform linux/amd64 \
|
|
-t registry.meghsakha.com/breakpilot/compliance-dsms-node:latest \
|
|
-t registry.meghsakha.com/breakpilot/compliance-dsms-node:${SHORT_SHA} \
|
|
dsms-node/
|
|
docker push registry.meghsakha.com/breakpilot/compliance-dsms-node:latest
|
|
docker push registry.meghsakha.com/breakpilot/compliance-dsms-node:${SHORT_SHA}
|
|
|
|
# ── advance the last-build/main tag — the diff base for future runs ──────
|
|
# Runs when no build failed. Covers two cases:
|
|
# - at least one service was rebuilt → mark this SHA as the new baseline
|
|
# - all services were skipped (nothing changed) → still advance the tag
|
|
# so we don't keep re-evaluating the same skipped commits forever
|
|
# Skips if any build failed → tag stays put → next push retries those
|
|
# services from the previous known-good base.
|
|
mark-last-build:
|
|
runs-on: docker
|
|
container: alpine:3.20
|
|
needs:
|
|
- build-admin-compliance
|
|
- build-backend-compliance
|
|
- build-ai-sdk
|
|
- build-developer-portal
|
|
- build-tts
|
|
- build-document-crawler
|
|
- build-dsms-gateway
|
|
- build-dsms-node
|
|
if: |
|
|
always() &&
|
|
!contains(needs.*.result, 'failure') &&
|
|
!contains(needs.*.result, 'cancelled')
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Force-push last-build/main tag
|
|
run: |
|
|
set -e
|
|
SHA="${HEAD_SHA:-$(git rev-parse HEAD)}"
|
|
echo "Advancing last-build/main → ${SHA}"
|
|
git tag -f last-build/main "$SHA"
|
|
# Encode token into the push URL (no on-disk credential persistence).
|
|
PUSH_URL="${GITHUB_SERVER_URL/https:\/\//https:\/\/x-access-token:${GITEA_TOKEN}@}/${GITHUB_REPOSITORY}.git"
|
|
git push --force "$PUSH_URL" "refs/tags/last-build/main"
|
|
echo "Tag last-build/main now at ${SHA}"
|
|
|
|
# ── orca redeploy — runs only if at least one build succeeded ─────────────
|
|
# `always()` lets this run when some builds are skipped (unchanged services).
|
|
# The contains() checks ensure we only redeploy when something actually built
|
|
# and no build failed.
|
|
|
|
trigger-orca:
|
|
runs-on: docker
|
|
container: docker:27-cli
|
|
needs:
|
|
- build-admin-compliance
|
|
- build-backend-compliance
|
|
- build-ai-sdk
|
|
- build-developer-portal
|
|
- build-tts
|
|
- build-document-crawler
|
|
- build-dsms-gateway
|
|
- build-dsms-node
|
|
if: |
|
|
always() &&
|
|
contains(needs.*.result, 'success') &&
|
|
!contains(needs.*.result, 'failure') &&
|
|
!contains(needs.*.result, 'cancelled')
|
|
steps:
|
|
- name: Checkout (for SHA)
|
|
run: |
|
|
apk add --no-cache git curl openssl
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Trigger orca redeploy
|
|
env:
|
|
ORCA_WEBHOOK_SECRET: ${{ secrets.ORCA_WEBHOOK_SECRET }}
|
|
ORCA_WEBHOOK_URL: http://46.225.100.82:6880/api/v1/webhooks/github
|
|
run: |
|
|
SHA=$(git rev-parse HEAD)
|
|
PAYLOAD="{\"ref\":\"refs/heads/main\",\"repository\":{\"full_name\":\"${GITHUB_REPOSITORY}\"},\"head_commit\":{\"id\":\"$SHA\",\"message\":\"ci: compliance images built\"}}"
|
|
SIG=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$ORCA_WEBHOOK_SECRET" -r | awk '{print $1}')
|
|
curl -sSf -k \
|
|
-X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-GitHub-Event: push" \
|
|
-H "X-Hub-Signature-256: sha256=$SIG" \
|
|
-d "$PAYLOAD" \
|
|
"$ORCA_WEBHOOK_URL" \
|
|
|| { echo "Orca redeploy failed"; exit 1; }
|
|
echo "Orca redeploy triggered for compliance services"
|