From 83377a7d0d74c5cde666bb294fa1c1fda4627672 Mon Sep 17 00:00:00 2001 From: Jackson Roberts Date: Wed, 8 Apr 2026 20:19:06 +0000 Subject: [PATCH] feat(ci): run dalpuri CW-to-API sync as a k8s Job before deploy The CW MSSQL and API Postgres addresses are internal to the cluster and unreachable from GitHub-hosted runners, so the sync must run inside k8s. - Add dalpuri-sync Docker stage to api/Dockerfile: installs deps, generates both Prisma clients, and runs dalpuri/src/sync.ts - Add dalpuri/kubernetes/sync-job.yaml: mounts api-env-secret (which already contains CW_DATABASE_URL) and maps DATABASE_URL -> API_DATABASE_URL - build-api job now also pushes optima-dalpuri-sync:TAG image - sync-cw-to-api CI job replaced with kubectl apply/wait pattern, needs [build-api, build-worker], blocks deploy-api and deploy-worker --- .github/workflows/deploy.yaml | 64 +++++++++++++++++++++++++++++++- api/Dockerfile | 32 +++++++++++++++- dalpuri/kubernetes/sync-job.yaml | 31 ++++++++++++++++ 3 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 dalpuri/kubernetes/sync-job.yaml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 3c5bdba..4a41831 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -130,6 +130,17 @@ jobs: ghcr.io/horizonstacksoftware/optima-api-migrate:latest ghcr.io/horizonstacksoftware/optima-api-migrate:${{ github.event.release.tag_name }} + - name: Build and push the dalpuri sync image + uses: docker/build-push-action@v6 + with: + context: . + file: api/Dockerfile + push: true + target: dalpuri-sync + tags: | + ghcr.io/horizonstacksoftware/optima-dalpuri-sync:latest + ghcr.io/horizonstacksoftware/optima-dalpuri-sync:${{ github.event.release.tag_name }} + build-worker: name: Build - Worker needs: [test-api, test-dalpuri, test-ui] @@ -276,6 +287,55 @@ jobs: files: | ui/out/make/**/*.exe + # Runs a full CW → API data sync as a Kubernetes Job (the CW MSSQL and + # API Postgres addresses are internal to the cluster and unreachable from + # GitHub-hosted runners). Waits for both images to be built first and + # must succeed before either the API or worker deploys. + sync-cw-to-api: + name: Sync - CW to API + needs: [build-api, build-worker] + runs-on: ubuntu-latest + steps: + - name: Set the Kubernetes context + uses: azure/k8s-set-context@v2 + with: + method: kubeconfig + kubeconfig: ${{ secrets.KUBECONFIG }} + + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Delete previous sync job if exists + run: kubectl delete job -n optima -l app=dalpuri-sync --ignore-not-found + + - name: Apply sync job + run: | + TAG=${{ github.event.release.tag_name }} + sed "s/RELEASE_TAG/${TAG}/g" dalpuri/kubernetes/sync-job.yaml | kubectl apply -f - + + - name: Wait for sync to complete + run: | + TAG=${{ github.event.release.tag_name }} + JOB="job/dalpuri-sync-${TAG}" + + kubectl wait --for=condition=complete --timeout=1800s -n optima "$JOB" & + WAIT_COMPLETE=$! + kubectl wait --for=condition=failed --timeout=1800s -n optima "$JOB" & + WAIT_FAILED=$! + + wait -n $WAIT_COMPLETE $WAIT_FAILED + + echo "--- Sync job logs ---" + kubectl logs -n optima "$JOB" --tail=500 || true + + if kubectl get -n optima "$JOB" -o jsonpath='{.status.conditions[?(@.type=="Complete")].status}' | grep -q "True"; then + echo "Sync completed successfully." + exit 0 + else + echo "Sync FAILED." + exit 1 + fi + # ========================================================================== # Deploy jobs # ========================================================================== @@ -332,7 +392,7 @@ jobs: deploy-api: name: Deploy - API - needs: [migrate-api] + needs: [migrate-api, sync-cw-to-api] runs-on: ubuntu-latest steps: - name: Set the Kubernetes context @@ -402,7 +462,7 @@ jobs: deploy-worker: name: Deploy - Worker - needs: [build-worker] + needs: [build-worker, sync-cw-to-api] runs-on: ubuntu-latest steps: - name: Set the Kubernetes context diff --git a/api/Dockerfile b/api/Dockerfile index 85efda5..cdac97e 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -133,4 +133,34 @@ COPY api/prisma.config.ts ./api/prisma.config.ts RUN chmod +x /app/api/prisma/migrate-entrypoint.sh WORKDIR /app/api -CMD ["sh", "prisma/migrate-entrypoint.sh"] \ No newline at end of file +CMD ["sh", "prisma/migrate-entrypoint.sh"] + +# ---- Stage 7: Dalpuri CW-to-API sync runner ---- +FROM oven/bun:1.3.11 AS dalpuri-sync + +WORKDIR /app + +COPY package.json bun.lock ./ +COPY api/package.json ./api/package.json +COPY dalpuri/package.json ./dalpuri/package.json +COPY ui/package.json ./ui/package.json +COPY patches ./patches + +RUN bun install --frozen-lockfile + +COPY dalpuri/src/ ./dalpuri/src/ +COPY dalpuri/prisma/ ./dalpuri/prisma/ +COPY dalpuri/prisma.config.ts ./dalpuri/prisma.config.ts + +COPY api/prisma/ ./api/prisma/ +COPY api/prisma.config.ts ./api/prisma.config.ts + +WORKDIR /app/dalpuri +RUN DATABASE_URL="sqlserver://localhost:1433;database=dummy;user=dummy;password=dummy;trustServerCertificate=true" \ + bunx prisma generate + +WORKDIR /app/api +RUN DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" bunx prisma generate + +WORKDIR /app/dalpuri +CMD ["bun", "run", "src/sync.ts"] \ No newline at end of file diff --git a/dalpuri/kubernetes/sync-job.yaml b/dalpuri/kubernetes/sync-job.yaml new file mode 100644 index 0000000..6d2a837 --- /dev/null +++ b/dalpuri/kubernetes/sync-job.yaml @@ -0,0 +1,31 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: dalpuri-sync-RELEASE_TAG + namespace: optima + labels: + app: dalpuri-sync +spec: + backoffLimit: 0 + ttlSecondsAfterFinished: 86400 + activeDeadlineSeconds: 1800 + template: + metadata: + labels: + app: dalpuri-sync + spec: + containers: + - name: sync + image: ghcr.io/horizonstacksoftware/optima-dalpuri-sync:RELEASE_TAG + envFrom: + - secretRef: + name: api-env-secret + env: + - name: API_DATABASE_URL + valueFrom: + secretKeyRef: + name: api-env-secret + key: DATABASE_URL + restartPolicy: Never + imagePullSecrets: + - name: github-container-registry