Compare commits

..

3 Commits

Author SHA1 Message Date
HoloPanio d9a431d99a fix(ci): sync-cw-to-api must wait for migrate-api to complete
Migration must finish before sync runs so the schema exists.
2026-04-08 20:27:05 +00:00
HoloPanio 83377a7d0d 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
2026-04-08 20:19:06 +00:00
HoloPanio a81618007c fix(worker): pass socket to enqueueDalpuriFullSync
The socket retrieved from ensureManagerSocketReady() was never passed to
enqueueDalpuriFullSync(), so inside createWorkerJob the socket.emit('requestId')
call crashed with 'TypeError: undefined is not an object (evaluating A.emit)'.

This caused every full sync job to fail immediately, leaving the DB empty.
The 5s incremental sync interval then flooded the queue with 4700+ jobs that
all failed too since there was no data.

Also manually cleared the backlog of 4720 failed/pending incremental jobs and
2 failed full sync jobs from the production queue.
2026-04-08 19:34:33 +00:00
4 changed files with 125 additions and 4 deletions
+62 -2
View File
@@ -130,6 +130,17 @@ jobs:
ghcr.io/horizonstacksoftware/optima-api-migrate:latest ghcr.io/horizonstacksoftware/optima-api-migrate:latest
ghcr.io/horizonstacksoftware/optima-api-migrate:${{ github.event.release.tag_name }} 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: build-worker:
name: Build - Worker name: Build - Worker
needs: [test-api, test-dalpuri, test-ui] needs: [test-api, test-dalpuri, test-ui]
@@ -276,6 +287,55 @@ jobs:
files: | files: |
ui/out/make/**/*.exe 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: [migrate-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 # Deploy jobs
# ========================================================================== # ==========================================================================
@@ -332,7 +392,7 @@ jobs:
deploy-api: deploy-api:
name: Deploy - API name: Deploy - API
needs: [migrate-api] needs: [migrate-api, sync-cw-to-api]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set the Kubernetes context - name: Set the Kubernetes context
@@ -402,7 +462,7 @@ jobs:
deploy-worker: deploy-worker:
name: Deploy - Worker name: Deploy - Worker
needs: [build-worker] needs: [build-worker, sync-cw-to-api]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set the Kubernetes context - name: Set the Kubernetes context
+31 -1
View File
@@ -133,4 +133,34 @@ COPY api/prisma.config.ts ./api/prisma.config.ts
RUN chmod +x /app/api/prisma/migrate-entrypoint.sh RUN chmod +x /app/api/prisma/migrate-entrypoint.sh
WORKDIR /app/api WORKDIR /app/api
CMD ["sh", "prisma/migrate-entrypoint.sh"] 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"]
+1 -1
View File
@@ -165,7 +165,7 @@ if (import.meta.main) {
const { executeIncrementalSync } = await import("./modules/workers/dalpuri-sync"); const { executeIncrementalSync } = await import("./modules/workers/dalpuri-sync");
await boss.work(WorkerQueue.DALPURI_FULL_SYNC, async () => { await boss.work(WorkerQueue.DALPURI_FULL_SYNC, async () => {
const socket = await ensureManagerSocketReady(); const socket = await ensureManagerSocketReady();
await enqueueDalpuriFullSync(); await enqueueDalpuriFullSync(socket);
}); });
console.log("[worker] Registered DALPURI_FULL_SYNC job handler"); console.log("[worker] Registered DALPURI_FULL_SYNC job handler");
+31
View File
@@ -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