From 7af860c3282429d6b40cd798fd00099a306d660e Mon Sep 17 00:00:00 2001 From: sandeep-aot <89100322+sandeep-aot@users.noreply.github.com> Date: Wed, 8 Apr 2026 08:20:23 -0700 Subject: [PATCH 01/11] CCP-4080-create database for temporal Adds chnges for:- 1. Temporal db 2.Uses ext helm repo to install temporal server and worker --- .github/workflows/pr_close.yaml | 11 +++ .github/workflows/pr_open.yaml | 37 +++++++++ deployments/helm/action-crunchy/values.yml | 4 + .../soba/templates/temporal/configmap.yaml | 11 +++ .../templates/temporal/deployment-worker.yaml | 53 ++++++++++++ deployments/helm/soba/values-dev.yaml | 10 +++ deployments/helm/soba/values-pr.yaml | 17 ++++ deployments/helm/soba/values.yaml | 19 +++++ deployments/helm/temporal/values.yaml | 80 +++++++++++++++++++ 9 files changed, 242 insertions(+) create mode 100644 deployments/helm/soba/templates/temporal/configmap.yaml create mode 100644 deployments/helm/soba/templates/temporal/deployment-worker.yaml create mode 100644 deployments/helm/temporal/values.yaml diff --git a/.github/workflows/pr_close.yaml b/.github/workflows/pr_close.yaml index a89d504..c5f0121 100644 --- a/.github/workflows/pr_close.yaml +++ b/.github/workflows/pr_close.yaml @@ -73,6 +73,17 @@ jobs: insecure_skip_tls_verify: true namespace: ${{ secrets.OC_NAMESPACE }} + - name: Delete Temporal PR namespace + env: + NAMESPACE: ${{ secrets.OC_NAMESPACE }} + PR_NUM: ${{ needs.set-vars.outputs.PR_NUM }} + run: | + TEMPORAL_NS="soba-pr-${PR_NUM}" + if oc get deployment temporal-admintools -n "${NAMESPACE}" &>/dev/null; then + oc exec deployment/temporal-admintools -n "${NAMESPACE}" -- \ + temporal operator namespace delete -n "${TEMPORAL_NS}" --yes 2>/dev/null || true + fi + - name: Uninstall SOBA and delete orphaned resources env: RELEASE: ${{ needs.set-vars.outputs.RELEASE }} diff --git a/.github/workflows/pr_open.yaml b/.github/workflows/pr_open.yaml index 4fc234a..b3feadb 100644 --- a/.github/workflows/pr_open.yaml +++ b/.github/workflows/pr_open.yaml @@ -109,6 +109,36 @@ jobs: insecure_skip_tls_verify: true namespace: ${{ secrets.OC_NAMESPACE }} + - name: Deploy Shared Temporal (idempotent) + env: + NAMESPACE: ${{ secrets.OC_NAMESPACE }} + TEMPORAL_CHART_VERSION: ${{ vars.TEMPORAL_CHART_VERSION || '0.74.0' }} + run: | + helm upgrade --install temporal temporal \ + --repo https://go.temporal.io/helm-charts \ + --version "${TEMPORAL_CHART_VERSION}" \ + --namespace "${NAMESPACE}" \ + --timeout 900s \ + -f deployments/helm/temporal/values.yaml + + echo "Waiting for schema jobs..." + oc wait --for=condition=complete job -l app.kubernetes.io/instance=temporal \ + -n "${NAMESPACE}" --timeout=600s || true + + echo "Waiting for Temporal frontend..." + oc rollout status deployment/temporal-frontend \ + -n "${NAMESPACE}" --timeout=300s + + - name: Ensure Temporal PR namespace exists + env: + NAMESPACE: ${{ secrets.OC_NAMESPACE }} + PR_NUM: ${{ needs.set-vars.outputs.PR_NUM }} + run: | + TEMPORAL_NS="soba-pr-${PR_NUM}" + oc exec deployment/temporal-admintools -n "${NAMESPACE}" -- \ + sh -c "temporal operator namespace describe -n ${TEMPORAL_NS} 2>/dev/null || \ + temporal operator namespace create -n ${TEMPORAL_NS} --retention 3d" + - name: Get DB config from Crunchy secret id: db env: @@ -133,6 +163,7 @@ jobs: TAG: sha-${{ needs.build.outputs.short_sha }} DOMAIN: ${{ vars.OC_DOMAIN }} DATABASE_URI: ${{ steps.db.outputs.uri }} + PR_NUM: ${{ needs.set-vars.outputs.PR_NUM }} run: | REPO_LC=$(echo "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]') # Escape single quotes for YAML: ' -> '' @@ -155,6 +186,9 @@ jobs: admin: password: "${{ secrets.FORMIO_ADMIN_PASSWORD }}" jwtSecret: "${{ secrets.FORMIO_JWT_SECRET }}" + temporal: + address: "temporal-frontend.${NAMESPACE}.svc.cluster.local:7233" + namespace: "soba-pr-${PR_NUM}" OVERRIDE helm upgrade --install "${RELEASE}" ./deployments/helm/soba \ @@ -186,6 +220,9 @@ jobs: echo "Waiting for mongodb rollout..." oc rollout status statefulset/${RELEASE}-mongodb -n "${NAMESPACE}" --timeout=300s + echo "Waiting for temporal-worker rollout..." + oc rollout status deployment/${RELEASE}-temporal-worker -n "${NAMESPACE}" --timeout=300s + - name: Release Comment on PR uses: marocchino/sticky-pull-request-comment@v2 if: success() diff --git a/deployments/helm/action-crunchy/values.yml b/deployments/helm/action-crunchy/values.yml index d692777..2db27d3 100644 --- a/deployments/helm/action-crunchy/values.yml +++ b/deployments/helm/action-crunchy/values.yml @@ -83,3 +83,7 @@ crunchy: databases: - '{{ .Values.global.config.dbName }}' - 'postgres' + - name: 'temporal' + databases: + - 'temporal' + - 'temporal_visibility' diff --git a/deployments/helm/soba/templates/temporal/configmap.yaml b/deployments/helm/soba/templates/temporal/configmap.yaml new file mode 100644 index 0000000..975d157 --- /dev/null +++ b/deployments/helm/soba/templates/temporal/configmap.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "soba.fullname" . }}-temporal + labels: + {{- include "soba.labels" (dict "root" . "component" "temporal") | nindent 4 }} +data: + TEMPORAL_ALLOWED: {{ .Values.temporal.allowed | default "false" | quote }} + TEMPORAL_ADDRESS: {{ .Values.temporal.address | default "" | quote }} + TEMPORAL_NAMESPACE: {{ .Values.temporal.namespace | default "default" | quote }} + TEMPORAL_TASK_QUEUE: {{ .Values.temporal.taskQueue | default "soba" | quote }} diff --git a/deployments/helm/soba/templates/temporal/deployment-worker.yaml b/deployments/helm/soba/templates/temporal/deployment-worker.yaml new file mode 100644 index 0000000..3f06bd2 --- /dev/null +++ b/deployments/helm/soba/templates/temporal/deployment-worker.yaml @@ -0,0 +1,53 @@ +{{- $tw := .Values.temporalWorker | default dict }} +{{- $twEnabled := false }} +{{- if hasKey $tw "enabled" }}{{ $twEnabled = index $tw "enabled" }}{{ end }} +{{- if $twEnabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "soba.fullname" . }}-temporal-worker + labels: + {{- include "soba.labels" (dict "root" . "component" "temporal-worker") | nindent 4 }} +spec: + replicas: {{ .Values.temporalWorker.replicas }} + selector: + matchLabels: + {{- include "soba.selectorLabels" (dict "root" . "component" "temporal-worker") | nindent 6 }} + template: + metadata: + annotations: + checksum/config-temporal: {{ include (print $.Template.BasePath "/temporal/configmap.yaml") . | sha256sum }} + checksum/config-app: {{ include (print $.Template.BasePath "/backend/configmap-app.yaml") . | sha256sum }} + checksum/config-formio: {{ include (print $.Template.BasePath "/backend/configmap-formio.yaml") . | sha256sum }} + checksum/config-sso: {{ include (print $.Template.BasePath "/backend/configmap-sso.yaml") . | sha256sum }} + checksum/config-ratelimit: {{ include (print $.Template.BasePath "/backend/configmap-ratelimit.yaml") . | sha256sum }} + checksum/secret-db: {{ include (print $.Template.BasePath "/secrets/db-secret.yaml") . | sha256sum }} + checksum/secret-formio: {{ include (print $.Template.BasePath "/secrets/formio-secret.yaml") . | sha256sum }} + labels: + {{- include "soba.selectorLabels" (dict "root" . "component" "temporal-worker") | nindent 8 }} + spec: + containers: + - name: temporal-worker + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + command: + - node + - backend/dist/src/temporal/worker.js + envFrom: + - configMapRef: + name: {{ include "soba.fullname" . }}-temporal + - configMapRef: + name: {{ include "soba.fullname" . }}-backend-app + - configMapRef: + name: {{ include "soba.fullname" . }}-backend-formio + - configMapRef: + name: {{ include "soba.fullname" . }}-backend-sso + - configMapRef: + name: {{ include "soba.fullname" . }}-backend-ratelimit + - secretRef: + name: {{ include "soba.fullname" . }}-db + - secretRef: + name: {{ include "soba.fullname" . }}-formio + resources: + {{- toYaml .Values.temporalWorker.resources | nindent 12 }} +{{- end }} diff --git a/deployments/helm/soba/values-dev.yaml b/deployments/helm/soba/values-dev.yaml index 2dcbedf..6802e5c 100644 --- a/deployments/helm/soba/values-dev.yaml +++ b/deployments/helm/soba/values-dev.yaml @@ -51,3 +51,13 @@ outboxWorker: limits: cpu: 100m memory: 256Mi + +temporal: + allowed: "true" + address: "" + namespace: "soba-dev" + taskQueue: "soba" + +temporalWorker: + enabled: true + replicas: 1 diff --git a/deployments/helm/soba/values-pr.yaml b/deployments/helm/soba/values-pr.yaml index 9707684..2e87b72 100644 --- a/deployments/helm/soba/values-pr.yaml +++ b/deployments/helm/soba/values-pr.yaml @@ -77,3 +77,20 @@ outboxWorker: limits: cpu: 100m memory: 256Mi + +temporal: + allowed: "true" + address: "" + namespace: "default" + taskQueue: "soba" + +temporalWorker: + enabled: true + replicas: 1 + resources: + requests: + cpu: 25m + memory: 128Mi + limits: + cpu: 100m + memory: 512Mi diff --git a/deployments/helm/soba/values.yaml b/deployments/helm/soba/values.yaml index bcb438a..99b354b 100644 --- a/deployments/helm/soba/values.yaml +++ b/deployments/helm/soba/values.yaml @@ -227,3 +227,22 @@ mongodb: persistence: size: 1Gi storageClassName: "" + +# -- Temporal (shared server, env vars for backend + worker) ------------------ +temporal: + allowed: "false" + address: "" + namespace: "default" + taskQueue: "soba" + +# -- Temporal worker (same backend image, runs temporal worker process) ------ +temporalWorker: + enabled: false + replicas: 1 + resources: + requests: + cpu: 25m + memory: 128Mi + limits: + cpu: 100m + memory: 512Mi diff --git a/deployments/helm/temporal/values.yaml b/deployments/helm/temporal/values.yaml new file mode 100644 index 0000000..9d5fdaf --- /dev/null +++ b/deployments/helm/temporal/values.yaml @@ -0,0 +1,80 @@ +server: + image: + repository: temporalio/server + tag: 1.30.3 + securityContext: + runAsUser: null + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + config: + persistence: + default: + driver: "sql" + sql: + driver: "postgres12" + host: pg-soba-crunchy-primary + port: 5432 + database: temporal + user: temporal + existingSecret: pg-soba-crunchy-pguser-temporal + maxConns: 20 + maxIdleConns: 20 + maxConnLifetime: "1h" + visibility: + driver: "sql" + sql: + driver: "postgres12" + host: pg-soba-crunchy-primary + port: 5432 + database: temporal_visibility + user: temporal + existingSecret: pg-soba-crunchy-pguser-temporal + maxConns: 20 + maxIdleConns: 20 + maxConnLifetime: "1h" + +cassandra: + enabled: false +mysql: + enabled: false +elasticsearch: + enabled: false +prometheus: + enabled: false +grafana: + enabled: false + +schema: + createDatabase: + enabled: false + setup: + enabled: true + backoffLimit: 100 + update: + enabled: true + backoffLimit: 100 + +web: + enabled: true + resources: + requests: + cpu: 15m + memory: 48Mi + limits: + cpu: 100m + memory: 128Mi + +admintools: + enabled: true + resources: + requests: + cpu: 10m + memory: 48Mi + limits: + cpu: 50m + memory: 128Mi From 502e6af22eeb61f4c65de0b20884fa3ffdfcec65 Mon Sep 17 00:00:00 2001 From: sandeep-aot <89100322+sandeep-aot@users.noreply.github.com> Date: Wed, 8 Apr 2026 08:58:41 -0700 Subject: [PATCH 02/11] CCP-4080-Pipeline fix for temporal Adds fix for pipeline failure due to temporal --- deployments/helm/temporal/values.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deployments/helm/temporal/values.yaml b/deployments/helm/temporal/values.yaml index 9d5fdaf..6d4d9d6 100644 --- a/deployments/helm/temporal/values.yaml +++ b/deployments/helm/temporal/values.yaml @@ -2,8 +2,7 @@ server: image: repository: temporalio/server tag: 1.30.3 - securityContext: - runAsUser: null + securityContext: {} resources: requests: cpu: 50m From 552b6aedc0051c05cb75e2326ca22448ca59ecbb Mon Sep 17 00:00:00 2001 From: sandeep-aot <89100322+sandeep-aot@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:13:46 -0700 Subject: [PATCH 03/11] CCP-4080-fix pipeline error --- deployments/helm/action-crunchy/values.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/deployments/helm/action-crunchy/values.yml b/deployments/helm/action-crunchy/values.yml index 2db27d3..37ff1b7 100644 --- a/deployments/helm/action-crunchy/values.yml +++ b/deployments/helm/action-crunchy/values.yml @@ -87,3 +87,4 @@ crunchy: databases: - 'temporal' - 'temporal_visibility' + options: "SUPERUSER CREATEDB CREATEROLE" From 4fd41798cae2832a5ca0774648f3e96adebb6d2f Mon Sep 17 00:00:00 2001 From: sandeep-aot <89100322+sandeep-aot@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:59:13 -0700 Subject: [PATCH 04/11] CCP-4080-Pipeline failure fix Handles Openshift security context. --- deployments/helm/temporal/values.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deployments/helm/temporal/values.yaml b/deployments/helm/temporal/values.yaml index 6d4d9d6..27e3722 100644 --- a/deployments/helm/temporal/values.yaml +++ b/deployments/helm/temporal/values.yaml @@ -2,7 +2,9 @@ server: image: repository: temporalio/server tag: 1.30.3 - securityContext: {} + securityContext: + fsGroup: null + runAsUser: null resources: requests: cpu: 50m From 9849033a63c3970df9d6e310e587416c54fb7a3f Mon Sep 17 00:00:00 2001 From: sandeep-aot <89100322+sandeep-aot@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:59:26 -0700 Subject: [PATCH 05/11] CCP-4080-Pipeline fix --- deployments/helm/temporal/values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deployments/helm/temporal/values.yaml b/deployments/helm/temporal/values.yaml index 27e3722..7e6b586 100644 --- a/deployments/helm/temporal/values.yaml +++ b/deployments/helm/temporal/values.yaml @@ -14,6 +14,8 @@ server: memory: 512Mi config: persistence: + defaultStore: default + visibilityStore: visibility default: driver: "sql" sql: From 92df6bdca83d159abac12e4f83f9d2ca90603b5a Mon Sep 17 00:00:00 2001 From: sandeep-aot <89100322+sandeep-aot@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:06:26 -0700 Subject: [PATCH 06/11] CCP-4080-pipeline_fix --- deployments/helm/temporal/values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deployments/helm/temporal/values.yaml b/deployments/helm/temporal/values.yaml index 7e6b586..2af94cb 100644 --- a/deployments/helm/temporal/values.yaml +++ b/deployments/helm/temporal/values.yaml @@ -2,6 +2,8 @@ server: image: repository: temporalio/server tag: 1.30.3 + configMapsToMount: "sprig" + setConfigFilePath: true securityContext: fsGroup: null runAsUser: null From 2514dd8547cef3690662f0fb48fdfbb5242ab5af Mon Sep 17 00:00:00 2001 From: sandeep-aot <89100322+sandeep-aot@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:34:05 -0700 Subject: [PATCH 07/11] CCP-4080-pipeline changes adds docker file to use lts-slim for temporal worker image as it doesnt work with existing backend alpine image used for soba --- .github/workflows/build-images.yaml | 27 ++++++++++++++++--- .github/workflows/pr_open.yaml | 4 +++ backend/Dockerfile.temporal-worker | 25 +++++++++++++++++ .../templates/temporal/deployment-worker.yaml | 7 ++--- deployments/helm/soba/values-dev.yaml | 4 +++ deployments/helm/soba/values-pr.yaml | 4 +++ deployments/helm/soba/values.yaml | 6 ++++- 7 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 backend/Dockerfile.temporal-worker diff --git a/.github/workflows/build-images.yaml b/.github/workflows/build-images.yaml index 3817539..eb452c1 100644 --- a/.github/workflows/build-images.yaml +++ b/.github/workflows/build-images.yaml @@ -133,13 +133,15 @@ jobs: if echo "$PLATFORMS" | grep -q 'amd64'; then MATRIX=$(echo "$MATRIX" | jq -c '.include += [ {"component":"backend","dockerfile_dir":"./backend","platform":"linux/amd64","platform_pair":"linux-amd64","runner":"ubuntu-latest"}, - {"component":"frontend","dockerfile_dir":"./frontend","platform":"linux/amd64","platform_pair":"linux-amd64","runner":"ubuntu-latest"} + {"component":"frontend","dockerfile_dir":"./frontend","platform":"linux/amd64","platform_pair":"linux-amd64","runner":"ubuntu-latest"}, + {"component":"temporal-worker","dockerfile_dir":"./backend","dockerfile":"Dockerfile.temporal-worker","platform":"linux/amd64","platform_pair":"linux-amd64","runner":"ubuntu-latest"} ]') fi if echo "$PLATFORMS" | grep -q 'arm64'; then MATRIX=$(echo "$MATRIX" | jq -c '.include += [ {"component":"backend","dockerfile_dir":"./backend","platform":"linux/arm64","platform_pair":"linux-arm64","runner":"ubuntu-24.04-arm"}, - {"component":"frontend","dockerfile_dir":"./frontend","platform":"linux/arm64","platform_pair":"linux-arm64","runner":"ubuntu-24.04-arm"} + {"component":"frontend","dockerfile_dir":"./frontend","platform":"linux/arm64","platform_pair":"linux-arm64","runner":"ubuntu-24.04-arm"}, + {"component":"temporal-worker","dockerfile_dir":"./backend","dockerfile":"Dockerfile.temporal-worker","platform":"linux/arm64","platform_pair":"linux-arm64","runner":"ubuntu-24.04-arm"} ]') fi # Use delimiter so JSON is not mangled by GITHUB_OUTPUT parsing @@ -194,7 +196,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: ${{ matrix.dockerfile_dir }}/Dockerfile + file: ${{ matrix.dockerfile_dir }}/${{ matrix.dockerfile || 'Dockerfile' }} platforms: ${{ matrix.platform }} labels: | org.opencontainers.image.revision=${{ github.sha }} @@ -288,3 +290,22 @@ jobs: --annotation "index:org.opencontainers.image.description=SOBA Next.js frontend for web form builder" \ -t ${IMAGE}:latest ${DIGESTS} fi + + - name: Merge temporal-worker + run: | + SHORT_SHA=$(git rev-parse --short HEAD) + IMAGE="ghcr.io/${{ github.repository }}/temporal-worker" + IMAGE_VERSION="${{ needs.prepare.outputs.image_version }}" + DIGESTS="" + for f in $(find digests -type f -name 'temporal-worker-*' 2>/dev/null); do + DIGESTS="${DIGESTS} ${IMAGE}@$(cat $f)" + done + [ -n "$DIGESTS" ] || { echo "No temporal-worker digests"; exit 1; } + docker buildx imagetools create \ + --annotation "index:org.opencontainers.image.description=SOBA Temporal worker for workflow execution" \ + -t ${IMAGE}:sha-${SHORT_SHA} -t ${IMAGE}:${IMAGE_VERSION} ${DIGESTS} + if [ "$IMAGE_VERSION" = "main" ]; then + docker buildx imagetools create \ + --annotation "index:org.opencontainers.image.description=SOBA Temporal worker for workflow execution" \ + -t ${IMAGE}:latest ${DIGESTS} + fi diff --git a/.github/workflows/pr_open.yaml b/.github/workflows/pr_open.yaml index b3feadb..17d6cb2 100644 --- a/.github/workflows/pr_open.yaml +++ b/.github/workflows/pr_open.yaml @@ -178,6 +178,10 @@ jobs: image: repository: "ghcr.io/${REPO_LC}/frontend" tag: "${TAG}" + temporalWorker: + image: + repository: "ghcr.io/${REPO_LC}/temporal-worker" + tag: "${TAG}" global: domain: "${DOMAIN}" database: diff --git a/backend/Dockerfile.temporal-worker b/backend/Dockerfile.temporal-worker new file mode 100644 index 0000000..93d3242 --- /dev/null +++ b/backend/Dockerfile.temporal-worker @@ -0,0 +1,25 @@ +FROM node:lts-slim + +# enable pnpm via corepack and ensure workspace installs work +RUN corepack enable pnpm + +WORKDIR /app + +# copy root workspace config + backend package file +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY backend/package.json backend/ + +# install only backend deps (hoisted into root node_modules) +RUN pnpm install --filter ./backend... + +# copy entire repo and build backend +COPY . . +RUN pnpm --filter ./backend run build + +# OpenShift / arbitrary UID: run non-root; g=u lets assigned user (often gid 0) read /app +RUN chgrp -R 0 /app && chmod -R g=u /app +ENV HOME=/tmp +ENV XDG_CACHE_HOME=/tmp/.cache +USER 1001 + +CMD ["node", "backend/dist/temporal-worker.js"] diff --git a/deployments/helm/soba/templates/temporal/deployment-worker.yaml b/deployments/helm/soba/templates/temporal/deployment-worker.yaml index 3f06bd2..9f3bdf4 100644 --- a/deployments/helm/soba/templates/temporal/deployment-worker.yaml +++ b/deployments/helm/soba/templates/temporal/deployment-worker.yaml @@ -28,11 +28,8 @@ spec: spec: containers: - name: temporal-worker - image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" - imagePullPolicy: {{ .Values.backend.image.pullPolicy }} - command: - - node - - backend/dist/src/temporal/worker.js + image: "{{ .Values.temporalWorker.image.repository }}:{{ .Values.temporalWorker.image.tag }}" + imagePullPolicy: {{ .Values.temporalWorker.image.pullPolicy }} envFrom: - configMapRef: name: {{ include "soba.fullname" . }}-temporal diff --git a/deployments/helm/soba/values-dev.yaml b/deployments/helm/soba/values-dev.yaml index 6802e5c..04490ad 100644 --- a/deployments/helm/soba/values-dev.yaml +++ b/deployments/helm/soba/values-dev.yaml @@ -60,4 +60,8 @@ temporal: temporalWorker: enabled: true + image: + repository: ghcr.io/bcgov/soba/temporal-worker + tag: latest + pullPolicy: IfNotPresent replicas: 1 diff --git a/deployments/helm/soba/values-pr.yaml b/deployments/helm/soba/values-pr.yaml index 2e87b72..063c099 100644 --- a/deployments/helm/soba/values-pr.yaml +++ b/deployments/helm/soba/values-pr.yaml @@ -86,6 +86,10 @@ temporal: temporalWorker: enabled: true + image: + repository: ghcr.io/bcgov/soba/temporal-worker + tag: latest + pullPolicy: IfNotPresent replicas: 1 resources: requests: diff --git a/deployments/helm/soba/values.yaml b/deployments/helm/soba/values.yaml index 99b354b..a6a7202 100644 --- a/deployments/helm/soba/values.yaml +++ b/deployments/helm/soba/values.yaml @@ -235,9 +235,13 @@ temporal: namespace: "default" taskQueue: "soba" -# -- Temporal worker (same backend image, runs temporal worker process) ------ +# -- Temporal worker (dedicated image with glibc for @temporalio/core-bridge) -- temporalWorker: enabled: false + image: + repository: ghcr.io/bcgov/soba/temporal-worker + tag: latest + pullPolicy: IfNotPresent replicas: 1 resources: requests: From 9dc4e9a9208eba35b4ee09f7ebbd0e5f39b7365f Mon Sep 17 00:00:00 2001 From: Navdeep Kaur Date: Tue, 14 Apr 2026 12:55:25 -0700 Subject: [PATCH 08/11] feat: add Temporal sample workflow, test script, and testing runbook --- backend/package.json | 1 + backend/scripts/test-temporal.ts | 72 ++++ backend/src/temporal/activities/index.ts | 2 +- backend/src/temporal/activities/sample.ts | 6 + backend/src/temporal/workflows/index.ts | 2 +- .../src/temporal/workflows/sampleWorkflow.ts | 10 + docs/temporal-testing.md | 325 ++++++++++++++++++ 7 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 backend/scripts/test-temporal.ts create mode 100644 backend/src/temporal/activities/sample.ts create mode 100644 backend/src/temporal/workflows/sampleWorkflow.ts create mode 100644 docs/temporal-testing.md diff --git a/backend/package.json b/backend/package.json index 60e6429..bff3ad5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,6 +8,7 @@ "test:signin": "tsx scripts/test-signin.ts", "script:test-signin": "tsx scripts/test-signin.ts", "script:test-formio-proxy": "tsx scripts/test-formio-proxy.ts", + "script:test-temporal": "tsx scripts/test-temporal.ts", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "db:migrate": "tsx src/core/db/migrate.ts", diff --git a/backend/scripts/test-temporal.ts b/backend/scripts/test-temporal.ts new file mode 100644 index 0000000..20fe806 --- /dev/null +++ b/backend/scripts/test-temporal.ts @@ -0,0 +1,72 @@ +/** + * Submit a sample Temporal workflow and wait for its result. + * Use this to verify the deployed Temporal server, worker, and UI are working. + * + * Usage (from backend/): + * # Against the default TEMPORAL_ADDRESS from .env (localhost:7233 for local dev) + * pnpm script:test-temporal + * + * # Override the address (e.g. after port-forwarding to OpenShift) + * # oc port-forward svc/temporal-frontend -n acf456-dev 7233:7233 + * TEMPORAL_ADDRESS=localhost:7233 pnpm script:test-temporal + * + * # Pass a custom name argument + * pnpm script:test-temporal -- --name=OpenShift + * + * After running, open the Temporal UI (port-forward svc/temporal-web -n acf456-dev 8080:8080) + * and look for the printed workflowId in the "default" namespace. + * + * Database check — connect to the temporal DB and run: + * SELECT workflow_id, run_id, workflow_type_name, status, start_time, close_time + * FROM executions + * ORDER BY start_time DESC + * LIMIT 10; + */ +import '../dotenv-init'; + +import { Client, Connection } from '@temporalio/client'; +import { sampleWorkflow } from '../src/temporal/workflows/sampleWorkflow'; + +function parseName(): string { + const arg = process.argv.find((a) => a.startsWith('--name=')); + return arg?.slice(7) || 'World'; +} + +async function main() { + const address = process.env.TEMPORAL_ADDRESS ?? 'localhost:7233'; + const namespace = process.env.TEMPORAL_NAMESPACE ?? 'default'; + const taskQueue = process.env.TEMPORAL_TASK_QUEUE ?? 'soba'; + const name = parseName(); + + console.log(`Connecting to Temporal at ${address} (namespace: ${namespace})`); + + const connection = await Connection.connect({ address }); + const client = new Client({ connection, namespace }); + + const workflowId = `sample-${Date.now()}`; + + console.log(`Starting sampleWorkflow (workflowId: ${workflowId}, name: "${name}")`); + + const handle = await client.workflow.start(sampleWorkflow, { + taskQueue, + workflowId, + args: [name], + }); + + console.log(`Workflow started. Waiting for result...`); + console.log(` → Look it up in the Temporal UI: namespace=${namespace}, workflowId=${workflowId}`); + + const result = await handle.result(); + + console.log(`\nResult: ${result}`); + console.log(`\nWorkflow completed successfully.`); + console.log(` workflowId : ${workflowId}`); + console.log(` runId : ${handle.firstExecutionRunId}`); + + await connection.close(); +} + +main().catch((err) => { + console.error('test-temporal failed:', (err as Error).message); + process.exit(1); +}); diff --git a/backend/src/temporal/activities/index.ts b/backend/src/temporal/activities/index.ts index 55e07be..746c2ac 100644 --- a/backend/src/temporal/activities/index.ts +++ b/backend/src/temporal/activities/index.ts @@ -1,2 +1,2 @@ // Temporal activities barrel — add activity exports here -export {}; +export { sample } from './sample'; diff --git a/backend/src/temporal/activities/sample.ts b/backend/src/temporal/activities/sample.ts new file mode 100644 index 0000000..0ba51cb --- /dev/null +++ b/backend/src/temporal/activities/sample.ts @@ -0,0 +1,6 @@ +import { log } from '@temporalio/activity'; + +export async function sample(name: string): Promise { + log.info('Running sample activity', { name }); + return `Hello, ${name}! Temporal is working on OpenShift.`; +} diff --git a/backend/src/temporal/workflows/index.ts b/backend/src/temporal/workflows/index.ts index 01bb348..b7cdff2 100644 --- a/backend/src/temporal/workflows/index.ts +++ b/backend/src/temporal/workflows/index.ts @@ -1,2 +1,2 @@ // Temporal workflows barrel — add workflow exports here -export {}; +export { sampleWorkflow } from './sampleWorkflow'; diff --git a/backend/src/temporal/workflows/sampleWorkflow.ts b/backend/src/temporal/workflows/sampleWorkflow.ts new file mode 100644 index 0000000..82b34b9 --- /dev/null +++ b/backend/src/temporal/workflows/sampleWorkflow.ts @@ -0,0 +1,10 @@ +import { proxyActivities } from '@temporalio/workflow'; +import type * as activities from '../activities'; + +const { sample } = proxyActivities({ + startToCloseTimeout: '30 seconds', +}); + +export async function sampleWorkflow(name: string): Promise { + return await sample(name); +} diff --git a/docs/temporal-testing.md b/docs/temporal-testing.md new file mode 100644 index 0000000..ce33d44 --- /dev/null +++ b/docs/temporal-testing.md @@ -0,0 +1,325 @@ +# Testing the Temporal Deployment + +How to verify that the Temporal server, worker, and UI are working correctly — both locally and against a deployed OpenShift environment. + +--- + +## Prerequisites — build the worker code first + +The OpenShift worker runs compiled JavaScript (`node dist/temporal-worker.js`). Before deploying or testing, the TypeScript must be compiled so the `dist/` output includes the sample workflow and activity. + +From `backend/`: + +```bash +# Inside devcontainer +pnpm build + +# Outside devcontainer (bare WSL shell) +npx tsc +``` + +Verify the compiled files include the new exports: + +```bash +cat dist/src/temporal/workflows/index.js +# Expected: exports.sampleWorkflow = ... + +cat dist/src/temporal/activities/index.js +# Expected: exports.sample = ... +``` + +After building, a new worker image must be built and pushed (via GitHub Actions or locally) and the worker Deployment redeployed in OpenShift before the end-to-end test will complete. + +--- + +## What gets tested + +Running the test script: + +1. Connects to the Temporal server over gRPC +2. Submits a `sampleWorkflow` to the `soba` task queue +3. Waits for the deployed worker to pick it up and execute the `sample` activity +4. Prints the result and the workflow ID +5. The workflow appears in the Temporal UI under the `TEMPORAL_NAMESPACE` (e.g. `soba-pr-45` on OpenShift, `default` locally) + +--- + +## Files involved + +| File | Purpose | +| ---- | ------- | +| `backend/scripts/test-temporal.ts` | Script — connects, submits workflow, waits for result | +| `backend/src/temporal/workflows/sampleWorkflow.ts` | Calls the `sample` activity | +| `backend/src/temporal/activities/sample.ts` | Returns a greeting string — the actual work | + +--- + +## Testing locally (devcontainer) + +**Prerequisites:** Temporal containers running and the worker process started. + +```bash +# Start Temporal and its dependencies (from repo root) +docker compose -f .devcontainer/docker-compose.yml up -d postgres temporal temporal-ui + +# Start the worker (from backend/) — inside devcontainer +pnpm temporal-worker:dev + +# Outside devcontainer +npx tsx watch temporal-worker.ts +``` + +Run the script in a separate terminal (from `backend/`): + +```bash +# Inside devcontainer +pnpm script:test-temporal +pnpm script:test-temporal -- --name=LocalTest + +# Outside devcontainer (bare WSL shell) +npx tsx scripts/test-temporal.ts +npx tsx scripts/test-temporal.ts -- --name=LocalTest +``` + +Expected output: + +``` +Connecting to Temporal at localhost:7233 (namespace: default) +Starting sampleWorkflow (workflowId: sample-1713000000000, name: "World") +Workflow started. Waiting for result... + → Look it up in the Temporal UI: namespace=default, workflowId=sample-1713000000000 + +Result: Hello, World! Temporal is working on OpenShift. + +Workflow completed successfully. + workflowId : sample-1713000000000 + runId : +``` + +Open the UI at `http://localhost:8080` and search for the printed `workflowId`. + +--- + +## Testing against OpenShift + +### Step 1 — Port-forward the Temporal gRPC port + +```bash +oc port-forward svc/temporal-frontend -n acf456-dev 7233:7233 +``` + +Keep this terminal open. + +> **Note:** If the local devcontainer is running, its Temporal container already occupies port 7233. Use port 7234 instead to avoid the conflict: +> ```bash +> oc port-forward svc/temporal-frontend -n acf456-dev 7234:7233 +> ``` +> Then use `TEMPORAL_ADDRESS=localhost:7234` in Step 2. + +### Step 2 — Find the Temporal namespace + +The Temporal namespace is set per Helm release (e.g. `soba-pr-45`). Check the configmap: + +```bash +oc get configmap -n acf456-dev -l app.kubernetes.io/component=temporal -o jsonpath='{.items[0].data.TEMPORAL_NAMESPACE}' +``` + +### Step 3 — Run the script + +In a separate terminal (from `backend/`): + +```bash +# Inside devcontainer (7233 available, port-forward on 7233) +TEMPORAL_ADDRESS=localhost:7233 TEMPORAL_NAMESPACE=soba-pr-45 pnpm script:test-temporal -- --name=OpenShift + +# Outside devcontainer (bare WSL shell; use 7234 if local docker Temporal holds 7233) +TEMPORAL_ADDRESS=localhost:7234 TEMPORAL_NAMESPACE=soba-pr-45 npx tsx backend/scripts/test-temporal.ts -- --name=OpenShift +``` + +The script connects through the port-forward. The worker running in OpenShift picks up the task and executes it. + +### Step 3 — View the workflow in the UI + +Port-forward the Temporal UI: + +```bash +oc port-forward svc/temporal-web -n acf456-dev 8080:8080 +``` + +Open `http://localhost:8080`. Navigate to the `default` namespace and search for the `workflowId` printed by the script. You should see: + +- **Status**: Completed +- **Workflow type**: `sampleWorkflow` +- **Task queue**: `soba` +- **Input / Result**: the name argument and returned greeting + +--- + +## Testing on OpenShift without port-forwarding + +Three approaches that work entirely inside the cluster — no `oc port-forward` needed. + +--- + +### Method 1 — Connectivity check via `oc exec` + +Exec into the worker pod and verify it can reach the Temporal server over the internal cluster DNS. No workflow is submitted — this is a quick smoke test only. + +```bash +oc exec -it deployment/temporal-worker -- \ + node -e " + const { Connection } = require('@temporalio/client'); + Connection.connect({ address: 'temporal:7233' }) + .then(() => { console.log('Connected OK'); process.exit(0); }) + .catch(e => { console.error('Failed:', e.message); process.exit(1); }); + " +``` + +Expected: `Connected OK` + +--- + +### Method 2 — Submit and inspect workflows with `tctl` inside the Temporal pod + +The `temporalio/auto-setup` image ships with the `tctl` CLI. Exec into the Temporal server pod to submit a workflow and inspect results — all from inside the cluster. + +**Check namespace and task queue:** + +```bash +# List namespaces (should show "default") +oc exec -it deployment/temporal -- tctl namespace list + +# Check the soba task queue has active pollers (the worker) +oc exec -it deployment/temporal -- tctl --namespace default taskqueue describe --taskqueue soba --taskqueuetype workflow +``` + +**Submit a workflow:** + +```bash +oc exec -it deployment/temporal -- \ + tctl --namespace default workflow run \ + --tq soba \ + --wt sampleWorkflow \ + --et 60 \ + -i '"OpenShift"' +``` + +The `-i` flag takes JSON — the string `"OpenShift"` is the `name` argument to `sampleWorkflow`. + +**List recent workflows:** + +```bash +oc exec -it deployment/temporal -- \ + tctl --namespace default workflow list +``` + +**Show the result of a specific run:** + +```bash +oc exec -it deployment/temporal -- \ + tctl --namespace default workflow show -w +``` + +A completed workflow will show its result in the event history at the bottom of the output. + +--- + +### Method 3 — Trigger via the API server Route + +If the API server has a route that starts a Temporal workflow (once real features are added), you can hit it with `curl` using the externally exposed Route — no port-forward needed. The workflow submission goes: `curl → API Route → API pod → temporal:7233 (internal) → worker pod`. + +This is the pattern all production workflows will follow. For now, use Method 1 or 2 for ad-hoc testing until a real workflow-triggering endpoint exists in the API. + +--- + +## Checking data in the Temporal database + +Port-forward to Postgres and connect to the `temporal` database: + +```bash +# Port-forward (use your actual postgres service name) +oc port-forward svc/ 5432:5432 + +# Connect +psql -h localhost -U temporal -d temporal +``` + +### Useful queries + +```sql +-- Recent workflow executions (status: 1 = running, 2 = completed, 3 = failed, 4 = cancelled, 5 = terminated) +SELECT workflow_id, run_id, workflow_type_name, status, start_time, close_time +FROM executions +ORDER BY start_time DESC +LIMIT 10; + +-- Activity schedules for the sample workflow +SELECT workflow_id, activity_id, activity_type_name, schedule_time, start_time, close_time +FROM activity_info_maps +ORDER BY schedule_time DESC +LIMIT 10; + +-- Task queues the worker is registered on +SELECT task_queue_name, task_queue_type, range_id +FROM task_queues +WHERE task_queue_name = 'soba'; + +-- Namespaces (should show "default") +SELECT id, name, status +FROM namespaces; +``` + +--- + +## Troubleshooting + +**`unable to listen on port 7233: address already in use`** + +The local devcontainer Temporal is already bound to port 7233. Use 7234 instead: + +```bash +oc port-forward svc/temporal-frontend -n acf456-dev 7234:7233 +TEMPORAL_ADDRESS=localhost:7234 npx tsx scripts/test-temporal.ts -- --name=OpenShift +``` + +Check what's using 7233: `docker ps --filter "expose=7233"` + +**`Namespace not found: 'default'`** + +The Temporal server doesn't have a `default` namespace — it uses a per-release namespace (e.g. `soba-pr-45`). Set `TEMPORAL_NAMESPACE`: + +```bash +# Find the namespace +oc get configmap -n acf456-dev -l app.kubernetes.io/component=temporal -o jsonpath='{.items[0].data.TEMPORAL_NAMESPACE}' + +# Pass it to the script +TEMPORAL_ADDRESS=localhost:7234 TEMPORAL_NAMESPACE=soba-pr-45 npx tsx backend/scripts/test-temporal.ts -- --name=OpenShift +``` + +**Script exits with "connection refused"** + +The Temporal server is not reachable. Check: + +- Port-forward is running: `oc port-forward svc/temporal-frontend -n acf456-dev 7233:7233` +- Temporal server pod is healthy: `oc get pods -l app=temporal` + +**Script connects but result never comes back** + +The worker is not picking up the task. Check: + +- Worker pod is running: `oc get pods -l app=temporal-worker` +- Worker logs show "Worker started, polling for tasks": `oc logs -l app=temporal-worker --tail=20` +- Worker image was rebuilt after the sample workflow was added (see Prerequisites above) +- `TEMPORAL_TASK_QUEUE` is `soba` in both the worker Deployment and the script (default is `soba`) + +**Workflow shows as "Running" in UI but never completes** + +The worker started the task but the activity is stuck. Check worker logs for errors: + +```bash +oc logs -l app=temporal-worker --tail=50 +``` + +**`TEMPORAL_ALLOWED` is false** + +The script connects directly using `@temporalio/client` and does not check `TEMPORAL_ALLOWED` — that flag only gates the worker process and the Express `getClient()` helper. The test script works regardless of that setting. From 5e80452c4ebc5a18958fda60c7d0ff9e3b6b609a Mon Sep 17 00:00:00 2001 From: Navdeep Kaur Date: Fri, 17 Apr 2026 10:27:29 -0700 Subject: [PATCH 09/11] chore: remove sample Temporal workflow and test scaffolding --- backend/package.json | 3 +- backend/scripts/test-temporal.ts | 72 ---- backend/src/temporal/activities/index.ts | 1 - backend/src/temporal/activities/sample.ts | 6 - backend/src/temporal/workflows/index.ts | 1 - .../src/temporal/workflows/sampleWorkflow.ts | 10 - docs/temporal-testing.md | 325 ------------------ 7 files changed, 1 insertion(+), 417 deletions(-) delete mode 100644 backend/scripts/test-temporal.ts delete mode 100644 backend/src/temporal/activities/sample.ts delete mode 100644 backend/src/temporal/workflows/sampleWorkflow.ts delete mode 100644 docs/temporal-testing.md diff --git a/backend/package.json b/backend/package.json index bff3ad5..4f308ee 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,8 +8,7 @@ "test:signin": "tsx scripts/test-signin.ts", "script:test-signin": "tsx scripts/test-signin.ts", "script:test-formio-proxy": "tsx scripts/test-formio-proxy.ts", - "script:test-temporal": "tsx scripts/test-temporal.ts", - "test:watch": "jest --watch", +"test:watch": "jest --watch", "test:coverage": "jest --coverage", "db:migrate": "tsx src/core/db/migrate.ts", "db:seed": "tsx src/core/db/seed.ts", diff --git a/backend/scripts/test-temporal.ts b/backend/scripts/test-temporal.ts deleted file mode 100644 index 20fe806..0000000 --- a/backend/scripts/test-temporal.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Submit a sample Temporal workflow and wait for its result. - * Use this to verify the deployed Temporal server, worker, and UI are working. - * - * Usage (from backend/): - * # Against the default TEMPORAL_ADDRESS from .env (localhost:7233 for local dev) - * pnpm script:test-temporal - * - * # Override the address (e.g. after port-forwarding to OpenShift) - * # oc port-forward svc/temporal-frontend -n acf456-dev 7233:7233 - * TEMPORAL_ADDRESS=localhost:7233 pnpm script:test-temporal - * - * # Pass a custom name argument - * pnpm script:test-temporal -- --name=OpenShift - * - * After running, open the Temporal UI (port-forward svc/temporal-web -n acf456-dev 8080:8080) - * and look for the printed workflowId in the "default" namespace. - * - * Database check — connect to the temporal DB and run: - * SELECT workflow_id, run_id, workflow_type_name, status, start_time, close_time - * FROM executions - * ORDER BY start_time DESC - * LIMIT 10; - */ -import '../dotenv-init'; - -import { Client, Connection } from '@temporalio/client'; -import { sampleWorkflow } from '../src/temporal/workflows/sampleWorkflow'; - -function parseName(): string { - const arg = process.argv.find((a) => a.startsWith('--name=')); - return arg?.slice(7) || 'World'; -} - -async function main() { - const address = process.env.TEMPORAL_ADDRESS ?? 'localhost:7233'; - const namespace = process.env.TEMPORAL_NAMESPACE ?? 'default'; - const taskQueue = process.env.TEMPORAL_TASK_QUEUE ?? 'soba'; - const name = parseName(); - - console.log(`Connecting to Temporal at ${address} (namespace: ${namespace})`); - - const connection = await Connection.connect({ address }); - const client = new Client({ connection, namespace }); - - const workflowId = `sample-${Date.now()}`; - - console.log(`Starting sampleWorkflow (workflowId: ${workflowId}, name: "${name}")`); - - const handle = await client.workflow.start(sampleWorkflow, { - taskQueue, - workflowId, - args: [name], - }); - - console.log(`Workflow started. Waiting for result...`); - console.log(` → Look it up in the Temporal UI: namespace=${namespace}, workflowId=${workflowId}`); - - const result = await handle.result(); - - console.log(`\nResult: ${result}`); - console.log(`\nWorkflow completed successfully.`); - console.log(` workflowId : ${workflowId}`); - console.log(` runId : ${handle.firstExecutionRunId}`); - - await connection.close(); -} - -main().catch((err) => { - console.error('test-temporal failed:', (err as Error).message); - process.exit(1); -}); diff --git a/backend/src/temporal/activities/index.ts b/backend/src/temporal/activities/index.ts index 746c2ac..3bf6ea7 100644 --- a/backend/src/temporal/activities/index.ts +++ b/backend/src/temporal/activities/index.ts @@ -1,2 +1 @@ // Temporal activities barrel — add activity exports here -export { sample } from './sample'; diff --git a/backend/src/temporal/activities/sample.ts b/backend/src/temporal/activities/sample.ts deleted file mode 100644 index 0ba51cb..0000000 --- a/backend/src/temporal/activities/sample.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { log } from '@temporalio/activity'; - -export async function sample(name: string): Promise { - log.info('Running sample activity', { name }); - return `Hello, ${name}! Temporal is working on OpenShift.`; -} diff --git a/backend/src/temporal/workflows/index.ts b/backend/src/temporal/workflows/index.ts index b7cdff2..8ee0354 100644 --- a/backend/src/temporal/workflows/index.ts +++ b/backend/src/temporal/workflows/index.ts @@ -1,2 +1 @@ // Temporal workflows barrel — add workflow exports here -export { sampleWorkflow } from './sampleWorkflow'; diff --git a/backend/src/temporal/workflows/sampleWorkflow.ts b/backend/src/temporal/workflows/sampleWorkflow.ts deleted file mode 100644 index 82b34b9..0000000 --- a/backend/src/temporal/workflows/sampleWorkflow.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { proxyActivities } from '@temporalio/workflow'; -import type * as activities from '../activities'; - -const { sample } = proxyActivities({ - startToCloseTimeout: '30 seconds', -}); - -export async function sampleWorkflow(name: string): Promise { - return await sample(name); -} diff --git a/docs/temporal-testing.md b/docs/temporal-testing.md deleted file mode 100644 index ce33d44..0000000 --- a/docs/temporal-testing.md +++ /dev/null @@ -1,325 +0,0 @@ -# Testing the Temporal Deployment - -How to verify that the Temporal server, worker, and UI are working correctly — both locally and against a deployed OpenShift environment. - ---- - -## Prerequisites — build the worker code first - -The OpenShift worker runs compiled JavaScript (`node dist/temporal-worker.js`). Before deploying or testing, the TypeScript must be compiled so the `dist/` output includes the sample workflow and activity. - -From `backend/`: - -```bash -# Inside devcontainer -pnpm build - -# Outside devcontainer (bare WSL shell) -npx tsc -``` - -Verify the compiled files include the new exports: - -```bash -cat dist/src/temporal/workflows/index.js -# Expected: exports.sampleWorkflow = ... - -cat dist/src/temporal/activities/index.js -# Expected: exports.sample = ... -``` - -After building, a new worker image must be built and pushed (via GitHub Actions or locally) and the worker Deployment redeployed in OpenShift before the end-to-end test will complete. - ---- - -## What gets tested - -Running the test script: - -1. Connects to the Temporal server over gRPC -2. Submits a `sampleWorkflow` to the `soba` task queue -3. Waits for the deployed worker to pick it up and execute the `sample` activity -4. Prints the result and the workflow ID -5. The workflow appears in the Temporal UI under the `TEMPORAL_NAMESPACE` (e.g. `soba-pr-45` on OpenShift, `default` locally) - ---- - -## Files involved - -| File | Purpose | -| ---- | ------- | -| `backend/scripts/test-temporal.ts` | Script — connects, submits workflow, waits for result | -| `backend/src/temporal/workflows/sampleWorkflow.ts` | Calls the `sample` activity | -| `backend/src/temporal/activities/sample.ts` | Returns a greeting string — the actual work | - ---- - -## Testing locally (devcontainer) - -**Prerequisites:** Temporal containers running and the worker process started. - -```bash -# Start Temporal and its dependencies (from repo root) -docker compose -f .devcontainer/docker-compose.yml up -d postgres temporal temporal-ui - -# Start the worker (from backend/) — inside devcontainer -pnpm temporal-worker:dev - -# Outside devcontainer -npx tsx watch temporal-worker.ts -``` - -Run the script in a separate terminal (from `backend/`): - -```bash -# Inside devcontainer -pnpm script:test-temporal -pnpm script:test-temporal -- --name=LocalTest - -# Outside devcontainer (bare WSL shell) -npx tsx scripts/test-temporal.ts -npx tsx scripts/test-temporal.ts -- --name=LocalTest -``` - -Expected output: - -``` -Connecting to Temporal at localhost:7233 (namespace: default) -Starting sampleWorkflow (workflowId: sample-1713000000000, name: "World") -Workflow started. Waiting for result... - → Look it up in the Temporal UI: namespace=default, workflowId=sample-1713000000000 - -Result: Hello, World! Temporal is working on OpenShift. - -Workflow completed successfully. - workflowId : sample-1713000000000 - runId : -``` - -Open the UI at `http://localhost:8080` and search for the printed `workflowId`. - ---- - -## Testing against OpenShift - -### Step 1 — Port-forward the Temporal gRPC port - -```bash -oc port-forward svc/temporal-frontend -n acf456-dev 7233:7233 -``` - -Keep this terminal open. - -> **Note:** If the local devcontainer is running, its Temporal container already occupies port 7233. Use port 7234 instead to avoid the conflict: -> ```bash -> oc port-forward svc/temporal-frontend -n acf456-dev 7234:7233 -> ``` -> Then use `TEMPORAL_ADDRESS=localhost:7234` in Step 2. - -### Step 2 — Find the Temporal namespace - -The Temporal namespace is set per Helm release (e.g. `soba-pr-45`). Check the configmap: - -```bash -oc get configmap -n acf456-dev -l app.kubernetes.io/component=temporal -o jsonpath='{.items[0].data.TEMPORAL_NAMESPACE}' -``` - -### Step 3 — Run the script - -In a separate terminal (from `backend/`): - -```bash -# Inside devcontainer (7233 available, port-forward on 7233) -TEMPORAL_ADDRESS=localhost:7233 TEMPORAL_NAMESPACE=soba-pr-45 pnpm script:test-temporal -- --name=OpenShift - -# Outside devcontainer (bare WSL shell; use 7234 if local docker Temporal holds 7233) -TEMPORAL_ADDRESS=localhost:7234 TEMPORAL_NAMESPACE=soba-pr-45 npx tsx backend/scripts/test-temporal.ts -- --name=OpenShift -``` - -The script connects through the port-forward. The worker running in OpenShift picks up the task and executes it. - -### Step 3 — View the workflow in the UI - -Port-forward the Temporal UI: - -```bash -oc port-forward svc/temporal-web -n acf456-dev 8080:8080 -``` - -Open `http://localhost:8080`. Navigate to the `default` namespace and search for the `workflowId` printed by the script. You should see: - -- **Status**: Completed -- **Workflow type**: `sampleWorkflow` -- **Task queue**: `soba` -- **Input / Result**: the name argument and returned greeting - ---- - -## Testing on OpenShift without port-forwarding - -Three approaches that work entirely inside the cluster — no `oc port-forward` needed. - ---- - -### Method 1 — Connectivity check via `oc exec` - -Exec into the worker pod and verify it can reach the Temporal server over the internal cluster DNS. No workflow is submitted — this is a quick smoke test only. - -```bash -oc exec -it deployment/temporal-worker -- \ - node -e " - const { Connection } = require('@temporalio/client'); - Connection.connect({ address: 'temporal:7233' }) - .then(() => { console.log('Connected OK'); process.exit(0); }) - .catch(e => { console.error('Failed:', e.message); process.exit(1); }); - " -``` - -Expected: `Connected OK` - ---- - -### Method 2 — Submit and inspect workflows with `tctl` inside the Temporal pod - -The `temporalio/auto-setup` image ships with the `tctl` CLI. Exec into the Temporal server pod to submit a workflow and inspect results — all from inside the cluster. - -**Check namespace and task queue:** - -```bash -# List namespaces (should show "default") -oc exec -it deployment/temporal -- tctl namespace list - -# Check the soba task queue has active pollers (the worker) -oc exec -it deployment/temporal -- tctl --namespace default taskqueue describe --taskqueue soba --taskqueuetype workflow -``` - -**Submit a workflow:** - -```bash -oc exec -it deployment/temporal -- \ - tctl --namespace default workflow run \ - --tq soba \ - --wt sampleWorkflow \ - --et 60 \ - -i '"OpenShift"' -``` - -The `-i` flag takes JSON — the string `"OpenShift"` is the `name` argument to `sampleWorkflow`. - -**List recent workflows:** - -```bash -oc exec -it deployment/temporal -- \ - tctl --namespace default workflow list -``` - -**Show the result of a specific run:** - -```bash -oc exec -it deployment/temporal -- \ - tctl --namespace default workflow show -w -``` - -A completed workflow will show its result in the event history at the bottom of the output. - ---- - -### Method 3 — Trigger via the API server Route - -If the API server has a route that starts a Temporal workflow (once real features are added), you can hit it with `curl` using the externally exposed Route — no port-forward needed. The workflow submission goes: `curl → API Route → API pod → temporal:7233 (internal) → worker pod`. - -This is the pattern all production workflows will follow. For now, use Method 1 or 2 for ad-hoc testing until a real workflow-triggering endpoint exists in the API. - ---- - -## Checking data in the Temporal database - -Port-forward to Postgres and connect to the `temporal` database: - -```bash -# Port-forward (use your actual postgres service name) -oc port-forward svc/ 5432:5432 - -# Connect -psql -h localhost -U temporal -d temporal -``` - -### Useful queries - -```sql --- Recent workflow executions (status: 1 = running, 2 = completed, 3 = failed, 4 = cancelled, 5 = terminated) -SELECT workflow_id, run_id, workflow_type_name, status, start_time, close_time -FROM executions -ORDER BY start_time DESC -LIMIT 10; - --- Activity schedules for the sample workflow -SELECT workflow_id, activity_id, activity_type_name, schedule_time, start_time, close_time -FROM activity_info_maps -ORDER BY schedule_time DESC -LIMIT 10; - --- Task queues the worker is registered on -SELECT task_queue_name, task_queue_type, range_id -FROM task_queues -WHERE task_queue_name = 'soba'; - --- Namespaces (should show "default") -SELECT id, name, status -FROM namespaces; -``` - ---- - -## Troubleshooting - -**`unable to listen on port 7233: address already in use`** - -The local devcontainer Temporal is already bound to port 7233. Use 7234 instead: - -```bash -oc port-forward svc/temporal-frontend -n acf456-dev 7234:7233 -TEMPORAL_ADDRESS=localhost:7234 npx tsx scripts/test-temporal.ts -- --name=OpenShift -``` - -Check what's using 7233: `docker ps --filter "expose=7233"` - -**`Namespace not found: 'default'`** - -The Temporal server doesn't have a `default` namespace — it uses a per-release namespace (e.g. `soba-pr-45`). Set `TEMPORAL_NAMESPACE`: - -```bash -# Find the namespace -oc get configmap -n acf456-dev -l app.kubernetes.io/component=temporal -o jsonpath='{.items[0].data.TEMPORAL_NAMESPACE}' - -# Pass it to the script -TEMPORAL_ADDRESS=localhost:7234 TEMPORAL_NAMESPACE=soba-pr-45 npx tsx backend/scripts/test-temporal.ts -- --name=OpenShift -``` - -**Script exits with "connection refused"** - -The Temporal server is not reachable. Check: - -- Port-forward is running: `oc port-forward svc/temporal-frontend -n acf456-dev 7233:7233` -- Temporal server pod is healthy: `oc get pods -l app=temporal` - -**Script connects but result never comes back** - -The worker is not picking up the task. Check: - -- Worker pod is running: `oc get pods -l app=temporal-worker` -- Worker logs show "Worker started, polling for tasks": `oc logs -l app=temporal-worker --tail=20` -- Worker image was rebuilt after the sample workflow was added (see Prerequisites above) -- `TEMPORAL_TASK_QUEUE` is `soba` in both the worker Deployment and the script (default is `soba`) - -**Workflow shows as "Running" in UI but never completes** - -The worker started the task but the activity is stuck. Check worker logs for errors: - -```bash -oc logs -l app=temporal-worker --tail=50 -``` - -**`TEMPORAL_ALLOWED` is false** - -The script connects directly using `@temporalio/client` and does not check `TEMPORAL_ALLOWED` — that flag only gates the worker process and the Express `getClient()` helper. The test script works regardless of that setting. From 2c9f43a4c4bce0bb654086d2e816a50e0cd7bc57 Mon Sep 17 00:00:00 2001 From: Navdeep Kaur Date: Fri, 17 Apr 2026 10:35:31 -0700 Subject: [PATCH 10/11] fix: add empty export to Temporal barrel files --- backend/src/temporal/activities/index.ts | 1 + backend/src/temporal/workflows/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/backend/src/temporal/activities/index.ts b/backend/src/temporal/activities/index.ts index 3bf6ea7..55e07be 100644 --- a/backend/src/temporal/activities/index.ts +++ b/backend/src/temporal/activities/index.ts @@ -1 +1,2 @@ // Temporal activities barrel — add activity exports here +export {}; diff --git a/backend/src/temporal/workflows/index.ts b/backend/src/temporal/workflows/index.ts index 8ee0354..01bb348 100644 --- a/backend/src/temporal/workflows/index.ts +++ b/backend/src/temporal/workflows/index.ts @@ -1 +1,2 @@ // Temporal workflows barrel — add workflow exports here +export {}; From 9125dcabccde7b560edf09060ba01ddf53a2e102 Mon Sep 17 00:00:00 2001 From: sandeep-aot <89100322+sandeep-aot@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:43:51 -0700 Subject: [PATCH 11/11] Update temporal docs for openshift deployment Update temporal docs for openshift deployment --- docs/temporal-devops.md | 490 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 docs/temporal-devops.md diff --git a/docs/temporal-devops.md b/docs/temporal-devops.md new file mode 100644 index 0000000..be7d5ef --- /dev/null +++ b/docs/temporal-devops.md @@ -0,0 +1,490 @@ +# Temporal on OpenShift for SOBA + +This document describes how Temporal is currently installed and used for SOBA in the OpenShift namespace pattern `acf456-xx` and what must change when the newer upstream Temporal Helm chart line moves from release candidate to the supported stable release. + +This guide is intended for: + +- Developers working on CI/CD, Helm, or Temporal worker code +- Testers validating PR and `develop` deployments +- Platform operators troubleshooting Temporal inside the shared namespace + +Examples below use `acf456-dev`, but the same deployment pattern applies to the `acf456-xx` namespace family. + +## Executive Summary + +SOBA currently uses a shared, namespace-level Temporal server plus per-application Temporal workers: + +- One shared Temporal release named `temporal` is installed into the OpenShift namespace +- Each PR gets its own Temporal namespace such as `soba-pr-46` +- Each SOBA release gets its own application worker deployment such as `soba-pr-46-temporal-worker` +- The shared Temporal server uses Crunchy Postgres databases `temporal` and `temporal_visibility` +- PR cleanup deletes the PR-specific Temporal namespace and SOBA release, but keeps the shared Temporal server in place + +Today, CI installs Temporal from the published Helm repository using chart `0.74.0`. The checked-out `helm-charts` repository in the workspace is not what CI deploys today; it is the upstream newer chart line and should be treated as the migration reference for the next upgrade. + +## Current Deployment Model + +### Shared components per OpenShift namespace + +The shared Temporal deployment is installed once per namespace and is reused by all PR releases in that namespace. + +Current shared Temporal services and pods are expected to include: + +- `temporal-frontend` +- `temporal-history` +- `temporal-matching` +- `temporal-worker` for the Temporal server internals, not the SOBA application worker +- `temporal-web` +- `temporal-admintools` +- schema jobs created by the chart + +### Per-PR SOBA components + +For each pull request deployment, SOBA adds: + +- `soba-pr--backend` +- `soba-pr--frontend` +- `soba-pr--formio` +- `soba-pr--mongodb` +- `soba-pr--temporal-worker` + +Each PR also gets a dedicated Temporal namespace inside the shared server: + +- `soba-pr-45` +- `soba-pr-46` +- and so on + +This design keeps Temporal infrastructure shared while isolating workflow state by namespace. + +## What Has Already Been Implemented + +### 1. PR deployments now install shared Temporal automatically + +The PR deployment workflow in `.github/workflows/pr_open.yaml` does all of the following: + +- Builds SOBA images +- Deploys or reconciles Crunchy Postgres +- Runs `helm upgrade --install temporal temporal` against `https://go.temporal.io/helm-charts` +- Uses `deployments/helm/temporal/values.yaml` for the Temporal values +- Waits for Temporal schema jobs and `temporal-frontend` +- Ensures the PR-specific Temporal namespace exists +- Deploys the SOBA Helm release with the Temporal address and namespace injected +- Waits for the SOBA Temporal worker rollout + +This means a PR deploy is no longer just an app deployment. It is also responsible for ensuring the shared Temporal control plane exists in the target namespace. + +### 2. SOBA has a dedicated Temporal worker image + +SOBA no longer relies on the backend image for Temporal worker execution. A dedicated worker image is built from `backend/Dockerfile.temporal-worker`. + +Why this was done: + +- The Temporal TypeScript SDK depends on `@temporalio/core-bridge` +- That native dependency requires a glibc-based runtime +- Alpine-based images are not reliable for this workload + +What the dedicated worker image does: + +- Uses `node:lts-slim` +- Installs backend dependencies +- Builds the backend TypeScript output +- Starts `backend/dist/temporal-worker.js` +- Remains compatible with OpenShift arbitrary UID behavior + +This is the key application-side change that made the SOBA worker stable on OpenShift. + +### 3. Image build and publish now include `temporal-worker` + +The reusable workflow `.github/workflows/build-images.yaml` now builds and publishes three image families: + +- `backend` +- `frontend` +- `temporal-worker` + +For each image family, the workflow: + +- builds per-platform images +- uploads digests +- merges them into a final manifest image +- tags images with `sha-` +- tags environment-oriented aliases such as `pr-` or `develop` + +### 4. SOBA Helm values support Temporal worker deployment + +The SOBA Helm chart under `deployments/helm/soba` now includes: + +- a `temporal` config block for shared connection settings +- a `temporalWorker` block for the dedicated worker deployment +- a `templates/temporal/configmap.yaml` that injects: + - `TEMPORAL_ALLOWED` + - `TEMPORAL_ADDRESS` + - `TEMPORAL_NAMESPACE` + - `TEMPORAL_TASK_QUEUE` +- a `templates/temporal/deployment-worker.yaml` that deploys the SOBA worker as its own Deployment + +Current enablement pattern: + +- `values.yaml`: disabled by default +- `values-pr.yaml`: enabled +- `values-dev.yaml`: enabled + +### 5. PR cleanup removes PR-specific Temporal namespaces + +The cleanup workflow `.github/workflows/pr_close.yaml` now: + +- deletes the Temporal namespace `soba-pr-` through `temporal-admintools` +- uninstalls the PR-specific SOBA Helm release +- removes the PR-specific Crunchy user and database resources + +It does not uninstall the shared `temporal` release. That is intentional. + +## Current Shared Temporal Server Configuration + +The file `deployments/helm/temporal/values.yaml` defines the current Temporal server deployment used by PR CI. + +### Current chart and image line + +- Helm repo: `https://go.temporal.io/helm-charts` +- Chart version in CI today: `0.74.0` by default +- Release name: `temporal` +- Server image: `temporalio/server:1.30.3` + +The workflow allows override via the repository variable `TEMPORAL_CHART_VERSION`, but defaults to `0.74.0`. + +### Current persistence model + +Temporal uses Crunchy Postgres with two SQL databases: + +- `temporal` +- `temporal_visibility` + +Current connection characteristics: + +- host: `pg-soba-crunchy-primary` +- port: `5432` +- user: `temporal` +- secret: `pg-soba-crunchy-pguser-temporal` + +### Current non-bundled dependency posture + +The current values explicitly disable bundled sub-chart style dependencies: + +- Cassandra +- MySQL +- Elasticsearch +- Prometheus +- Grafana + +That matches the current SOBA platform model where external platform services are used instead of chart-managed databases and monitoring stacks. + +### Current server features enabled + +- Temporal Web UI enabled +- Temporal AdminTools enabled +- schema creation and update jobs enabled +- database creation disabled + +This is consistent with: + +- platform-managed Postgres databases and credentials +- chart-managed Temporal schema setup and schema upgrades + +## Current PR Deployment Flow + +The current PR flow is: + +1. Build `backend`, `frontend`, and `temporal-worker` images +2. Reconcile Crunchy Postgres +3. Install or upgrade the shared `temporal` release +4. Wait for Temporal to finish schema work and come up +5. Create or confirm the Temporal namespace `soba-pr-` +6. Deploy the SOBA release +7. Inject: + - `temporal.address = temporal-frontend..svc.cluster.local:7233` + - `temporal.namespace = soba-pr-` + - `temporalWorker.image.repository = ghcr.io//temporal-worker` + - `temporalWorker.image.tag = sha-` +8. Wait for `backend`, `frontend`, `formio`, `mongodb`, and `temporal-worker` +9. Run Playwright tests + +This is the most complete and correct Temporal deployment path in the repository today. + +## Current Gaps and Operational Notes + +### 1. `develop` deployment is not yet at PR parity + +The `develop` deployment workflow in `.github/workflows/on-merge.yaml` currently builds images and deploys SOBA, but it does not yet mirror the full PR Temporal flow. + +Important gaps: + +- it does not install or upgrade the shared `temporal` release +- it does not ensure the `soba-dev` Temporal namespace exists +- it does not override the `temporalWorker` image tag to the just-built SHA +- it does not wait for the SOBA Temporal worker rollout + +Because `values-dev.yaml` enables the worker, the dev deployment currently depends on: + +- a pre-existing shared Temporal installation +- whatever `temporalWorker.image.tag` resolves to in values rather than the just-built image + +This should be fixed independently of the future chart upgrade. + +### 2. Secret rotation requires pod restarts + +During troubleshooting in `acf456-dev`, Temporal returned `503` in the Web UI because `temporal-frontend` and later `temporal-matching` were still running with stale database credentials after the Postgres secret changed. + +Symptoms included: + +- `pq: password authentication failed for user "temporal"` +- Temporal Web UI `503` from `/api/v1/namespaces` +- `Not enough hosts to serve the request` + +Operational fix: + +- restart `deployment/temporal-frontend` +- restart `deployment/temporal-matching` + +Recommendation: + +- document this as the first operational response after any Temporal DB password rotation +- consider adding an explicit operational runbook section for stale-secret recovery + +### 3. The shared server is namespace-scoped, not cluster-global + +This documentation should keep emphasizing that Temporal is shared within an OpenShift namespace, not across all namespaces. `acf456-dev` has its own shared Temporal release. Another namespace would require its own shared deployment unless platform architecture changes. + +## What the Local `helm-charts` Repo Means + +The checked-out repository at `../helm-charts` is important, but it is not the chart source used by current CI. + +What it tells us: + +- the upstream chart in that checkout is currently `1.0.0-rc.3` +- its `appVersion` is `1.30.2` +- it introduces a new persistence values structure +- it removes support for the old top-level database and monitoring keys + +This repo should be treated as the upgrade target and migration reference, not as proof that SOBA already deploys the new chart line. + +## What Must Change When the RC Chart Becomes the Supported Stable Release + +Once the current upstream RC line becomes the supported stable release, the work is not just to bump the version. The current SOBA values file is still written in the old chart format. + +### Required change 1. Update the chart version used by CI + +Current state: + +- PR CI defaults to `0.74.0` + +Next step: + +- update the repository variable `TEMPORAL_CHART_VERSION` or the workflow default in `.github/workflows/pr_open.yaml` +- move to the chosen stable `1.0.x` release after non-production validation + +### Required change 2. Migrate `deployments/helm/temporal/values.yaml` to the new persistence format + +The new chart expects persistence under: + +`server.config.persistence.datastores` + +The current SOBA values file still uses the old structure: + +- `server.config.persistence.default` +- `server.config.persistence.visibility` +- old SQL field names such as `driver`, `host`, `port`, and `database` + +The new chart expects SQL datastores shaped more like: + +```yaml +server: + image: + repository: temporalio/server + tag: 1.30.3 + config: + persistence: + defaultStore: default + visibilityStore: visibility + datastores: + default: + sql: + createDatabase: false + manageSchema: true + pluginName: postgres12 + driverName: postgres12 + databaseName: temporal + connectAddr: "pg-soba-crunchy-primary:5432" + user: temporal + existingSecret: pg-soba-crunchy-pguser-temporal + maxConns: 20 + maxIdleConns: 20 + maxConnLifetime: "1h" + visibility: + sql: + createDatabase: false + manageSchema: true + pluginName: postgres12 + driverName: postgres12 + databaseName: temporal_visibility + connectAddr: "pg-soba-crunchy-primary:5432" + user: temporal + existingSecret: pg-soba-crunchy-pguser-temporal + maxConns: 20 + maxIdleConns: 20 + maxConnLifetime: "1h" +``` + +This is the most important migration task. + +### Required change 3. Remove deprecated top-level keys entirely + +The new chart line does not accept the old top-level keys for bundled dependencies. + +These keys must be removed from `deployments/helm/temporal/values.yaml`, not merely left present with `enabled: false`: + +- `cassandra` +- `mysql` +- `elasticsearch` +- `prometheus` +- `grafana` + +Why this matters: + +- the newer chart validates deprecations during template rendering +- leaving these keys in place can fail the install or upgrade even when they are set to disabled + +### Required change 4. Replace old schema flags with datastore-level schema management + +Current SOBA values use: + +```yaml +schema: + createDatabase: + enabled: false + setup: + enabled: true + update: + enabled: true +``` + +In the newer chart line: + +- schema job resources still live under top-level `schema` +- schema execution behavior is controlled per datastore using: + - `createDatabase` + - `manageSchema` + +For SOBA, the likely target is: + +- `createDatabase: false` +- `manageSchema: true` + +That keeps database ownership with Crunchy while allowing the chart to apply Temporal schema migrations. + +### Required change 5. Disable compatibility shims for Temporal 1.30+ + +The newer chart line includes compatibility shims intended for 1.29-era images. + +Because SOBA is already using Temporal `1.30.x`, the future stable values should explicitly review and likely set: + +```yaml +shims: + dockerize: false + elasticsearchTool: false +``` + +This should be validated during the upgrade rehearsal. + +### Required change 6. Re-test PR deployment and cleanup end to end + +When the stable chart version is adopted, validate all of the following in a non-production namespace: + +- shared Temporal install succeeds +- schema jobs complete successfully +- `temporal-frontend`, `temporal-history`, `temporal-matching`, `temporal-web`, `temporal-admintools`, and Temporal internal `worker` all come up +- PR namespace creation through `temporal-admintools` still works +- `soba-pr--temporal-worker` connects and polls successfully +- the Web UI returns `200` from `/api/v1/namespaces` +- PR cleanup still deletes the PR-specific Temporal namespace cleanly + +### Required change 7. Bring `develop` deployment up to the same standard + +This is not strictly tied to the stable chart release, but it should be done as part of the same effort so dev behavior matches PR behavior. + +`on-merge.yaml` should be updated to: + +- install or reconcile the shared `temporal` release +- ensure `soba-dev` exists as a Temporal namespace +- inject the built `temporal-worker` image tag into the Helm override +- wait for `soba-dev-temporal-worker` rollout + +Without this, developers and testers can see different behavior in PR versus dev. + +## Recommended Rollout Plan for the Stable Chart Upgrade + +1. Create a branch that only updates the Temporal chart version and `deployments/helm/temporal/values.yaml`. +2. Convert the values file to the new `datastores` structure. +3. Remove deprecated top-level keys. +4. Review shims for Temporal `1.30.x`. +5. Test the branch in a disposable non-production namespace. +6. Verify Web UI, PR namespace creation, worker polling, and cleanup behavior. +7. After validation, update `TEMPORAL_CHART_VERSION` to the stable version. +8. Update `on-merge.yaml` so dev matches PR behavior. + +## Verification Commands + +### Shared Temporal health + +```bash +oc get pods -n acf456-dev | grep temporal +oc logs -n acf456-dev deployment/temporal-frontend --since=5m +oc logs -n acf456-dev deployment/temporal-matching --since=5m +``` + +### PR namespace validation + +```bash +oc exec deployment/temporal-admintools -n acf456-dev -- \ + temporal operator namespace list + +oc exec deployment/temporal-admintools -n acf456-dev -- \ + temporal operator namespace describe -n soba-pr-46 +``` + +### Web UI validation + +```bash +oc port-forward -n acf456-dev svc/temporal-web 8088:8080 +curl -i http://localhost:8088/api/v1/namespaces +``` + +### SOBA worker validation + +```bash +oc get deployment -n acf456-dev | grep temporal-worker +oc logs deployment/soba-pr-46-temporal-worker -n acf456-dev --since=5m +``` + +Healthy worker logs should show: + +- connection to the configured Temporal frontend +- the expected namespace +- the expected task queue +- `Worker started, polling for tasks` + +## Final Recommendation + +The current OpenShift Temporal implementation is partly complete and already good enough for PR-based testing: + +- shared Temporal server deployment exists +- PR namespace lifecycle exists +- dedicated SOBA worker image exists +- PR deployment path is mostly correct + +The next meaningful work is not new feature work. It is upgrade hardening: + +- migrate the Temporal values file to the new chart format +- adopt the stable `1.0.x` chart line after validation +- bring `develop` deployment to feature parity with PR deployment +- document secret-rotation recovery for `temporal-frontend` and `temporal-matching` + +Once those items are done, the Temporal OpenShift story for SOBA will be much easier for developers and testers to understand, operate, and trust.