From 5d6bf6a3d210db1f257a2f7e116f47a886eb754f Mon Sep 17 00:00:00 2001 From: Fred Amaral Date: Thu, 2 Jul 2026 19:40:43 -0700 Subject: [PATCH] feat(charts): add br-spi-helm chart for the SPI rail Backfills develop with the br-spi-helm chart, which landed on main via #1607 (direct-to-main) and was therefore absent from the develop prerelease channel. Scope is limited to charts/br-spi + its render fixture; the pre-existing main/develop drift in other charts is untouched. Claude-Session: https://claude.ai/code/session_012fNphWnucMKd84KskVFgvz Signed-off-by: Fred Amaral --- .../configs/helm-render-values/br-spi.yaml | 38 ++ charts/br-spi/Chart.lock | 9 + charts/br-spi/Chart.yaml | 49 ++ charts/br-spi/README.md | 73 +++ charts/br-spi/templates/_component.tpl | 422 +++++++++++++++++ charts/br-spi/templates/_helpers.tpl | 132 ++++++ charts/br-spi/templates/brcode/configmap.yaml | 1 + .../br-spi/templates/brcode/deployment.yaml | 1 + charts/br-spi/templates/brcode/hpa.yaml | 1 + charts/br-spi/templates/brcode/ingress.yaml | 1 + charts/br-spi/templates/brcode/pdb.yaml | 1 + charts/br-spi/templates/brcode/secrets.yaml | 1 + charts/br-spi/templates/brcode/service.yaml | 1 + charts/br-spi/templates/common/NOTES.txt | 31 ++ .../templates/common/serviceaccount.yaml | 18 + charts/br-spi/templates/core/configmap.yaml | 1 + charts/br-spi/templates/core/deployment.yaml | 1 + charts/br-spi/templates/core/hpa.yaml | 1 + charts/br-spi/templates/core/ingress.yaml | 1 + charts/br-spi/templates/core/pdb.yaml | 1 + charts/br-spi/templates/core/secrets.yaml | 1 + charts/br-spi/templates/core/service.yaml | 1 + charts/br-spi/templates/dict/configmap.yaml | 1 + charts/br-spi/templates/dict/deployment.yaml | 1 + charts/br-spi/templates/dict/hpa.yaml | 1 + charts/br-spi/templates/dict/ingress.yaml | 1 + charts/br-spi/templates/dict/pdb.yaml | 1 + charts/br-spi/templates/dict/secrets.yaml | 1 + charts/br-spi/templates/dict/service.yaml | 1 + charts/br-spi/templates/migrations/job.yaml | 105 +++++ .../br-spi/templates/migrations/secrets.yaml | 24 + charts/br-spi/templates/spi/configmap.yaml | 1 + charts/br-spi/templates/spi/deployment.yaml | 1 + charts/br-spi/templates/spi/hpa.yaml | 1 + charts/br-spi/templates/spi/ingress.yaml | 1 + charts/br-spi/templates/spi/pdb.yaml | 1 + charts/br-spi/templates/spi/secrets.yaml | 1 + charts/br-spi/templates/spi/service.yaml | 1 + charts/br-spi/values-template.yaml | 61 +++ charts/br-spi/values.schema.json | 95 ++++ charts/br-spi/values.yaml | 443 ++++++++++++++++++ 41 files changed, 1528 insertions(+) create mode 100644 .github/configs/helm-render-values/br-spi.yaml create mode 100644 charts/br-spi/Chart.lock create mode 100644 charts/br-spi/Chart.yaml create mode 100644 charts/br-spi/README.md create mode 100644 charts/br-spi/templates/_component.tpl create mode 100644 charts/br-spi/templates/_helpers.tpl create mode 100644 charts/br-spi/templates/brcode/configmap.yaml create mode 100644 charts/br-spi/templates/brcode/deployment.yaml create mode 100644 charts/br-spi/templates/brcode/hpa.yaml create mode 100644 charts/br-spi/templates/brcode/ingress.yaml create mode 100644 charts/br-spi/templates/brcode/pdb.yaml create mode 100644 charts/br-spi/templates/brcode/secrets.yaml create mode 100644 charts/br-spi/templates/brcode/service.yaml create mode 100644 charts/br-spi/templates/common/NOTES.txt create mode 100644 charts/br-spi/templates/common/serviceaccount.yaml create mode 100644 charts/br-spi/templates/core/configmap.yaml create mode 100644 charts/br-spi/templates/core/deployment.yaml create mode 100644 charts/br-spi/templates/core/hpa.yaml create mode 100644 charts/br-spi/templates/core/ingress.yaml create mode 100644 charts/br-spi/templates/core/pdb.yaml create mode 100644 charts/br-spi/templates/core/secrets.yaml create mode 100644 charts/br-spi/templates/core/service.yaml create mode 100644 charts/br-spi/templates/dict/configmap.yaml create mode 100644 charts/br-spi/templates/dict/deployment.yaml create mode 100644 charts/br-spi/templates/dict/hpa.yaml create mode 100644 charts/br-spi/templates/dict/ingress.yaml create mode 100644 charts/br-spi/templates/dict/pdb.yaml create mode 100644 charts/br-spi/templates/dict/secrets.yaml create mode 100644 charts/br-spi/templates/dict/service.yaml create mode 100644 charts/br-spi/templates/migrations/job.yaml create mode 100644 charts/br-spi/templates/migrations/secrets.yaml create mode 100644 charts/br-spi/templates/spi/configmap.yaml create mode 100644 charts/br-spi/templates/spi/deployment.yaml create mode 100644 charts/br-spi/templates/spi/hpa.yaml create mode 100644 charts/br-spi/templates/spi/ingress.yaml create mode 100644 charts/br-spi/templates/spi/pdb.yaml create mode 100644 charts/br-spi/templates/spi/secrets.yaml create mode 100644 charts/br-spi/templates/spi/service.yaml create mode 100644 charts/br-spi/values-template.yaml create mode 100644 charts/br-spi/values.schema.json create mode 100644 charts/br-spi/values.yaml diff --git a/.github/configs/helm-render-values/br-spi.yaml b/.github/configs/helm-render-values/br-spi.yaml new file mode 100644 index 000000000..0aca4babd --- /dev/null +++ b/.github/configs/helm-render-values/br-spi.yaml @@ -0,0 +1,38 @@ +# CI-only render fixture for br-spi. Dummy values, NOT a production example and +# NOT real credentials. Exercises the external-infra path (the target-environment +# reality): all four components + the migration Job enabled, Postgres/Redis +# external. The declared Bitnami subcharts stay disabled; the render gate still +# runs the release-name collapse renders against them. +core: + configmap: + POSTGRES_HOST: "pg.example.svc" + STREAMING_BROKERS: "kafka-dummy:9092" + secrets: + POSTGRES_PASSWORD: "dummy-ci-password" + +spi: + configmap: + POSTGRES_HOST: "pg.example.svc" + STREAMING_BROKERS: "kafka-dummy:9092" + BACEN_SPI_ENDPOINT: "http://mock-bacen.example.svc:8080" + secrets: + POSTGRES_PASSWORD: "dummy-ci-password" + +brcode: + configmap: + POSTGRES_HOST: "pg.example.svc" + secrets: + POSTGRES_PASSWORD: "dummy-ci-password" + +dict: + configmap: + POSTGRES_HOST: "pg.example.svc" + REDIS_HOST: "redis.example.svc:6379" + secrets: + POSTGRES_PASSWORD: "dummy-ci-password" + REDIS_PASSWORD: "dummy-ci-redis" + +migrations: + enabled: true + postgres: + host: "pg.example.svc" diff --git a/charts/br-spi/Chart.lock b/charts/br-spi/Chart.lock new file mode 100644 index 000000000..51ae192cc --- /dev/null +++ b/charts/br-spi/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 16.3.5 +- name: valkey + repository: oci://registry-1.docker.io/bitnamicharts + version: 2.4.7 +digest: sha256:52ff7322d903f193a03cdb12e017ea953e3f7c06cd6019216410086fef3bcd6b +generated: "2026-07-02T18:30:40.65224-07:00" diff --git a/charts/br-spi/Chart.yaml b/charts/br-spi/Chart.yaml new file mode 100644 index 000000000..a0532300c --- /dev/null +++ b/charts/br-spi/Chart.yaml @@ -0,0 +1,49 @@ +apiVersion: v2 +name: br-spi-helm +description: A Helm chart for the br-sfn SPI (Pix) rail — the four Go binaries (core, spi, brcode, dict) that connect an FI to BACEN SPI/DICT/Pix and emit settlement CloudEvents onto a Redpanda bus + +type: application +annotations: + lerian.studio/chart-type: multi-component + +home: https://github.com/LerianStudio/helm + +sources: + - https://github.com/LerianStudio/helm/tree/main/charts/br-spi + - https://github.com/LerianStudio/br-spi + +maintainers: + - name: "Lerian Studio" + email: "support@lerian.studio" + +version: 0.1.0-beta.1 + +# NOTE: appVersion is the default image tag for every component and the +# migration Job. Override per component via .image.tag when a +# component needs to pin a different build. +appVersion: "0.1.0" + +keywords: + - br-spi + - spi + - pix + - bacen + - dict + - lerian + - settlement + +icon: https://avatars.githubusercontent.com/u/148895005?s=200&v=4 + +# Bitnami subcharts are DECLARED (exact pins, condition-gated) so the chart can +# optionally bundle infra, but they default to disabled: on the target +# environments Postgres and Redis are EXTERNAL and pre-provisioned. Chart.lock is +# committed; re-verify Secret name/key against `helm template` on any pin bump. +dependencies: + - name: postgresql + version: "16.3.5" + repository: "https://charts.bitnami.com/bitnami" + condition: postgresql.enabled + - name: valkey + version: "2.4.7" + repository: "oci://registry-1.docker.io/bitnamicharts" + condition: valkey.enabled diff --git a/charts/br-spi/README.md b/charts/br-spi/README.md new file mode 100644 index 000000000..5209c1f6f --- /dev/null +++ b/charts/br-spi/README.md @@ -0,0 +1,73 @@ +# br-spi Helm Chart + +Deploys the **br-sfn SPI (Pix) rail** — the four Go binaries (`core`, `spi`, `brcode`, `dict`) that connect a financial institution to BACEN SPI / DICT / Pix — into Kubernetes. When streaming is enabled, `core` becomes a CloudEvents **producer**, emitting `studio.lerian.settlement.*` onto the `br-spi.settlement` topic of a Redpanda/Kafka bus. + +## Chart Contract + +- Chart type: `multi-component` +- Required secrets: **None for a default render.** For external Postgres (the default), provide each component's `POSTGRES_PASSWORD` (all four share the single `brspi` database) via `.secrets.POSTGRES_PASSWORD` or `.useExistingSecret`; on the target environments this is sourced from GitOps/Vault. `dict` additionally needs `REDIS_PASSWORD` when its external Redis requires auth. When the bundled `postgresql`/`valkey` subcharts are enabled instead, those passwords are **single-sourced** from the subchart Secrets (read at runtime via `secretKeyRef`) and are not stored in the app Secrets. No credential is ever placed in a ConfigMap. Optional at-rest / PII keys added under `.secrets` are emitted only when set. +- Dependency notes: Bundled `postgresql` (16.3.5) and `valkey` (2.4.7) Bitnami subcharts are **declared but disabled by default** — Postgres and Redis are external and pre-provisioned on the target environments. Kafka/Redpanda is external and referenced only by `STREAMING_BROKERS` (plaintext; no SASL/TLS knobs). Enable the subcharts only for a self-contained install. +- Production overrides: Set `.image.tag` (defaults to `Chart.appVersion`); `POSTGRES_HOST` and `POSTGRES_PASSWORD` for every enabled component; `STREAMING_BROKERS` for `core` and `spi` (a producer with `STREAMING_ENABLED=true` and empty `STREAMING_BROKERS`/`STREAMING_CLOUDEVENTS_SOURCE` **fails closed at boot** by design); `REDIS_HOST` for `dict`; and per-component `ingress`, `autoscaling`, and `resources` as needed. `useExistingSecret`/`existingSecretName` are supported per component and for the migration Job. +- Source/license: Source is in `github.com/LerianStudio/helm`; chart license is Apache-2.0. The `br-spi` service source is `github.com/LerianStudio/br-spi`. + +## Components + +All four components are the same Go binary family (identical deployment shape). The chart renders them from one shared template library, so env — especially `STREAMING_*` — is emitted uniformly and a component can never silently drop a producer knob. + +| Component | Image (default) | Listens | Streams | Role | +|-----------|-----------------|---------|---------|------| +| `core` | `ghcr.io/lerianstudio/br-sfn-core` | `:8080` | **yes** | Settlement engine; sole producer of `studio.lerian.settlement.*` | +| `spi` | `ghcr.io/lerianstudio/br-sfn-spi` | `:8080` | **yes** | ISO 20022 / BACEN SPI; writes the `spi->core` seam | +| `brcode` | `ghcr.io/lerianstudio/br-sfn-brcode` | `:8080` | no | Pix BR Code (QR) REST | +| `dict` | `ghcr.io/lerianstudio/br-sfn-dict` | `:8080` | no | DICT (Pix directory) REST; **requires `REDIS_HOST`** | + +Each component exposes: `.enabled`, `image.{repository,tag,pullPolicy}`, `replicaCount`, `service`, `ingress`, `autoscaling`, `pdb`, `resources`, `configmap` (map, emitted verbatim), `secrets`, `useExistingSecret`/`existingSecretName`, `extraEnvVars` (raw env list escape). Probes are `/health` (liveness) and `/readyz` (readiness) on the `http` port (containerPort `8080`). + +### Settlement producer coupling + +`core` produces `settlement.*` only by **consuming** the `spi->core` seam topics that `spi` writes. Therefore **both** `core` and `spi` must have `STREAMING_ENABLED=true`, the **same** `STREAMING_BROKERS`, and the **same** Postgres, or no settlement event is emitted at all. `brcode` and `dict` are REST-only and stream `false`. + +## Configuration knobs (ConfigMap passthrough) + +`.configmap` is emitted verbatim into the component ConfigMap — it is **not** a fixed allowlist. Any additional env (BACEN endpoints, Postgres pool tuning, signer/JOSE knobs) can be added under `.configmap` without editing the chart. `STREAMING_ENABLED`, `STREAMING_BROKERS`, `STREAMING_CLOUDEVENTS_SOURCE`, `POSTGRES_HOST`, and `REDIS_HOST` are handled first-class by the template (the hosts derive from the bundled subchart when enabled). Secrets must go under `.secrets`, never `configmap`. + +| Key | Default | Notes | +|-----|---------|-------| +| `ENV_NAME` | `development` | SPI env; `development` relaxes BACEN mTLS + accepts `http://`/mock endpoints | +| `SERVER_ADDRESS` | `:8080` | host:port form (container port is 8080) | +| `POSTGRES_{HOST,PORT,USER,DB,SSLMODE}` | see values | All four share the `brspi` database | +| `REDIS_HOST` | `""` | host:port; **required for `dict`** | +| `OUTBOX_ENABLED` | `true` | keep on for the producer | +| `IDEMPOTENCY_RETRY_WINDOW_SEC` | `300` | `core` only; must be `> 0` | +| `STREAMING_ENABLED` | `true` (core/spi), `false` (brcode/dict) | fail-closed when brokers/source empty | +| `STREAMING_BROKERS` | `""` | CSV; **required when streaming enabled** | +| `STREAMING_CLOUDEVENTS_SOURCE` | `studio.lerian.core` / `studio.lerian.spi` | required when streaming enabled | +| `ENABLE_TELEMETRY` | `false` | when `true`, `OTEL_EXPORTER_OTLP_ENDPOINT` is set to `$(HOST_IP):4317` (gRPC) | +| `OTEL_RESOURCE_SERVICE_NAME` | `br-spi-` | distinct per binary so dashboards don't collapse | + +## Detached migrations + +`migrations.enabled` (default `true`) ships an ArgoCD **PreSync** Secret (`hook-weight: -2`) + Job (`hook-weight: -1`) that runs `ghcr.io/lerianstudio/br-sfn-spi-migrations`. That image's entrypoint applies the 6-module `golang-migrate` loop in manifest order (`global events spi dict brcode core`) to the single `brspi` database, using the per-module `x-migrations-table=schema_migrations_` naming that `/readyz` reads back. `systemplane` is **not** applied by `golang-migrate` — it is managed by lib-systemplane at runtime. br-sfn runtime binaries are detached — they verify the schema on boot and refuse to start unmigrated — so the PreSync Job runs before every component Deployment. The Job pod is hardened (non-root, read-only rootfs, drop ALL, no service-account token) and waits for Postgres via a `busybox` initContainer. Supports `migrations.useExistingSecret`/`existingSecretName`. + +Source code: +* https://github.com/LerianStudio/helm/tree/main/charts/br-spi +* https://github.com/LerianStudio/br-spi + +## Install + +```console +$ helm install br-spi oci://ghcr.io/lerianstudio/br-spi-helm --version 0.1.0 -n br-spi --create-namespace \ + --set core.configmap.POSTGRES_HOST=pg-dev,core.secrets.POSTGRES_PASSWORD=... \ + --set core.configmap.STREAMING_BROKERS=kafka-dev:9092 +``` + +## External vs bundled infrastructure + +Default (external): leave `postgresql.enabled=false` and `valkey.enabled=false`, set `POSTGRES_HOST`/`REDIS_HOST` per component, and provide `POSTGRES_PASSWORD` (and `REDIS_PASSWORD` if the Redis needs auth). + +Bundled (self-contained): set `postgresql.enabled=true` (and `valkey.enabled=true`); the app reads the subchart-generated passwords via `secretKeyRef` (single-sourced), and `POSTGRES_HOST`/`REDIS_HOST` derive from the subchart Service names collapse-aware via `common.names.dependency.fullname`. + +## Support & Community + +- **GitHub Issues**: https://github.com/LerianStudio/br-spi/issues +- **Email**: contact@lerian.studio diff --git a/charts/br-spi/templates/_component.tpl b/charts/br-spi/templates/_component.tpl new file mode 100644 index 000000000..2c505fdf8 --- /dev/null +++ b/charts/br-spi/templates/_component.tpl @@ -0,0 +1,422 @@ +{{/* +============================================================================== +Shared workload library for the four SPI binaries (core / spi / brcode / dict). +All four are the same Go binary family with an identical deployment shape, so +the logic lives here once and each templates//*.yaml is a thin +include. This is deliberate: emitting env uniformly (especially STREAMING_*) +from ONE place removes the "one component silently drops a key" drift hazard. + +Every template takes (dict "context" $ "component" ""). +============================================================================== +*/}} + +{{/* +componentConfigMap — the component's ConfigMap. +STREAMING_* and the derived hosts are emitted first-class; everything else in +.configmap is ranged verbatim (NOT a fixed allowlist — a fixed +allowlist is exactly what makes a producer boot as a silent NoopEmitter). +*/}} +{{- define "br-spi.componentConfigMap" -}} +{{- $ctx := .context -}} +{{- $c := .component -}} +{{- $cv := index $ctx.Values $c -}} +{{- if $cv.enabled -}} +{{- $reserved := list "POSTGRES_HOST" "REDIS_HOST" "STREAMING_ENABLED" "STREAMING_BROKERS" "STREAMING_CLOUDEVENTS_SOURCE" -}} +{{- $pgHost := $cv.configmap.POSTGRES_HOST -}} +{{- if and (not $pgHost) (ne (toString $ctx.Values.postgresql.enabled) "false") -}} +{{- $pgHost = printf "%s.%s.svc.cluster.local." (include "common.names.dependency.fullname" (dict "chartName" "postgresql" "chartValues" $ctx.Values.postgresql "context" $ctx)) (include "global.namespace" $ctx) -}} +{{- end -}} +{{- $redisHost := $cv.configmap.REDIS_HOST -}} +{{- if and (not $redisHost) (ne (toString $ctx.Values.valkey.enabled) "false") -}} +{{- $redisHost = printf "%s-primary.%s.svc.cluster.local.:6379" (include "common.names.dependency.fullname" (dict "chartName" "valkey" "chartValues" $ctx.Values.valkey "context" $ctx)) (include "global.namespace" $ctx) -}} +{{- end -}} +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ include "br-spi.componentFullname" (dict "context" $ctx "component" $c) }} + namespace: {{ include "global.namespace" $ctx }} + labels: + {{- include "br-spi.componentLabels" (dict "context" $ctx "component" $c) | nindent 4 }} +data: + # Streaming (CloudEvents producer knobs). First-class + always emitted: a + # producer with STREAMING_ENABLED=true but empty STREAMING_BROKERS/SOURCE + # fails closed at boot by design — it must never silently degrade to a + # NoopEmitter because the chart dropped these keys. + STREAMING_ENABLED: {{ $cv.configmap.STREAMING_ENABLED | default "false" | quote }} + STREAMING_BROKERS: {{ $cv.configmap.STREAMING_BROKERS | default "" | quote }} + STREAMING_CLOUDEVENTS_SOURCE: {{ $cv.configmap.STREAMING_CLOUDEVENTS_SOURCE | default "" | quote }} + # Datastore hosts (derived from the bundled subchart when enabled, else the + # operator-supplied value; on external infra these come from values/GitOps). + POSTGRES_HOST: {{ $pgHost | default "" | quote }} + REDIS_HOST: {{ $redisHost | default "" | quote }} + {{- range $k, $v := $cv.configmap }} + {{- if not (has $k $reserved) }} + {{ $k }}: {{ $v | quote }} + {{- end }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +componentSecret — the component's Opaque Secret. Minted only when the component +is enabled and not using an existingSecret. Infra passwords are single-sourced +from the Bitnami subchart when bundled (read via secretKeyRef in the Deployment), +so they are emitted here ONLY on the external path (subchart disabled, no +existingSecret). Optional at-rest / PII keys are emitted only when set. +*/}} +{{- define "br-spi.componentSecret" -}} +{{- $ctx := .context -}} +{{- $c := .component -}} +{{- $cv := index $ctx.Values $c -}} +{{- if and $cv.enabled (not $cv.useExistingSecret) -}} +{{- $pg := $ctx.Values.postgresql | default dict -}} +{{- $pgAuth := $pg.auth | default dict -}} +{{- $pgInternal := and (ne (toString $pg.enabled) "false") (not $pg.external) -}} +{{- $vk := $ctx.Values.valkey | default dict -}} +{{- $vkAuth := $vk.auth | default dict -}} +{{- $vkInternal := and (ne (toString $vk.enabled) "false") (not $vk.external) $vkAuth.enabled -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "br-spi.componentFullname" (dict "context" $ctx "component" $c) }} + namespace: {{ include "global.namespace" $ctx }} + labels: + {{- include "br-spi.componentLabels" (dict "context" $ctx "component" $c) | nindent 4 }} +type: Opaque +data: + # PostgreSQL password — single-sourced from the postgresql subchart Secret when + # bundled; emitted here only for external Postgres without an existingSecret. + {{- if and (not $pgInternal) (not $pgAuth.existingSecret) $cv.secrets.POSTGRES_PASSWORD }} + POSTGRES_PASSWORD: {{ $cv.secrets.POSTGRES_PASSWORD | b64enc | quote }} + {{- end }} + # Redis/Valkey password — single-sourced from the valkey subchart Secret when + # bundled; emitted here only for external Redis without an existingSecret. + {{- if and (not $vkInternal) (not $vkAuth.existingSecret) $cv.secrets.REDIS_PASSWORD }} + REDIS_PASSWORD: {{ $cv.secrets.REDIS_PASSWORD | b64enc | quote }} + {{- end }} + {{- range $k, $v := $cv.secrets }} + {{- if and (not (has $k (list "POSTGRES_PASSWORD" "REDIS_PASSWORD"))) $v }} + {{ $k }}: {{ $v | b64enc | quote }} + {{- end }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +componentDeployment — the component's Deployment. +*/}} +{{- define "br-spi.componentDeployment" -}} +{{- $ctx := .context -}} +{{- $c := .component -}} +{{- $cv := index $ctx.Values $c -}} +{{- if $cv.enabled -}} +{{- $fullname := include "br-spi.componentFullname" (dict "context" $ctx "component" $c) -}} +{{- if and $cv.useExistingSecret (not $cv.existingSecretName) -}} +{{- fail (printf "br-spi: %s.existingSecretName must be set when %s.useExistingSecret is true" $c $c) -}} +{{- end -}} +{{- $secretName := ternary $cv.existingSecretName $fullname $cv.useExistingSecret -}} +{{- $pullSecrets := $cv.imagePullSecrets | default $ctx.Values.imagePullSecrets -}} +{{- $telemetry := eq (toString $cv.configmap.ENABLE_TELEMETRY) "true" -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullname }} + namespace: {{ include "global.namespace" $ctx }} + labels: + {{- include "br-spi.componentLabels" (dict "context" $ctx "component" $c) | nindent 4 }} +spec: + revisionHistoryLimit: {{ $cv.revisionHistoryLimit | default 10 }} + {{- if not $cv.autoscaling.enabled }} + replicas: {{ $cv.replicaCount | default 1 }} + {{- end }} + strategy: + type: {{ $cv.deploymentUpdate.type | default "RollingUpdate" }} + {{- if eq ($cv.deploymentUpdate.type | default "RollingUpdate") "RollingUpdate" }} + rollingUpdate: + maxSurge: {{ $cv.deploymentUpdate.maxSurge | default "100%" }} + maxUnavailable: {{ $cv.deploymentUpdate.maxUnavailable | default 0 }} + {{- end }} + selector: + matchLabels: + {{- include "br-spi.componentSelectorLabels" (dict "context" $ctx "component" $c) | nindent 6 }} + template: + metadata: + {{- with $cv.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "br-spi.componentLabels" (dict "context" $ctx "component" $c) | nindent 8 }} + spec: + {{- with $pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "br-spi.serviceAccountName" $ctx }} + securityContext: + {{- toYaml $ctx.Values.podSecurityContext | nindent 8 }} + initContainers: + - name: wait-for-dependencies + image: {{ $cv.waitImage | default "busybox" }} + envFrom: + - configMapRef: + name: {{ $fullname }} + command: + - /bin/sh + - -c + - > + if [ -n "$POSTGRES_HOST" ]; then + echo "waiting for postgres $POSTGRES_HOST:${POSTGRES_PORT:-5432}..."; + until nc -z "$POSTGRES_HOST" "${POSTGRES_PORT:-5432}"; do + echo "postgres not ready, waiting..."; sleep 5; + done; + echo "postgres is ready"; + fi; + if [ -n "$REDIS_HOST" ]; then + RH=$(echo "$REDIS_HOST" | cut -d: -f1); + RP=$(echo "$REDIS_HOST" | cut -d: -f2); + [ -z "$RP" ] && RP=6379; + echo "waiting for redis $RH:$RP..."; + until nc -z "$RH" "$RP"; do + echo "redis not ready, waiting..."; sleep 5; + done; + echo "redis is ready"; + fi; + echo "dependencies ready" + securityContext: + {{- toYaml $ctx.Values.securityContext | nindent 12 }} + containers: + - name: {{ $fullname }} + securityContext: + {{- toYaml $ctx.Values.securityContext | nindent 12 }} + image: "{{ $cv.image.repository }}:{{ $cv.image.tag | default $ctx.Chart.AppVersion }}" + imagePullPolicy: {{ $cv.image.pullPolicy | default "IfNotPresent" }} + envFrom: + - secretRef: + name: {{ $secretName }} + - configMapRef: + name: {{ $fullname }} + env: + {{- $pg := $ctx.Values.postgresql | default dict }} + {{- $pgAuth := $pg.auth | default dict }} + {{- if or (and (ne (toString $pg.enabled) "false") (not $pg.external)) $pgAuth.existingSecret }} + {{- include "br-spi.infraSecretRef" (dict "context" $ctx "subchart" "postgresql" "key" "password" "envName" "POSTGRES_PASSWORD") | nindent 12 }} + {{- else if $cv.secrets.POSTGRES_PASSWORD }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: POSTGRES_PASSWORD + {{- end }} + {{- $vk := $ctx.Values.valkey | default dict }} + {{- $vkAuth := $vk.auth | default dict }} + {{- if or (and (ne (toString $vk.enabled) "false") (not $vk.external) $vkAuth.enabled) $vkAuth.existingSecret }} + {{- include "br-spi.infraSecretRef" (dict "context" $ctx "subchart" "valkey" "key" "valkey-password" "envName" "REDIS_PASSWORD") | nindent 12 }} + {{- else if $cv.secrets.REDIS_PASSWORD }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: REDIS_PASSWORD + {{- end }} + {{- if $telemetry }} + - name: HOST_IP + valueFrom: + fieldRef: + fieldPath: status.hostIP + # SPI exports OTLP over gRPC on 4317 (no scheme), to the node-local collector. + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: "$(HOST_IP):4317" + {{- end }} + {{- with $cv.extraEnvVars }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: {{ $cv.livenessProbe.path | default "/health" }} + port: http + initialDelaySeconds: {{ $cv.livenessProbe.initialDelaySeconds | default 15 }} + periodSeconds: {{ $cv.livenessProbe.periodSeconds | default 20 }} + timeoutSeconds: {{ $cv.livenessProbe.timeoutSeconds | default 5 }} + successThreshold: {{ $cv.livenessProbe.successThreshold | default 1 }} + failureThreshold: {{ $cv.livenessProbe.failureThreshold | default 3 }} + readinessProbe: + httpGet: + path: {{ $cv.readinessProbe.path | default "/readyz" }} + port: http + initialDelaySeconds: {{ $cv.readinessProbe.initialDelaySeconds | default 5 }} + periodSeconds: {{ $cv.readinessProbe.periodSeconds | default 10 }} + timeoutSeconds: {{ $cv.readinessProbe.timeoutSeconds | default 5 }} + successThreshold: {{ $cv.readinessProbe.successThreshold | default 1 }} + failureThreshold: {{ $cv.readinessProbe.failureThreshold | default 3 }} + resources: + {{- toYaml $cv.resources | nindent 12 }} + {{- with $cv.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with $cv.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with $cv.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +componentService — the component's ClusterIP Service. +*/}} +{{- define "br-spi.componentService" -}} +{{- $ctx := .context -}} +{{- $c := .component -}} +{{- $cv := index $ctx.Values $c -}} +{{- if $cv.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "br-spi.componentFullname" (dict "context" $ctx "component" $c) }} + namespace: {{ include "global.namespace" $ctx }} + labels: + {{- include "br-spi.componentLabels" (dict "context" $ctx "component" $c) | nindent 4 }} + {{- with $cv.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ $cv.service.type | default "ClusterIP" }} + ports: + - port: {{ $cv.service.port | default 8080 }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "br-spi.componentSelectorLabels" (dict "context" $ctx "component" $c) | nindent 4 }} +{{- end -}} +{{- end -}} + +{{/* +componentIngress — optional per-component Ingress (disabled by default). +*/}} +{{- define "br-spi.componentIngress" -}} +{{- $ctx := .context -}} +{{- $c := .component -}} +{{- $cv := index $ctx.Values $c -}} +{{- if and $cv.enabled $cv.ingress.enabled -}} +{{- $svcName := include "br-spi.componentFullname" (dict "context" $ctx "component" $c) -}} +{{- $svcPort := $cv.service.port | default 8080 -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $svcName }} + namespace: {{ include "global.namespace" $ctx }} + labels: + {{- include "br-spi.componentLabels" (dict "context" $ctx "component" $c) | nindent 4 }} + {{- with $cv.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with $cv.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- with $cv.ingress.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range $cv.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType | default "Prefix" }} + backend: + service: + name: {{ $svcName }} + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +componentHPA — optional per-component HorizontalPodAutoscaler. +*/}} +{{- define "br-spi.componentHPA" -}} +{{- $ctx := .context -}} +{{- $c := .component -}} +{{- $cv := index $ctx.Values $c -}} +{{- if and $cv.enabled $cv.autoscaling.enabled -}} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "br-spi.componentFullname" (dict "context" $ctx "component" $c) }} + namespace: {{ include "global.namespace" $ctx }} + labels: + {{- include "br-spi.componentLabels" (dict "context" $ctx "component" $c) | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "br-spi.componentFullname" (dict "context" $ctx "component" $c) }} + minReplicas: {{ $cv.autoscaling.minReplicas | default 1 }} + maxReplicas: {{ $cv.autoscaling.maxReplicas | default 3 }} + metrics: + {{- with $cv.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ . }} + {{- end }} + {{- with $cv.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ . }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +componentPDB — optional per-component PodDisruptionBudget. +*/}} +{{- define "br-spi.componentPDB" -}} +{{- $ctx := .context -}} +{{- $c := .component -}} +{{- $cv := index $ctx.Values $c -}} +{{- if and $cv.enabled $cv.pdb.enabled -}} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "br-spi.componentFullname" (dict "context" $ctx "component" $c) }} + namespace: {{ include "global.namespace" $ctx }} + labels: + {{- include "br-spi.componentLabels" (dict "context" $ctx "component" $c) | nindent 4 }} + {{- with $cv.pdb.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if $cv.pdb.maxUnavailable }} + maxUnavailable: {{ $cv.pdb.maxUnavailable }} + {{- else }} + minAvailable: {{ $cv.pdb.minAvailable | default 1 }} + {{- end }} + selector: + matchLabels: + {{- include "br-spi.componentSelectorLabels" (dict "context" $ctx "component" $c) | nindent 6 }} +{{- end -}} +{{- end -}} diff --git a/charts/br-spi/templates/_helpers.tpl b/charts/br-spi/templates/_helpers.tpl new file mode 100644 index 000000000..ff1c90cf9 --- /dev/null +++ b/charts/br-spi/templates/_helpers.tpl @@ -0,0 +1,132 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "br-spi.name" -}} +{{- default "br-spi" .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +Truncated at 63 chars because some Kubernetes name fields are limited by the DNS spec. +*/}} +{{- define "br-spi.fullname" -}} +{{- default (include "br-spi.name" .) .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "br-spi.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Expand the namespace of the release. Overridable for multi-namespace layouts. +*/}} +{{- define "global.namespace" -}} +{{- default .Release.Namespace .Values.namespaceOverride | trunc 63 | trimSuffix "-" -}} +{{- end }} + +{{/* +Name of the service account to use (single, shared across all components). +*/}} +{{- define "br-spi.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "br-spi.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +============================================================================== +Component helpers (multi-component: core / spi / brcode / dict). +Names derive from br-spi.fullname so cross-references stay deterministic and +release-aware (e.g. the migration Job and MCP-style refs resolve by name). +Inputs (dict): context (root .), component ("core"|"spi"|"brcode"|"dict"). +============================================================================== +*/}} + +{{/* Component fullname, e.g. br-spi-core */}} +{{- define "br-spi.componentFullname" -}} +{{- $base := include "br-spi.fullname" .context | trunc 57 | trimSuffix "-" -}} +{{- printf "%s-%s" $base .component | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Component version label — the component's image tag, falling back to AppVersion */}} +{{- define "br-spi.componentVersion" -}} +{{- $cv := index .context.Values .component -}} +{{- default .context.Chart.AppVersion $cv.image.tag -}} +{{- end -}} + +{{/* Component selector labels (stable across image bumps) */}} +{{- define "br-spi.componentSelectorLabels" -}} +app.kubernetes.io/name: {{ include "br-spi.name" .context }} +app.kubernetes.io/instance: {{ .context.Release.Name }} +app.kubernetes.io/component: {{ .component }} +{{- end -}} + +{{/* Component labels */}} +{{- define "br-spi.componentLabels" -}} +helm.sh/chart: {{ include "br-spi.chart" .context }} +{{ include "br-spi.componentSelectorLabels" (dict "context" .context "component" .component) }} +app.kubernetes.io/version: {{ regexReplaceAll "[^-A-Za-z0-9_.]" (include "br-spi.componentVersion" .) "-" | trunc 63 | trimAll "-" | quote }} +app.kubernetes.io/managed-by: {{ .context.Release.Service }} +{{- end -}} + +{{/* Migrations fullname, e.g. br-spi-migrations — Job and PreSync Secret reference this */}} +{{- define "br-spi-migrations.fullname" -}} +{{- printf "%s-migrations" (include "br-spi.fullname" . | trunc 52 | trimSuffix "-") | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Migrations labels */}} +{{- define "br-spi-migrations.labels" -}} +helm.sh/chart: {{ include "br-spi.chart" . }} +app.kubernetes.io/name: {{ include "br-spi-migrations.fullname" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/component: migrations +{{- end -}} + +{{/* +infraSecretRef — emit a `- name: valueFrom: secretKeyRef: {name,key}` +env entry pointing at a Bitnami subchart's generated Secret (or the operator's +existingSecret override). Only used on the bundled-subchart path; the external +path reads the app's own Secret. See docs/helm-chart-standard.md +"Single-Source Infra Secrets". +Inputs (dict): context (root .), subchart, key, envName. +*/}} +{{- define "br-spi.infraSecretRef" -}} +{{- $ctx := .context -}} +{{- $sub := .subchart -}} +{{- $auth := default dict (index $ctx.Values $sub "auth") -}} +{{- $secretName := "" -}} +{{- if $auth.existingSecret -}} +{{- $secretName = $auth.existingSecret -}} +{{- else -}} +{{- $secretName = include "common.names.dependency.fullname" (dict "chartName" $sub "chartValues" (index $ctx.Values $sub) "context" $ctx) -}} +{{- end -}} +- name: {{ .envName }} + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: {{ .key }} +{{- end }} + +{{/* +Vendored from Bitnami common (charts/common/templates/_names.tpl) so infra +Secret/Service names render even when all bundled subcharts are disabled +(external-infra path). Self-contained: no other common.* helpers required. +*/}} +{{- define "common.names.dependency.fullname" -}} +{{- if .chartValues.fullnameOverride -}} +{{- .chartValues.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .chartName .chartValues.nameOverride -}} +{{- if contains $name .context.Release.Name -}} +{{- .context.Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .context.Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/br-spi/templates/brcode/configmap.yaml b/charts/br-spi/templates/brcode/configmap.yaml new file mode 100644 index 000000000..19a7f8779 --- /dev/null +++ b/charts/br-spi/templates/brcode/configmap.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentConfigMap" (dict "context" . "component" "brcode") -}} diff --git a/charts/br-spi/templates/brcode/deployment.yaml b/charts/br-spi/templates/brcode/deployment.yaml new file mode 100644 index 000000000..3aa46a2ef --- /dev/null +++ b/charts/br-spi/templates/brcode/deployment.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentDeployment" (dict "context" . "component" "brcode") -}} diff --git a/charts/br-spi/templates/brcode/hpa.yaml b/charts/br-spi/templates/brcode/hpa.yaml new file mode 100644 index 000000000..d1a7ef3ca --- /dev/null +++ b/charts/br-spi/templates/brcode/hpa.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentHPA" (dict "context" . "component" "brcode") -}} diff --git a/charts/br-spi/templates/brcode/ingress.yaml b/charts/br-spi/templates/brcode/ingress.yaml new file mode 100644 index 000000000..048c73b6f --- /dev/null +++ b/charts/br-spi/templates/brcode/ingress.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentIngress" (dict "context" . "component" "brcode") -}} diff --git a/charts/br-spi/templates/brcode/pdb.yaml b/charts/br-spi/templates/brcode/pdb.yaml new file mode 100644 index 000000000..e304445ba --- /dev/null +++ b/charts/br-spi/templates/brcode/pdb.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentPDB" (dict "context" . "component" "brcode") -}} diff --git a/charts/br-spi/templates/brcode/secrets.yaml b/charts/br-spi/templates/brcode/secrets.yaml new file mode 100644 index 000000000..586326823 --- /dev/null +++ b/charts/br-spi/templates/brcode/secrets.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentSecret" (dict "context" . "component" "brcode") -}} diff --git a/charts/br-spi/templates/brcode/service.yaml b/charts/br-spi/templates/brcode/service.yaml new file mode 100644 index 000000000..75d72d79a --- /dev/null +++ b/charts/br-spi/templates/brcode/service.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentService" (dict "context" . "component" "brcode") -}} diff --git a/charts/br-spi/templates/common/NOTES.txt b/charts/br-spi/templates/common/NOTES.txt new file mode 100644 index 000000000..cb823a26f --- /dev/null +++ b/charts/br-spi/templates/common/NOTES.txt @@ -0,0 +1,31 @@ +br-spi (SPI / Pix rail) has been deployed to namespace {{ include "global.namespace" . }}. + +Components: +{{- range $c := list "core" "spi" "brcode" "dict" }} +{{- $cv := index $.Values $c }} + - {{ include "br-spi.componentFullname" (dict "context" $ "component" $c) }}: {{ if $cv.enabled }}enabled{{ else }}disabled{{ end }}{{ if $cv.enabled }} (streaming: {{ $cv.configmap.STREAMING_ENABLED | default "false" }}){{ end }} +{{- end }} + +Settlement CloudEvents producers: + - core emits studio.lerian.settlement.* on topic br-spi.settlement + (it produces them by consuming the spi->core seam topics, so BOTH core and + spi must have STREAMING_ENABLED=true, the same STREAMING_BROKERS, and the + same Postgres — else no settlement event is emitted at all). + +Verify: + kubectl -n {{ include "global.namespace" . }} get pods -l app.kubernetes.io/instance={{ .Release.Name }} + kubectl -n {{ include "global.namespace" . }} get cm,secret -l app.kubernetes.io/instance={{ .Release.Name }} + +{{- if or (eq (toString .Values.core.configmap.STREAMING_ENABLED) "true") (eq (toString .Values.spi.configmap.STREAMING_ENABLED) "true") }} + +Streaming reminder: a component with STREAMING_ENABLED=true fails closed at boot +if STREAMING_BROKERS or STREAMING_CLOUDEVENTS_SOURCE is empty. Confirm both are +set for core and spi. +{{- end }} + +{{- if .Values.migrations.enabled }} + +Migrations: an ArgoCD PreSync Job ({{ include "br-spi-migrations.fullname" . }}) +applies the 6-module schema to the `brspi` database before the app boots. Under +plain `helm install` (no ArgoCD) the PreSync hook Job still runs as a normal Job. +{{- end }} diff --git a/charts/br-spi/templates/common/serviceaccount.yaml b/charts/br-spi/templates/common/serviceaccount.yaml new file mode 100644 index 000000000..762b4a343 --- /dev/null +++ b/charts/br-spi/templates/common/serviceaccount.yaml @@ -0,0 +1,18 @@ +{{- if .Values.serviceAccount.create -}} +# One ServiceAccount shared by all four component Deployments — they run the same +# workload identity and need no per-component RBAC differentiation. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "br-spi.serviceAccountName" . }} + namespace: {{ include "global.namespace" . }} + labels: + helm.sh/chart: {{ include "br-spi.chart" . }} + app.kubernetes.io/name: {{ include "br-spi.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/br-spi/templates/core/configmap.yaml b/charts/br-spi/templates/core/configmap.yaml new file mode 100644 index 000000000..8aecfd80d --- /dev/null +++ b/charts/br-spi/templates/core/configmap.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentConfigMap" (dict "context" . "component" "core") -}} diff --git a/charts/br-spi/templates/core/deployment.yaml b/charts/br-spi/templates/core/deployment.yaml new file mode 100644 index 000000000..5dc3b39d2 --- /dev/null +++ b/charts/br-spi/templates/core/deployment.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentDeployment" (dict "context" . "component" "core") -}} diff --git a/charts/br-spi/templates/core/hpa.yaml b/charts/br-spi/templates/core/hpa.yaml new file mode 100644 index 000000000..106dce05b --- /dev/null +++ b/charts/br-spi/templates/core/hpa.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentHPA" (dict "context" . "component" "core") -}} diff --git a/charts/br-spi/templates/core/ingress.yaml b/charts/br-spi/templates/core/ingress.yaml new file mode 100644 index 000000000..784406031 --- /dev/null +++ b/charts/br-spi/templates/core/ingress.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentIngress" (dict "context" . "component" "core") -}} diff --git a/charts/br-spi/templates/core/pdb.yaml b/charts/br-spi/templates/core/pdb.yaml new file mode 100644 index 000000000..28040f58b --- /dev/null +++ b/charts/br-spi/templates/core/pdb.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentPDB" (dict "context" . "component" "core") -}} diff --git a/charts/br-spi/templates/core/secrets.yaml b/charts/br-spi/templates/core/secrets.yaml new file mode 100644 index 000000000..e59e79bc5 --- /dev/null +++ b/charts/br-spi/templates/core/secrets.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentSecret" (dict "context" . "component" "core") -}} diff --git a/charts/br-spi/templates/core/service.yaml b/charts/br-spi/templates/core/service.yaml new file mode 100644 index 000000000..62ee27efa --- /dev/null +++ b/charts/br-spi/templates/core/service.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentService" (dict "context" . "component" "core") -}} diff --git a/charts/br-spi/templates/dict/configmap.yaml b/charts/br-spi/templates/dict/configmap.yaml new file mode 100644 index 000000000..5774087c0 --- /dev/null +++ b/charts/br-spi/templates/dict/configmap.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentConfigMap" (dict "context" . "component" "dict") -}} diff --git a/charts/br-spi/templates/dict/deployment.yaml b/charts/br-spi/templates/dict/deployment.yaml new file mode 100644 index 000000000..e0a939184 --- /dev/null +++ b/charts/br-spi/templates/dict/deployment.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentDeployment" (dict "context" . "component" "dict") -}} diff --git a/charts/br-spi/templates/dict/hpa.yaml b/charts/br-spi/templates/dict/hpa.yaml new file mode 100644 index 000000000..82e3047ce --- /dev/null +++ b/charts/br-spi/templates/dict/hpa.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentHPA" (dict "context" . "component" "dict") -}} diff --git a/charts/br-spi/templates/dict/ingress.yaml b/charts/br-spi/templates/dict/ingress.yaml new file mode 100644 index 000000000..cf901857e --- /dev/null +++ b/charts/br-spi/templates/dict/ingress.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentIngress" (dict "context" . "component" "dict") -}} diff --git a/charts/br-spi/templates/dict/pdb.yaml b/charts/br-spi/templates/dict/pdb.yaml new file mode 100644 index 000000000..b243a4160 --- /dev/null +++ b/charts/br-spi/templates/dict/pdb.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentPDB" (dict "context" . "component" "dict") -}} diff --git a/charts/br-spi/templates/dict/secrets.yaml b/charts/br-spi/templates/dict/secrets.yaml new file mode 100644 index 000000000..5104e4b20 --- /dev/null +++ b/charts/br-spi/templates/dict/secrets.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentSecret" (dict "context" . "component" "dict") -}} diff --git a/charts/br-spi/templates/dict/service.yaml b/charts/br-spi/templates/dict/service.yaml new file mode 100644 index 000000000..0703eb74e --- /dev/null +++ b/charts/br-spi/templates/dict/service.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentService" (dict "context" . "component" "dict") -}} diff --git a/charts/br-spi/templates/migrations/job.yaml b/charts/br-spi/templates/migrations/job.yaml new file mode 100644 index 000000000..2e606aad2 --- /dev/null +++ b/charts/br-spi/templates/migrations/job.yaml @@ -0,0 +1,105 @@ +{{- if .Values.migrations.enabled }} +# PreSync migration Job. Runs the dedicated br-sfn-spi-migrations image, whose +# entrypoint already applies the 6-module golang-migrate loop in manifest order +# (global events spi dict brcode core) against the single `brspi` database, using +# the per-module `x-migrations-table=schema_migrations_` naming that +# /readyz reads back. systemplane is NOT applied by golang-migrate — it is +# lib-systemplane-managed at runtime. The chart's Job only provides the DB +# connection + runs the image; it does NOT build the loop. +# +# br-sfn runtime binaries are detached: they verify schema version on boot and +# refuse to start against an unmigrated DB, and the app images ship no migrator. +# hook-weight -1 puts this after the PreSync Secret (-2) and before the main-sync +# Deployments, so no binary ever boots unmigrated. +{{- $pgHost := .Values.migrations.postgres.host | default .Values.core.configmap.POSTGRES_HOST -}} +{{- if and (not $pgHost) (ne (toString .Values.postgresql.enabled) "false") -}} +{{- $pgHost = printf "%s.%s.svc.cluster.local." (include "common.names.dependency.fullname" (dict "chartName" "postgresql" "chartValues" .Values.postgresql "context" .)) (include "global.namespace" .) -}} +{{- end }} +{{- $pgPort := .Values.migrations.postgres.port | default .Values.core.configmap.POSTGRES_PORT | default "5432" }} +{{- $pgUser := .Values.migrations.postgres.user | default .Values.core.configmap.POSTGRES_USER | default "brspi" }} +{{- $pgDb := .Values.migrations.postgres.database | default .Values.core.configmap.POSTGRES_DB | default "brspi" }} +{{- $pgSslMode := .Values.migrations.postgres.sslMode | default .Values.core.configmap.POSTGRES_SSLMODE | default "disable" }} +{{- $secretName := ternary .Values.migrations.existingSecretName (include "br-spi-migrations.fullname" .) .Values.migrations.useExistingSecret }} +{{- $pullSecrets := .Values.migrations.imagePullSecrets | default .Values.imagePullSecrets }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "br-spi-migrations.fullname" . }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-spi-migrations.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/hook: PreSync + argocd.argoproj.io/hook-weight: "-1" + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded +spec: + backoffLimit: {{ .Values.migrations.backoffLimit | default 3 }} + activeDeadlineSeconds: {{ .Values.migrations.activeDeadlineSeconds | default 600 }} + ttlSecondsAfterFinished: {{ .Values.migrations.ttlSecondsAfterFinished | default 600 }} + template: + metadata: + labels: + {{- include "br-spi-migrations.labels" . | nindent 8 }} + spec: + restartPolicy: Never + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + {{- with $pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + - name: wait-for-postgres + image: {{ .Values.migrations.waitImage | default "busybox" }} + command: + - /bin/sh + - -c + - > + echo "waiting for {{ $pgHost }}:{{ $pgPort }}..."; + until nc -z {{ $pgHost }} {{ $pgPort }}; do + echo "{{ $pgHost }}:{{ $pgPort }} not ready, waiting..."; sleep 5; + done; + echo "{{ $pgHost }}:{{ $pgPort }} is ready" + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + containers: + - name: migrations + image: "{{ .Values.migrations.image.repository }}:{{ .Values.migrations.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.migrations.image.pullPolicy | default "IfNotPresent" }} + env: + - name: POSTGRES_HOST + value: {{ $pgHost | quote }} + - name: POSTGRES_PORT + value: {{ $pgPort | quote }} + - name: POSTGRES_USER + value: {{ $pgUser | quote }} + - name: POSTGRES_DB + value: {{ $pgDb | quote }} + - name: POSTGRES_SSLMODE + value: {{ $pgSslMode | quote }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: POSTGRES_PASSWORD + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + resources: + {{- toYaml .Values.migrations.resources | nindent 12 }} +{{- end }} diff --git a/charts/br-spi/templates/migrations/secrets.yaml b/charts/br-spi/templates/migrations/secrets.yaml new file mode 100644 index 000000000..54f104ddd --- /dev/null +++ b/charts/br-spi/templates/migrations/secrets.yaml @@ -0,0 +1,24 @@ +{{- if and .Values.migrations.enabled (not .Values.migrations.useExistingSecret) }} +# Dedicated PreSync Secret carrying only the Postgres password for the migration +# Job. PreSync hooks run BEFORE the main sync wave, so on a first install the +# component app Secrets do not exist yet. This hook Secret (weight -2) is created +# before the migration Job (weight -1). hook-delete-policy is BeforeHookCreation +# ONLY (not HookSucceeded) so it survives the whole PreSync phase for the Job to +# read; it is replaced at the next sync. When migrations.useExistingSecret=true +# this Secret is NOT minted and the Job reads POSTGRES_PASSWORD from the +# operator-provided existing Secret. +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "br-spi-migrations.fullname" . }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-spi-migrations.labels" . | nindent 4 }} + annotations: + argocd.argoproj.io/hook: PreSync + argocd.argoproj.io/hook-weight: "-2" + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +type: Opaque +stringData: + POSTGRES_PASSWORD: {{ .Values.migrations.postgres.password | default .Values.core.secrets.POSTGRES_PASSWORD | quote }} +{{- end }} diff --git a/charts/br-spi/templates/spi/configmap.yaml b/charts/br-spi/templates/spi/configmap.yaml new file mode 100644 index 000000000..3b095acfb --- /dev/null +++ b/charts/br-spi/templates/spi/configmap.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentConfigMap" (dict "context" . "component" "spi") -}} diff --git a/charts/br-spi/templates/spi/deployment.yaml b/charts/br-spi/templates/spi/deployment.yaml new file mode 100644 index 000000000..b461220f0 --- /dev/null +++ b/charts/br-spi/templates/spi/deployment.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentDeployment" (dict "context" . "component" "spi") -}} diff --git a/charts/br-spi/templates/spi/hpa.yaml b/charts/br-spi/templates/spi/hpa.yaml new file mode 100644 index 000000000..87e55ae09 --- /dev/null +++ b/charts/br-spi/templates/spi/hpa.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentHPA" (dict "context" . "component" "spi") -}} diff --git a/charts/br-spi/templates/spi/ingress.yaml b/charts/br-spi/templates/spi/ingress.yaml new file mode 100644 index 000000000..e6243a877 --- /dev/null +++ b/charts/br-spi/templates/spi/ingress.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentIngress" (dict "context" . "component" "spi") -}} diff --git a/charts/br-spi/templates/spi/pdb.yaml b/charts/br-spi/templates/spi/pdb.yaml new file mode 100644 index 000000000..42407e5e2 --- /dev/null +++ b/charts/br-spi/templates/spi/pdb.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentPDB" (dict "context" . "component" "spi") -}} diff --git a/charts/br-spi/templates/spi/secrets.yaml b/charts/br-spi/templates/spi/secrets.yaml new file mode 100644 index 000000000..8e8eb33eb --- /dev/null +++ b/charts/br-spi/templates/spi/secrets.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentSecret" (dict "context" . "component" "spi") -}} diff --git a/charts/br-spi/templates/spi/service.yaml b/charts/br-spi/templates/spi/service.yaml new file mode 100644 index 000000000..1ca2b91b2 --- /dev/null +++ b/charts/br-spi/templates/spi/service.yaml @@ -0,0 +1 @@ +{{- include "br-spi.componentService" (dict "context" . "component" "spi") -}} diff --git a/charts/br-spi/values-template.yaml b/charts/br-spi/values-template.yaml new file mode 100644 index 000000000..f13e5336f --- /dev/null +++ b/charts/br-spi/values-template.yaml @@ -0,0 +1,61 @@ +# Template values for the br-spi (SPI / Pix rail) chart. +# Copy this file and fill in the values for your environment. Postgres and Redis +# are external on the target environments — point every component at the shared +# `brspi` database and set STREAMING_BROKERS for the producers (core + spi). + +imagePullSecrets: + - name: ghcr-credential + +core: + image: + tag: "" # optional — defaults to Chart.appVersion + configmap: + ENV_NAME: "development" + POSTGRES_HOST: "" # REQUIRED — shared brspi Postgres host + POSTGRES_USER: "brspi" + POSTGRES_DB: "brspi" + STREAMING_BROKERS: "" # REQUIRED when STREAMING_ENABLED=true (fails closed otherwise) + STREAMING_CLOUDEVENTS_SOURCE: "studio.lerian.core" + secrets: + POSTGRES_PASSWORD: "" # REQUIRED for external Postgres (or use useExistingSecret) + +spi: + configmap: + ENV_NAME: "development" + POSTGRES_HOST: "" # REQUIRED — same host as core + STREAMING_BROKERS: "" # REQUIRED — same brokers as core + STREAMING_CLOUDEVENTS_SOURCE: "studio.lerian.spi" + BACEN_SPI_ENDPOINT: "" # dev: http:// / mock accepted; set the real endpoint for homolog/prod + secrets: + POSTGRES_PASSWORD: "" + +brcode: + configmap: + ENV_NAME: "development" + POSTGRES_HOST: "" # REQUIRED + secrets: + POSTGRES_PASSWORD: "" + +dict: + configmap: + ENV_NAME: "development" + POSTGRES_HOST: "" # REQUIRED + REDIS_HOST: "" # REQUIRED for dict — Redis host:port + secrets: + POSTGRES_PASSWORD: "" + REDIS_PASSWORD: "" # set if the external Redis requires auth + +migrations: + enabled: true + postgres: + host: "" # defaults to core.configmap.POSTGRES_HOST + password: "" # defaults to core.secrets.POSTGRES_PASSWORD + +# External infra (default). Enable only for a self-contained install. +postgresql: + enabled: false + external: true + +valkey: + enabled: false + external: true diff --git a/charts/br-spi/values.schema.json b/charts/br-spi/values.schema.json new file mode 100644 index 000000000..79574fdb5 --- /dev/null +++ b/charts/br-spi/values.schema.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "nameOverride": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "namespaceOverride": { + "type": "string" + }, + "global": { + "type": "object", + "additionalProperties": true + }, + "imagePullSecrets": { + "type": "array" + }, + "serviceAccount": { + "type": "object", + "additionalProperties": true + }, + "podSecurityContext": { + "type": "object", + "additionalProperties": true + }, + "securityContext": { + "type": "object", + "additionalProperties": true + }, + "core": { + "type": "object", + "properties": { + "configmap": { + "type": "object" + }, + "secrets": { + "type": "object" + } + }, + "additionalProperties": true + }, + "spi": { + "type": "object", + "properties": { + "configmap": { + "type": "object" + }, + "secrets": { + "type": "object" + } + }, + "additionalProperties": true + }, + "brcode": { + "type": "object", + "properties": { + "configmap": { + "type": "object" + }, + "secrets": { + "type": "object" + } + }, + "additionalProperties": true + }, + "dict": { + "type": "object", + "properties": { + "configmap": { + "type": "object" + }, + "secrets": { + "type": "object" + } + }, + "additionalProperties": true + }, + "migrations": { + "type": "object", + "additionalProperties": true + }, + "postgresql": { + "type": "object", + "additionalProperties": true + }, + "valkey": { + "type": "object", + "additionalProperties": true + } + } +} diff --git a/charts/br-spi/values.yaml b/charts/br-spi/values.yaml new file mode 100644 index 000000000..03a473b7c --- /dev/null +++ b/charts/br-spi/values.yaml @@ -0,0 +1,443 @@ +# Default values for the br-spi (SPI / Pix rail) chart. +# Four component blocks (core / spi / brcode / dict) share the same workload +# shape; they differ by image, OTEL resource name, streaming toggle, and a few +# per-binary env keys. Each component's `configmap` map is emitted VERBATIM into +# its ConfigMap (not a fixed allowlist), so any extra env — BACEN endpoints, +# Postgres pool tuning — can be added under `.configmap` without a +# chart change. STREAMING_* keys are always emitted first-class. +nameOverride: "br-spi" +fullnameOverride: "" +namespaceOverride: "" + +global: + imageRegistry: "" + imagePullSecrets: [] + +# -- Default pull secret for every component + the migration Job (single GHCR +# credential). Override per component via .imagePullSecrets. +imagePullSecrets: + - name: ghcr-credential + +serviceAccount: + # -- Create one ServiceAccount shared by all components + create: true + # -- Annotations for the ServiceAccount + annotations: {} + # -- Name of the service account (defaults to the release fullname) + name: "" + +# -- Pod-level security context (shared by all components) +podSecurityContext: {} + +# -- Container security context (shared by all components + the wait initContainer) +securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + +# ============================================================================= +# core — settlement engine + sole producer of studio.lerian.settlement.* on the +# br-spi.settlement topic. It emits by CONSUMING the spi->core seam topics, so +# core AND spi must both stream (same brokers, same Postgres) or no settlement +# event is emitted at all. +# ============================================================================= +core: + enabled: true + replicaCount: 1 + revisionHistoryLimit: 10 + image: + repository: ghcr.io/lerianstudio/br-sfn-core + tag: "" + pullPolicy: IfNotPresent + imagePullSecrets: [] + podAnnotations: {} + waitImage: busybox + deploymentUpdate: + type: RollingUpdate + maxSurge: 100% + maxUnavailable: 0 + service: + type: ClusterIP + port: 8080 + annotations: {} + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: "" + paths: + - path: / + pathType: Prefix + tls: [] + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + pdb: + # Off by default: with replicaCount 1, a PDB (minAvailable 1) blocks all + # voluntary evictions and stalls node drains. Enable it once scaled >1. + enabled: false + minAvailable: 1 + annotations: {} + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + memory: 512Mi + # -- Readiness/liveness probe overrides (path defaults /readyz and /health) + readinessProbe: {} + livenessProbe: {} + # -- Use an operator-provided Secret instead of the chart-managed one + useExistingSecret: false + existingSecretName: "" + # -- Raw container env entries appended verbatim (can include valueFrom). + # Range-free escape for anything the ConfigMap map cannot carry. + extraEnvVars: [] + # -- ConfigMap env. Emitted verbatim; STREAMING_*/POSTGRES_HOST/REDIS_HOST are + # handled first-class by the template. + configmap: + ENV_NAME: "development" + LOG_LEVEL: "info" + SERVER_ADDRESS: ":8080" + HTTP_BODY_LIMIT_BYTES: "104857600" + # PostgreSQL (all four binaries share ONE database: brspi) + POSTGRES_HOST: "" + POSTGRES_PORT: "5432" + POSTGRES_USER: "brspi" + POSTGRES_DB: "brspi" + POSTGRES_SSLMODE: "disable" + # Redis/Valkey (host:port in one var; optional for core) + REDIS_HOST: "" + # Outbox relay (must stay on for the producer) + OUTBOX_ENABLED: "true" + # core requires a positive idempotency retry window to boot + IDEMPOTENCY_RETRY_WINDOW_SEC: "300" + # Streaming — core is a producer + STREAMING_ENABLED: "true" + STREAMING_BROKERS: "" + STREAMING_CLOUDEVENTS_SOURCE: "studio.lerian.core" + # OpenTelemetry (gRPC/4317; endpoint overridden to $(HOST_IP):4317 when ENABLE_TELEMETRY=true) + ENABLE_TELEMETRY: "false" + OTEL_RESOURCE_SERVICE_NAME: "br-spi-core" + OTEL_RESOURCE_DEPLOYMENT_ENVIRONMENT: "development" + OTEL_EXPORTER_OTLP_ENDPOINT: "" + secrets: + # -- External-Postgres password. Single-sourced from the postgresql subchart + # Secret when it is bundled; set here only for external Postgres without an + # existingSecret. On the target environments this comes from GitOps/Vault. + POSTGRES_PASSWORD: "" + # -- External-Redis password (optional; single-sourced from the valkey subchart when bundled) + REDIS_PASSWORD: "" + +# ============================================================================= +# spi — the ISO 20022 / BACEN SPI binary. Producer of the spi->core seam topics, +# so it must stream alongside core. Talks to BACEN SPI (relaxed under ENV_NAME=development). +# ============================================================================= +spi: + enabled: true + replicaCount: 1 + revisionHistoryLimit: 10 + image: + repository: ghcr.io/lerianstudio/br-sfn-spi + tag: "" + pullPolicy: IfNotPresent + imagePullSecrets: [] + podAnnotations: {} + waitImage: busybox + deploymentUpdate: + type: RollingUpdate + maxSurge: 100% + maxUnavailable: 0 + service: + type: ClusterIP + port: 8080 + annotations: {} + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: "" + paths: + - path: / + pathType: Prefix + tls: [] + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + pdb: + # Off by default: with replicaCount 1, a PDB (minAvailable 1) blocks all + # voluntary evictions and stalls node drains. Enable it once scaled >1. + enabled: false + minAvailable: 1 + annotations: {} + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + memory: 512Mi + readinessProbe: {} + livenessProbe: {} + useExistingSecret: false + existingSecretName: "" + extraEnvVars: [] + configmap: + ENV_NAME: "development" + LOG_LEVEL: "info" + SERVER_ADDRESS: ":8080" + HTTP_BODY_LIMIT_BYTES: "104857600" + POSTGRES_HOST: "" + POSTGRES_PORT: "5432" + POSTGRES_USER: "brspi" + POSTGRES_DB: "brspi" + POSTGRES_SSLMODE: "disable" + REDIS_HOST: "" + OUTBOX_ENABLED: "true" + # BACEN SPI endpoint. Under ENV_NAME=development a http:// / mock endpoint is + # accepted and BACEN mTLS validation is bypassed (not required to boot). + # Other BACEN_* / signer knobs can be added here verbatim. + BACEN_SPI_ENDPOINT: "" + # Streaming — spi writes the spi->core seam that core turns into settlement.* + STREAMING_ENABLED: "true" + STREAMING_BROKERS: "" + STREAMING_CLOUDEVENTS_SOURCE: "studio.lerian.spi" + ENABLE_TELEMETRY: "false" + OTEL_RESOURCE_SERVICE_NAME: "br-spi-spi" + OTEL_RESOURCE_DEPLOYMENT_ENVIRONMENT: "development" + OTEL_EXPORTER_OTLP_ENDPOINT: "" + secrets: + POSTGRES_PASSWORD: "" + REDIS_PASSWORD: "" + +# ============================================================================= +# brcode — Pix BR Code (QR) REST binary. NOT a settlement producer; streaming off. +# ============================================================================= +brcode: + enabled: true + replicaCount: 1 + revisionHistoryLimit: 10 + image: + repository: ghcr.io/lerianstudio/br-sfn-brcode + tag: "" + pullPolicy: IfNotPresent + imagePullSecrets: [] + podAnnotations: {} + waitImage: busybox + deploymentUpdate: + type: RollingUpdate + maxSurge: 100% + maxUnavailable: 0 + service: + type: ClusterIP + port: 8080 + annotations: {} + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: "" + paths: + - path: / + pathType: Prefix + tls: [] + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + pdb: + # Off by default: with replicaCount 1, a PDB (minAvailable 1) blocks all + # voluntary evictions and stalls node drains. Enable it once scaled >1. + enabled: false + minAvailable: 1 + annotations: {} + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + memory: 512Mi + readinessProbe: {} + livenessProbe: {} + useExistingSecret: false + existingSecretName: "" + extraEnvVars: [] + configmap: + ENV_NAME: "development" + LOG_LEVEL: "info" + SERVER_ADDRESS: ":8080" + HTTP_BODY_LIMIT_BYTES: "104857600" + POSTGRES_HOST: "" + POSTGRES_PORT: "5432" + POSTGRES_USER: "brspi" + POSTGRES_DB: "brspi" + POSTGRES_SSLMODE: "disable" + REDIS_HOST: "" + OUTBOX_ENABLED: "true" + # Not a producer + STREAMING_ENABLED: "false" + STREAMING_BROKERS: "" + STREAMING_CLOUDEVENTS_SOURCE: "" + ENABLE_TELEMETRY: "false" + OTEL_RESOURCE_SERVICE_NAME: "br-spi-brcode" + OTEL_RESOURCE_DEPLOYMENT_ENVIRONMENT: "development" + OTEL_EXPORTER_OTLP_ENDPOINT: "" + secrets: + POSTGRES_PASSWORD: "" + REDIS_PASSWORD: "" + +# ============================================================================= +# dict — DICT (Pix directory) REST binary. NOT a settlement producer; streaming +# off. REQUIRES REDIS_HOST (scheduler / caches). +# ============================================================================= +dict: + enabled: true + replicaCount: 1 + revisionHistoryLimit: 10 + image: + repository: ghcr.io/lerianstudio/br-sfn-dict + tag: "" + pullPolicy: IfNotPresent + imagePullSecrets: [] + podAnnotations: {} + waitImage: busybox + deploymentUpdate: + type: RollingUpdate + maxSurge: 100% + maxUnavailable: 0 + service: + type: ClusterIP + port: 8080 + annotations: {} + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: "" + paths: + - path: / + pathType: Prefix + tls: [] + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + pdb: + # Off by default: with replicaCount 1, a PDB (minAvailable 1) blocks all + # voluntary evictions and stalls node drains. Enable it once scaled >1. + enabled: false + minAvailable: 1 + annotations: {} + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + memory: 512Mi + readinessProbe: {} + livenessProbe: {} + useExistingSecret: false + existingSecretName: "" + extraEnvVars: [] + configmap: + ENV_NAME: "development" + LOG_LEVEL: "info" + SERVER_ADDRESS: ":8080" + HTTP_BODY_LIMIT_BYTES: "104857600" + POSTGRES_HOST: "" + POSTGRES_PORT: "5432" + POSTGRES_USER: "brspi" + POSTGRES_DB: "brspi" + POSTGRES_SSLMODE: "disable" + # REQUIRED for dict — set to the Redis host:port (e.g. valkey-dev:6379). + REDIS_HOST: "" + OUTBOX_ENABLED: "true" + STREAMING_ENABLED: "false" + STREAMING_BROKERS: "" + STREAMING_CLOUDEVENTS_SOURCE: "" + ENABLE_TELEMETRY: "false" + OTEL_RESOURCE_SERVICE_NAME: "br-spi-dict" + OTEL_RESOURCE_DEPLOYMENT_ENVIRONMENT: "development" + OTEL_EXPORTER_OTLP_ENDPOINT: "" + secrets: + POSTGRES_PASSWORD: "" + REDIS_PASSWORD: "" + +# ============================================================================= +# Detached migrations — ArgoCD PreSync Secret + Job that applies the 6-module +# schema to the single `brspi` database before the app boots. The +# br-sfn-spi-migrations image's entrypoint runs the golang-migrate loop in +# manifest order (global events spi dict brcode core) with the per-module +# x-migrations-table naming; systemplane is lib-systemplane-managed at runtime, +# not golang-migrate. The chart only supplies the DB connection and runs it. +# ============================================================================= +migrations: + enabled: true + image: + repository: ghcr.io/lerianstudio/br-sfn-spi-migrations + tag: "" + pullPolicy: IfNotPresent + imagePullSecrets: [] + waitImage: busybox + # -- Use an operator-provided Secret (must carry POSTGRES_PASSWORD) instead of + # the chart-managed PreSync Secret. + useExistingSecret: false + existingSecretName: "" + # -- Postgres connection for the migration Job. Empty fields fall back to + # core.configmap.POSTGRES_* (all four binaries share the brspi database). + postgres: + host: "" + port: "" + user: "" + database: "" + sslMode: "" + # -- Password for the PreSync Secret (empty -> core.secrets.POSTGRES_PASSWORD). + # Ignored when migrations.useExistingSecret=true. NOTE: for a self-contained + # install (postgresql.enabled=true) this MUST be set explicitly (or use + # useExistingSecret) — the bundled Postgres Secret does not exist yet at + # PreSync time, so the fallback cannot read it. External Postgres (the + # default) has no such constraint. + password: "" + backoffLimit: 3 + activeDeadlineSeconds: 600 + ttlSecondsAfterFinished: 600 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + memory: 256Mi + +# ============================================================================= +# Bundled infra subcharts — DISABLED by default. On the target environments +# Postgres and Redis are external and pre-provisioned; wire hosts via +# .configmap.POSTGRES_HOST / REDIS_HOST. Enable these only for a +# self-contained install (then passwords are single-sourced from their Secrets). +# ============================================================================= +postgresql: + enabled: false + external: true + +valkey: + enabled: false + external: true