diff --git a/charts/midaz/README.md b/charts/midaz/README.md index 3ac74cf09..5e59e5f65 100644 --- a/charts/midaz/README.md +++ b/charts/midaz/README.md @@ -3,7 +3,7 @@ ## Chart Contract - Chart type: `multi-component` -- Required secrets: `ledger.secrets.RABBITMQ_DEFAULT_PASS` and `ledger.secrets.RABBITMQ_CONSUMER_PASS` (operator-provided — see "Known limitation" below) plus `crm.secrets.LCRYPTO_HASH_SECRET_KEY` and `crm.secrets.LCRYPTO_ENCRYPT_SECRET_KEY` (app crypto material). The database, replica, and cache passwords (`DB_ONBOARDING_PASSWORD`, `DB_ONBOARDING_REPLICA_PASSWORD`, `MONGO_ONBOARDING_PASSWORD`, `DB_TRANSACTION_PASSWORD`, `DB_TRANSACTION_REPLICA_PASSWORD`, `MONGO_TRANSACTION_PASSWORD`, `REDIS_PASSWORD`, `crm.secrets.MONGO_PASSWORD`) are single-sourced from the bundled Bitnami subcharts and are only required when the matching backend is external. +- Required secrets: `ledger.secrets.RABBITMQ_DEFAULT_PASS` and `ledger.secrets.RABBITMQ_CONSUMER_PASS` (operator-provided — see "Known limitation" below) plus `crm.secrets.LCRYPTO_HASH_SECRET_KEY` and `crm.secrets.LCRYPTO_ENCRYPT_SECRET_KEY` (app crypto material). With a 4.x `ledger.image.tag` and `KMS_VENDOR=none`, `ledger.secrets.LCRYPTO_HASH_SECRET_KEY` and `ledger.secrets.LCRYPTO_ENCRYPT_SECRET_KEY` are required too: the unified binary serves CRM in-process and initializes its cipher from them at boot. `LCRYPTO_ENCRYPT_SECRET_KEY` must be hex encoding an AES key of 16, 24, or 32 bytes (32, 48, or 64 hex characters); the chart rejects anything else at render time. The database, replica, and cache passwords (`DB_ONBOARDING_PASSWORD`, `DB_ONBOARDING_REPLICA_PASSWORD`, `MONGO_ONBOARDING_PASSWORD`, `DB_TRANSACTION_PASSWORD`, `DB_TRANSACTION_REPLICA_PASSWORD`, `MONGO_TRANSACTION_PASSWORD`, `REDIS_PASSWORD`, `crm.secrets.MONGO_PASSWORD`) are single-sourced from the bundled Bitnami subcharts and are only required when the matching backend is external. - Dependency notes: PostgreSQL, MongoDB, and Valkey passwords are single-sourced from the bundled Bitnami subchart Secrets (`-postgresql` key `password`/`replication-password`, `-mongodb` key `mongodb-root-password`, `-valkey` key `valkey-password`) and injected into the ledger/crm workloads via `secretKeyRef`. RabbitMQ and optional OpenTelemetry are also bundled. When a backend is external (`.enabled=false`/`.external=true`), supply its password through the component `secrets` block or point the subchart at an `auth.existingSecret`. - Production overrides: Provide RabbitMQ and CRM crypto credentials through the component `secrets` (or `useExistingSecret`); let the bundled Bitnami subcharts own the database/cache passwords (or set `.auth.existingSecret`/`.auth.password`). Override image tags, ingress, resources, namespace, and persistence as needed. - Source/license: Source is in `github.com/LerianStudio/helm`; license is Apache-2.0. @@ -295,6 +295,31 @@ crm: # MONGO_PASSWORD: "" ``` +### Tracer + +The `tracer` service provides real-time transaction validation and fraud prevention. From midaz v4 it ships from the monorepo (`components/tracer`) and is published as `lerianstudio/midaz-tracer`; the standalone `lerianstudio/tracer` 1.x image predates this configuration contract. It is disabled by default (`tracer.enabled=false`) and existing releases render unchanged. + +- **Database.** Tracer uses the bundled PostgreSQL with the same `midaz` role that owns `onboarding` and `transaction`. `tracer.configmap.DB_HOST` is empty by default and resolves to this release's PostgreSQL primary Service (`-postgresql-primary`, or `-postgresql` in the standalone topology), so a release name other than `midaz` still connects; set it explicitly for an external PostgreSQL, where the render fails rather than guessing. Its database (`tracer.configmap.DB_NAME`, default `tracer`) is created by `files/midaz/init.sql` on a fresh internal cluster, and by the external bootstrap Job (`global.externalPostgresDefinitions.enabled=true`) otherwise; both paths use the configured name. On a pre-existing internal cluster, initdb scripts no longer run: create the configured database once by hand before enabling tracer (`CREATE DATABASE tracer;` with the default `tracer.configmap.DB_NAME`). +- **Migrations.** Neither tracer nor the v4 ledger migrates at startup anymore. `tracer.migrations` and `ledger.migrations` render the dedicated `midaz-tracer-migrations` / `midaz-ledger-migrations` runner Jobs, whose tag defaults to the matching application tag. The tracer Job renders when both `tracer.enabled` and `tracer.migrations.enabled` (default `true`) are true; the ledger Job is on for 4.x ledger tags only (`ledger.migrations.enabled` pins it either way). Setting either `migrations.enabled` to `false` means the schema must be applied out of band, because the v4 services no longer migrate themselves. Both Jobs are plain Sync-phase resources because the bundled PostgreSQL Secret they read is only created during Sync; while a Job renders, its Deployment carries `argocd.argoproj.io/sync-wave: "1"` (`.migrations.deploymentSyncWave`) so Argo CD applies the service only after the Job reports Complete. Against a pre-provisioned database, set `argocd.argoproj.io/hook: PreSync` under `.migrations.annotations` instead. +- **Ledger to tracer transport.** `ledger.configmap.TRACER_TRANSPORT` defaults to `rest`, because the bundled tracer keeps its gRPC reservation seam off (the app only starts it when `TRACER_GRPC_PORT` is set) and its Service exposes HTTP only. To use gRPC, the seam's production transport, set `tracer.configmap.TRACER_GRPC_PORT` (e.g. `":4021"`), which publishes `tracer.service.grpcPort` on the Service and container, and point `ledger.configmap.TRACER_BASE_URL` at it. Selecting `grpc` against a bundled tracer with the seam off fails the render. +- **Memory.** `tracer.configmap.GOMEMLIMIT` must track `tracer.resources.limits.memory` (~90%). The image ships `GOMEMLIMIT=1800MiB`, sized for a 2Gi container, so without the override the Go heap outgrows a 512Mi cgroup and the pod is OOM-killed. +- **Fail-fast configuration.** The chart mirrors the tracer boot validators, so misconfiguration fails `helm template` instead of crash-looping: `API_KEY_ENABLED=true` requires an API key and rejects wildcard CORS; `MULTI_TENANT_ENABLED=true` requires `PLUGIN_AUTH_ENABLED=true`, a service API key, and rejects `API_KEY_ENABLED_ONLY_VALIDATION=true`; `tracer.useExistingSecret=true` requires `tracer.existingSecretName`. Booleans are matched the way the runtime parses them (`strconv.ParseBool`), so `TRUE`, `True`, `1`, `t` and `T` are treated as enabled here too. + +```yaml +tracer: + enabled: true + configmap: + API_KEY_ENABLED: "true" + CORS_ALLOWED_ORIGINS: "https://app.example.com" + secrets: + API_KEY: "" + # DB_PASSWORD is single-sourced from the bundled Bitnami postgresql subchart + # (Secret `midaz-postgresql`, key `password`) — only set it here when using an + # EXTERNAL PostgreSQL (postgresql.enabled=false / postgresql.external=true). +``` + +> **Images.** Every image this chart references is expected to be pullable anonymously, so no `imagePullSecrets` are needed for a default install. Three v4 packages do not satisfy that yet: `lerianstudio/midaz-tracer`, `lerianstudio/midaz-tracer-migrations`, and `lerianstudio/midaz-ledger-migrations` still answer `denied/unauthorized` on Docker Hub, while `lerianstudio/midaz-ledger` resolves. The chart keeps the coordinates the midaz release pipeline publishes; making those repositories public is the fix. + ## Observability Midaz uses [Grafana Docker OpenTelemetry LGTM](https://github.com/grafana/docker-otel-lgtm) for observability. This component collects, processes, and exports telemetry data such as traces and metrics. diff --git a/charts/midaz/files/midaz/init.sql b/charts/midaz/files/midaz/init.sql index 5ffbf30d9..a5f041c43 100644 --- a/charts/midaz/files/midaz/init.sql +++ b/charts/midaz/files/midaz/init.sql @@ -8,4 +8,13 @@ SELECT pg_create_physical_replication_slot('replication_slot'); SELECT * FROM pg_create_logical_replication_slot('logical_slot', 'pgoutput'); CREATE DATABASE onboarding; -CREATE DATABASE transaction; \ No newline at end of file +CREATE DATABASE transaction; + +-- Tracer shares this cluster and the `midaz` role that owns the databases above +-- (its Deployment reads the same subchart Secret). Created unconditionally, and +-- not gated on tracer.enabled, because initdb scripts run exactly once: gating +-- would leave the database missing for anyone enabling tracer after install. +-- An unused empty database costs nothing. The name tracks +-- tracer.configmap.DB_NAME so the bundled cluster and the external bootstrap +-- Job create the same database the tracer service and its migration Job read. +CREATE DATABASE {{ dig "configmap" "DB_NAME" "" (.Values.tracer | default dict) | default "tracer" }}; \ No newline at end of file diff --git a/charts/midaz/templates/_helpers.tpl b/charts/midaz/templates/_helpers.tpl index 9181d0ca3..98df64c27 100644 --- a/charts/midaz/templates/_helpers.tpl +++ b/charts/midaz/templates/_helpers.tpl @@ -166,6 +166,51 @@ app.kubernetes.io/name: {{ include "midaz.name" .context }}-{{ .name }} app.kubernetes.io/instance: {{ .context.Release.Name }} {{- end }} +{{/* +Create a default fully qualified app name for Tracer. +*/}} +{{- define "midaz-tracer.fullname" -}} +{{- if .Values.tracer.fullnameOverride }} +{{- .Values.tracer.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" (include "midaz.name" .) (default .Values.tracer.name .Values.tracer.nameOverride) | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{/* +Create Tracer app version +*/}} +{{- define "tracer.defaultTag" -}} +{{- default .Chart.AppVersion .Values.tracer.image.tag }} +{{- end -}} + +{{/* +Return valid Tracer version label +*/}} +{{- define "tracer.versionLabelValue" -}} +{{ regexReplaceAll "[^-A-Za-z0-9_.]" (include "tracer.defaultTag" .) "-" | trunc 63 | trimAll "-" | trimAll "_" | trimAll "." | quote }} +{{- end -}} + +{{/* +Tracer Common labels +*/}} +{{- define "midaz-tracer.labels" -}} +helm.sh/chart: {{ include "midaz.chart" .context }} +{{ include "midaz-tracer.selectorLabels" (dict "context" .context "name" .name) }} +app.kubernetes.io/version: {{ include "tracer.versionLabelValue" .context }} +app.kubernetes.io/managed-by: {{ .context.Release.Service }} +{{- end }} + +{{/* +Tracer Selector labels +*/}} +{{- define "midaz-tracer.selectorLabels" -}} +{{- if .name -}} +app.kubernetes.io/name: {{ include "midaz.name" .context }}-{{ .name }} +{{- end }} +app.kubernetes.io/instance: {{ .context.Release.Name }} +{{- end }} + {{/* Enable internal dependencies */}} @@ -215,3 +260,186 @@ Secret/Service names render even when all bundled subcharts are disabled {{- end -}} {{- end -}} {{- end -}} + +{{/* +midaz.tagIsV4 — reports "true" when the passed image tag is a semver >= 4.0.0 +(pre-releases included), "" otherwise. Non-semver tags ("latest", digests, +branch builds) resolve to "" so they never trip a version-gated requirement. + +Midaz v4 changed two boot contracts that 3.x does not have: the unified ledger +binary serves CRM in-process (so it initializes the CRM cipher at startup) and +neither ledger nor tracer migrates its schema anymore. Both are gated on this +helper so 3.x releases keep rendering exactly as before. +*/}} +{{- define "midaz.tagIsV4" -}} +{{- $tag := . | toString | trimPrefix "v" -}} +{{- /* Anchored, complete SemVer 2.0.0 match: semverCompare hard-fails rendering + on a malformed version, so a prefix-only test would let "4.0.0.1" through + and abort the render instead of resolving to "not v4". */ -}} +{{- if regexMatch "^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" $tag -}} +{{- if semverCompare ">=4.0.0-0" $tag -}}true{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +midaz.ledgerMigrationsEnabled — resolves the tri-state ledger.migrations.enabled. +Unset (null) means "auto": on for 4.x ledger tags, off for 3.x, which still runs +its migrations in-process. An explicit true/false always wins, so operators who +apply the schema out-of-band (managed-Postgres S3 pipeline) can pin it off. +*/}} +{{- define "midaz.ledgerMigrationsEnabled" -}} +{{- $enabled := dig "migrations" "enabled" nil .Values.ledger -}} +{{- if kindIs "invalid" $enabled -}} +{{- include "midaz.tagIsV4" (.Values.ledger.image.tag | default .Chart.AppVersion) -}} +{{- else if eq (toString $enabled) "true" -}} +true +{{- end -}} +{{- end -}} + +{{/* +midaz-tracer.migrationsFullname — one Job name per migration image tag. A Job +spec is immutable, so a stable name would silently skip the run on upgrade; the +tag suffix makes every version bump create a new Job. Re-running is safe: +golang-migrate tracks progress in the schema_migrations table. +*/}} +{{- define "midaz-tracer.migrationsFullname" -}} +{{- include "midaz.migrationsJobName" (dict "base" (include "midaz-tracer.fullname" .) "tag" (include "midaz-tracer.migrationsTag" .)) -}} +{{- end -}} + +{{- define "midaz-ledger.migrationsFullname" -}} +{{- include "midaz.migrationsJobName" (dict "base" (include "midaz-ledger.fullname" .) "tag" (include "midaz-ledger.migrationsTag" .)) -}} +{{- end -}} + +{{/* +midaz.migrationsJobName — "-migrations-" while it fits in the 63 +character limit. Beyond that, truncating the whole string would cut the tag off +and make two releases collide on one immutable Job name, so the long form +truncates the base and ends in a hash of the tag, which stays discriminating. +*/}} +{{- define "midaz.migrationsJobName" -}} +{{- $tag := regexReplaceAll "[^a-z0-9.]+" (lower .tag) "-" -}} +{{- $name := printf "%s-migrations-%s" .base $tag -}} +{{- if le (len $name) 63 -}} +{{- $name | trimSuffix "-" | trimSuffix "." -}} +{{- else -}} +{{- printf "%s-migrations-%s" (trunc 42 .base | trimSuffix "-" | trimSuffix ".") (sha256sum $tag | trunc 8) -}} +{{- end -}} +{{- end -}} + +{{/* +Migration-runner image tags default to the matching application image tag so the +schema and the binary reading it can never drift when only one is bumped. +*/}} +{{- define "midaz-tracer.migrationsTag" -}} +{{- dig "migrations" "image" "tag" "" .Values.tracer | default .Values.tracer.image.tag | default .Chart.AppVersion -}} +{{- end -}} + +{{- define "midaz-ledger.migrationsTag" -}} +{{- dig "migrations" "image" "tag" "" .Values.ledger | default .Values.ledger.image.tag | default .Chart.AppVersion -}} +{{- end -}} + +{{/* +midaz-tracer.validate — render-time mirror of the tracer bootstrap validators +(components/tracer/internal/bootstrap). Every rule below is a configuration the +v4 process rejects at boot, so failing the render turns a CrashLoopBackOff into +a helm error the operator can read. +*/}} +{{- define "midaz-tracer.validate" -}} +{{- $tracer := .Values.tracer -}} +{{- $cm := $tracer.configmap | default dict -}} +{{- $secrets := $tracer.secrets | default dict -}} +{{- $extra := $tracer.extraEnvVars | default dict -}} +{{- if and $tracer.useExistingSecret (not $tracer.existingSecretName) -}} +{{- fail "tracer.useExistingSecret=true requires tracer.existingSecretName (an empty secretRef.name is rejected by the API server)" -}} +{{- end -}} +{{- if include "midaz.isTrue" ($cm.API_KEY_ENABLED | default "false") -}} +{{- if and (not $secrets.API_KEY) (not $tracer.useExistingSecret) -}} +{{- fail "tracer.secrets.API_KEY is required when API_KEY_ENABLED=true (ValidateAuthConfig rejects an empty key at boot); or set tracer.useExistingSecret" -}} +{{- end -}} +{{- if eq ($cm.CORS_ALLOWED_ORIGINS | default "*" | toString) "*" -}} +{{- fail "tracer.configmap.CORS_ALLOWED_ORIGINS=\"*\" is rejected at boot when API_KEY_ENABLED=true: any site could drive authenticated calls once the key leaks. Set a concrete origin allow-list" -}} +{{- end -}} +{{- end -}} +{{- if include "midaz.isTrue" ($cm.MULTI_TENANT_ENABLED | default "false") -}} +{{- if not (include "midaz.isTrue" ($cm.PLUGIN_AUTH_ENABLED | default "false")) -}} +{{- fail "tracer.configmap.PLUGIN_AUTH_ENABLED must be \"true\" when MULTI_TENANT_ENABLED=true: API-key-only auth cannot verify tenant JWT signatures, so any caller could forge a tenantId" -}} +{{- end -}} +{{- if or (include "midaz.isTrue" ($cm.API_KEY_ENABLED_ONLY_VALIDATION | default "false")) (include "midaz.isTrue" (dig "API_KEY_ENABLED_ONLY_VALIDATION" "false" $extra)) -}} +{{- fail "API_KEY_ENABLED_ONLY_VALIDATION=true is incompatible with MULTI_TENANT_ENABLED=true: it lets /v1/validations bypass plugin auth, reopening cross-tenant forgery" -}} +{{- end -}} +{{- if and (not $secrets.MULTI_TENANT_SERVICE_API_KEY) (not $tracer.useExistingSecret) -}} +{{- fail "tracer.secrets.MULTI_TENANT_SERVICE_API_KEY is required when MULTI_TENANT_ENABLED=true" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +midaz.isTrue — "true" when the value is one of the tokens strconv.ParseBool +accepts as true, "" otherwise. Midaz loads boolean env vars through ParseBool, +so `TRUE`, `True`, `1`, `t` and `T` all enable a feature at runtime; comparing +against the literal string "true" would let those render past a validation the +process then fails, and would reject a valid `PLUGIN_AUTH_ENABLED=TRUE`. +Matching is exact (no trimming), like ParseBool: " true" is false at runtime. +*/}} +{{- define "midaz.isTrue" -}} +{{- if has (. | toString) (list "1" "t" "T" "TRUE" "true" "True") -}}true{{- end -}} +{{- end -}} + +{{/* +midaz.postgresqlPrimaryHost — in-cluster hostname of the bundled PostgreSQL +primary for THIS release, so a release named something other than `midaz` still +resolves its database. Bitnami names the Service `-primary` in the +replication topology and `` when standalone. Returns "" when +PostgreSQL is external, where only the operator knows the address. +*/}} +{{- define "midaz.postgresqlPrimaryHost" -}} +{{- $pg := .Values.postgresql | default dict -}} +{{- if and (ne (toString $pg.enabled) "false") (not $pg.external) -}} +{{- $fullname := include "common.names.dependency.fullname" (dict "chartName" "postgresql" "chartValues" $pg "context" .) -}} +{{- if eq ($pg.architecture | default "standalone") "replication" -}} +{{- printf "%s-primary" $fullname -}} +{{- else -}} +{{- $fullname -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +midaz-tracer.dbHost — explicit tracer.configmap.DB_HOST, else the bundled +PostgreSQL primary of this release. Fails when neither is available, which is +the external-PostgreSQL case where a wrong guess would silently point the +Deployment and the migration Job at a host that does not exist. +*/}} +{{- define "midaz-tracer.dbHost" -}} +{{- $explicit := (.Values.tracer.configmap | default dict).DB_HOST -}} +{{- if $explicit -}} +{{- $explicit -}} +{{- else -}} +{{- $bundled := include "midaz.postgresqlPrimaryHost" . -}} +{{- if $bundled -}} +{{- $bundled -}} +{{- else -}} +{{- fail "tracer.configmap.DB_HOST is required when PostgreSQL is external (postgresql.enabled=false / postgresql.external=true)" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +midaz.validateLcryptoKey — LCRYPTO_ENCRYPT_SECRET_KEY is hex-decoded and handed +to aes.NewCipher (lib-commons Crypto.InitializeCipher), so it must be hex +encoding 16, 24 or 32 bytes. A non-hex value fails at boot with +"encoding/hex: invalid byte" and a wrong length with "crypto/aes: invalid key +size", both of which this catches at render time instead. Only applies to keys +supplied through values; an existing Secret is opaque to the chart. +*/}} +{{- define "midaz.validateLcryptoKey" -}} +{{- $key := .key | toString -}} +{{- if $key -}} +{{- if not (regexMatch "^[0-9a-fA-F]+$" $key) -}} +{{- fail (printf "%s must be a hex-encoded AES key (0-9a-f only): it is hex-decoded before aes.NewCipher, so a non-hex value fails at boot" .name) -}} +{{- end -}} +{{- if not (has (len $key) (list 32 48 64)) -}} +{{- fail (printf "%s must be 32, 48 or 64 hex characters (AES-128/192/256); got %d" .name (len $key)) -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/midaz/templates/bootstrap-postgres.yaml b/charts/midaz/templates/bootstrap-postgres.yaml index a821c998f..247116c30 100644 --- a/charts/midaz/templates/bootstrap-postgres.yaml +++ b/charts/midaz/templates/bootstrap-postgres.yaml @@ -83,6 +83,9 @@ spec: ONB_EXISTS=0 TRX_EXISTS=0 ROLE_EXISTS=0 + {{- if .Values.tracer.enabled }} + TRACER_EXISTS=0 + {{- end }} if PGPASSWORD="$DB_ADMIN_PASSWORD" psql -At -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "$DB_DATABASE" -c "SELECT 1 FROM pg_database WHERE datname='onboarding'" | grep -q 1; then ONB_EXISTS=1 @@ -93,9 +96,14 @@ spec: if PGPASSWORD="$DB_ADMIN_PASSWORD" psql -At -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "$DB_DATABASE" -c "SELECT 1 FROM pg_roles WHERE rolname='midaz'" | grep -q 1; then ROLE_EXISTS=1 fi + {{- if .Values.tracer.enabled }} + if PGPASSWORD="$DB_ADMIN_PASSWORD" psql -At -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "$DB_DATABASE" -c "SELECT 1 FROM pg_database WHERE datname='{{ .Values.tracer.configmap.DB_NAME | default "tracer" }}'" | grep -q 1; then + TRACER_EXISTS=1 + fi + {{- end }} - if [ "$ONB_EXISTS" = "1" ] && [ "$TRX_EXISTS" = "1" ] && [ "$ROLE_EXISTS" = "1" ]; then - echo "Postgres bootstrap config already exists (databases 'onboarding' and 'transaction' and role 'midaz'). Skipping." + if [ "$ONB_EXISTS" = "1" ] && [ "$TRX_EXISTS" = "1" ] && [ "$ROLE_EXISTS" = "1" ]{{ if .Values.tracer.enabled }} && [ "$TRACER_EXISTS" = "1" ]{{ end }}; then + echo "Postgres bootstrap config already exists (databases 'onboarding' and 'transaction'{{ if .Values.tracer.enabled }} and '{{ .Values.tracer.configmap.DB_NAME | default "tracer" }}'{{ end }} and role 'midaz'). Skipping." exit 0 fi @@ -113,6 +121,16 @@ spec: echo "Creating database 'transaction'..." PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "$DB_DATABASE" -c "CREATE DATABASE transaction" fi + {{- if .Values.tracer.enabled }} + + # Tracer runs as the same 'midaz' role, so only its database is created here. + if [ "$TRACER_EXISTS" = "1" ]; then + echo "Database '{{ .Values.tracer.configmap.DB_NAME | default "tracer" }}' already exists. Skipping creation." + else + echo "Creating database '{{ .Values.tracer.configmap.DB_NAME | default "tracer" }}'..." + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "$DB_DATABASE" -c "CREATE DATABASE {{ .Values.tracer.configmap.DB_NAME | default "tracer" }}" + fi + {{- end }} # Role if [ "$ROLE_EXISTS" = "1" ]; then @@ -128,4 +146,8 @@ spec: PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "$DB_DATABASE" -c "GRANT ALL PRIVILEGES ON DATABASE transaction TO midaz" PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d onboarding -c "GRANT ALL ON SCHEMA public TO midaz" PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d transaction -c "GRANT ALL ON SCHEMA public TO midaz" + {{- if .Values.tracer.enabled }} + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "$DB_DATABASE" -c "GRANT ALL PRIVILEGES ON DATABASE {{ .Values.tracer.configmap.DB_NAME | default "tracer" }} TO midaz" + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d {{ .Values.tracer.configmap.DB_NAME | default "tracer" }} -c "GRANT ALL ON SCHEMA public TO midaz" + {{- end }} {{- end }} \ No newline at end of file diff --git a/charts/midaz/templates/configmap-postgres-midaz.yaml b/charts/midaz/templates/configmap-postgres-midaz.yaml index 68160b5bf..daadecee5 100644 --- a/charts/midaz/templates/configmap-postgres-midaz.yaml +++ b/charts/midaz/templates/configmap-postgres-midaz.yaml @@ -3,5 +3,8 @@ kind: ConfigMap metadata: name: midaz-init-sql data: + {{- /* Rendered through tpl so the tracer database it creates tracks + tracer.configmap.DB_NAME, the same name the external bootstrap Job, + the tracer Deployment and its migration Job use. */}} init.sql: | -{{ (.Files.Get "files/midaz/init.sql") | indent 4 }} +{{ tpl (.Files.Get "files/midaz/init.sql") . | indent 4 }} diff --git a/charts/midaz/templates/ledger/configmap.yaml b/charts/midaz/templates/ledger/configmap.yaml index 547708f7d..cdf62fe29 100644 --- a/charts/midaz/templates/ledger/configmap.yaml +++ b/charts/midaz/templates/ledger/configmap.yaml @@ -50,11 +50,12 @@ data: # MULTI-TENANT # ============================================================================= MULTI_TENANT_ENABLED: {{ .Values.ledger.configmap.MULTI_TENANT_ENABLED | default "false" | quote }} - {{- if eq (.Values.ledger.configmap.MULTI_TENANT_ENABLED | default "false" | toString) "true" }} + {{- if include "midaz.isTrue" (.Values.ledger.configmap.MULTI_TENANT_ENABLED | default "false") }} MULTI_TENANT_URL: {{ required "ledger.configmap.MULTI_TENANT_URL is required when MULTI_TENANT_ENABLED=true" .Values.ledger.configmap.MULTI_TENANT_URL | quote }} MULTI_TENANT_SERVICE_NAME: {{ .Values.ledger.configmap.MULTI_TENANT_SERVICE_NAME | default "ledger" | quote }} MULTI_TENANT_CIRCUIT_BREAKER_THRESHOLD: {{ .Values.ledger.configmap.MULTI_TENANT_CIRCUIT_BREAKER_THRESHOLD | default "5" | quote }} MULTI_TENANT_CIRCUIT_BREAKER_TIMEOUT_SEC: {{ .Values.ledger.configmap.MULTI_TENANT_CIRCUIT_BREAKER_TIMEOUT_SEC | default "30" | quote }} + MULTI_TENANT_CACHE_TTL_SEC: {{ .Values.ledger.configmap.MULTI_TENANT_CACHE_TTL_SEC | default "120" | quote }} MULTI_TENANT_REDIS_HOST: {{ required "ledger.configmap.MULTI_TENANT_REDIS_HOST is required when MULTI_TENANT_ENABLED=true" .Values.ledger.configmap.MULTI_TENANT_REDIS_HOST | quote }} MULTI_TENANT_REDIS_PORT: {{ .Values.ledger.configmap.MULTI_TENANT_REDIS_PORT | default "6379" | quote }} MULTI_TENANT_REDIS_TLS: {{ .Values.ledger.configmap.MULTI_TENANT_REDIS_TLS | default "false" | quote }} @@ -128,6 +129,73 @@ data: MONGO_TRANSACTION_PARAMETERS: {{ .Values.ledger.configmap.MONGO_TRANSACTION_PARAMETERS | default "" | quote }} MONGO_TRANSACTION_TLS_CA_CERT: {{ .Values.ledger.configmap.MONGO_TRANSACTION_TLS_CA_CERT | default "" | quote }} + # ============================================================================= + # MONGO DB - CRM MODULE (folded into the unified ledger binary) + # ============================================================================= + MONGO_CRM_URI: {{ .Values.ledger.configmap.MONGO_CRM_URI | default "mongodb" | quote }} + MONGO_CRM_HOST: {{ .Values.ledger.configmap.MONGO_CRM_HOST | default "midaz-mongodb" | quote }} + MONGO_CRM_NAME: {{ .Values.ledger.configmap.MONGO_CRM_NAME | default "crm" | quote }} + MONGO_CRM_USER: {{ .Values.ledger.configmap.MONGO_CRM_USER | default "midaz" | quote }} + MONGO_CRM_PORT: {{ .Values.ledger.configmap.MONGO_CRM_PORT | default "27017" | quote }} + # MONGO_CRM_PASSWORD lives in the ledger Secret (see templates/ledger/secrets.yaml) + MONGO_CRM_MAX_POOL_SIZE: {{ .Values.ledger.configmap.MONGO_CRM_MAX_POOL_SIZE | default "1000" | quote }} + MONGO_CRM_PARAMETERS: {{ .Values.ledger.configmap.MONGO_CRM_PARAMETERS | default "" | quote }} + MONGO_CRM_TLS_CA_CERT: {{ .Values.ledger.configmap.MONGO_CRM_TLS_CA_CERT | default "" | quote }} + + # ============================================================================= + # MONGO DB - FEES MODULE (folded into the unified ledger binary) + # ============================================================================= + MONGO_FEES_URI: {{ .Values.ledger.configmap.MONGO_FEES_URI | default "mongodb" | quote }} + MONGO_FEES_HOST: {{ .Values.ledger.configmap.MONGO_FEES_HOST | default "midaz-mongodb" | quote }} + MONGO_FEES_NAME: {{ .Values.ledger.configmap.MONGO_FEES_NAME | default "fees" | quote }} + MONGO_FEES_USER: {{ .Values.ledger.configmap.MONGO_FEES_USER | default "midaz" | quote }} + MONGO_FEES_PORT: {{ .Values.ledger.configmap.MONGO_FEES_PORT | default "27017" | quote }} + # MONGO_FEES_PASSWORD lives in the ledger Secret (see templates/ledger/secrets.yaml) + MONGO_FEES_MAX_POOL_SIZE: {{ .Values.ledger.configmap.MONGO_FEES_MAX_POOL_SIZE | default "100" | quote }} + MONGO_FEES_PARAMETERS: {{ .Values.ledger.configmap.MONGO_FEES_PARAMETERS | default "" | quote }} + MONGO_FEES_TLS_CA_CERT: {{ .Values.ledger.configmap.MONGO_FEES_TLS_CA_CERT | default "" | quote }} + + # ============================================================================= + # LCRYPTO — key material lives in the ledger Secret, never here. + # LCRYPTO_HASH_SECRET_KEY and LCRYPTO_ENCRYPT_SECRET_KEY protect CRM holder + # fields (PII); see templates/ledger/secrets.yaml. + # ============================================================================= + + # ============================================================================= + # KMS (encryption vendor selector; see midaz pkg/crypto/resolver.go) + # KMS_VAULT_SECRET_ID is the AppRole credential and lives in the Secret. + # ============================================================================= + KMS_VENDOR: {{ .Values.ledger.configmap.KMS_VENDOR | default "none" | quote }} + # Vault fields required only when KMS_VENDOR=hashicorp-vault (envelope encryption) + {{- if eq (.Values.ledger.configmap.KMS_VENDOR | default "none") "hashicorp-vault" }} + KMS_VAULT_ADDR: {{ required "ledger.configmap.KMS_VAULT_ADDR is required when KMS_VENDOR=hashicorp-vault" .Values.ledger.configmap.KMS_VAULT_ADDR | quote }} + KMS_VAULT_ROLE_ID: {{ required "ledger.configmap.KMS_VAULT_ROLE_ID is required when KMS_VENDOR=hashicorp-vault" .Values.ledger.configmap.KMS_VAULT_ROLE_ID | quote }} + KMS_VAULT_AUTH_METHOD: {{ required "ledger.configmap.KMS_VAULT_AUTH_METHOD is required when KMS_VENDOR=hashicorp-vault" .Values.ledger.configmap.KMS_VAULT_AUTH_METHOD | quote }} + {{- else }} + KMS_VAULT_ADDR: {{ .Values.ledger.configmap.KMS_VAULT_ADDR | default "" | quote }} + KMS_VAULT_ROLE_ID: {{ .Values.ledger.configmap.KMS_VAULT_ROLE_ID | default "" | quote }} + KMS_VAULT_AUTH_METHOD: {{ .Values.ledger.configmap.KMS_VAULT_AUTH_METHOD | default "" | quote }} + {{- end }} + + # ============================================================================= + # TRACER RESERVATION SEAM (ledger -> tracer client side) + # TRACER_BASE_URL empty disables the integration (nil reserver). + # ============================================================================= + TRACER_BASE_URL: {{ .Values.ledger.configmap.TRACER_BASE_URL | default "" | quote }} + {{- /* The seam accepts "grpc" or "rest". The bundled tracer only serves gRPC + when TRACER_GRPC_PORT is set, so selecting grpc against an in-chart + tracer that has it unset would dial a port nothing listens on. */ -}} + {{- $tracerTransport := .Values.ledger.configmap.TRACER_TRANSPORT | default "rest" }} + {{- if and .Values.ledger.configmap.TRACER_BASE_URL (eq (lower $tracerTransport) "grpc") .Values.tracer.enabled (not .Values.tracer.configmap.TRACER_GRPC_PORT) }} + {{- fail "ledger.configmap.TRACER_TRANSPORT=grpc requires tracer.configmap.TRACER_GRPC_PORT to be set: the bundled tracer keeps its gRPC seam off and its Service exposes only HTTP. Set the port, or use TRACER_TRANSPORT=rest" }} + {{- end }} + TRACER_TRANSPORT: {{ $tracerTransport | quote }} + TRACER_TLS_MODE: {{ .Values.ledger.configmap.TRACER_TLS_MODE | default "" | quote }} + TRACER_TIMEOUT_MS: {{ .Values.ledger.configmap.TRACER_TIMEOUT_MS | default "" | quote }} + TRACER_TLS_CERT_FILE: {{ .Values.ledger.configmap.TRACER_TLS_CERT_FILE | default "" | quote }} + TRACER_TLS_KEY_FILE: {{ .Values.ledger.configmap.TRACER_TLS_KEY_FILE | default "" | quote }} + TRACER_TLS_CA_FILE: {{ .Values.ledger.configmap.TRACER_TLS_CA_FILE | default "" | quote }} + # ============================================================================= # REDIS (shared between modules) # ============================================================================= @@ -268,6 +336,26 @@ data: SD_WATCH_WAIT_TIME: {{ .Values.ledger.configmap.SD_WATCH_WAIT_TIME | default "" | quote }} SD_WORKLOAD: {{ .Values.ledger.configmap.SD_WORKLOAD | default "" | quote }} STREAMING_ENABLED: {{ .Values.ledger.configmap.STREAMING_ENABLED | default "false" | quote }} + + # ============================================================================= + # STREAMING (lib-streaming producer; disabled by default) + # ============================================================================= + STREAMING_BROKERS: {{ .Values.ledger.configmap.STREAMING_BROKERS | default "" | quote }} + STREAMING_CLIENT_ID: {{ .Values.ledger.configmap.STREAMING_CLIENT_ID | default "" | quote }} + STREAMING_CLOUDEVENTS_SOURCE: {{ .Values.ledger.configmap.STREAMING_CLOUDEVENTS_SOURCE | default "" | quote }} + STREAMING_COMPRESSION: {{ .Values.ledger.configmap.STREAMING_COMPRESSION | default "" | quote }} + STREAMING_REQUIRED_ACKS: {{ .Values.ledger.configmap.STREAMING_REQUIRED_ACKS | default "" | quote }} + STREAMING_BATCH_LINGER_MS: {{ .Values.ledger.configmap.STREAMING_BATCH_LINGER_MS | default "" | quote }} + + # ============================================================================= + # FEE ENGINE + # ============================================================================= + DEFAULT_CURRENCY: {{ .Values.ledger.configmap.DEFAULT_CURRENCY | default "USD" | quote }} + + # ============================================================================= + # CASDOOR + # ============================================================================= + CASDOOR_JWK_ADDRESS: {{ .Values.ledger.configmap.CASDOOR_JWK_ADDRESS | default "" | quote }} SWAGGER_VERSION: {{ .Values.ledger.configmap.SWAGGER_VERSION | default "${VERSION}" | quote }} # Extra Env Vars {{- with .Values.ledger.extraEnvVars }} diff --git a/charts/midaz/templates/ledger/deployment.yaml b/charts/midaz/templates/ledger/deployment.yaml index d794b928c..d2cf323a7 100644 --- a/charts/midaz/templates/ledger/deployment.yaml +++ b/charts/midaz/templates/ledger/deployment.yaml @@ -5,6 +5,20 @@ metadata: name: {{ include "midaz-ledger.fullname" . }} labels: {{- include "midaz.labels" (dict "context" . "component" .Values.ledger.name "name" .Values.ledger.name ) | nindent 4 }} +{{- /* +Migration gate. When the migration Job renders, this Deployment moves one sync +wave later: the Job stays in the default wave, together with the bundled +PostgreSQL and the Secret it reads, and Argo CD only applies the Deployment once +that wave is healthy — a Job is healthy only once Complete. Set +`ledger.migrations.deploymentSyncWave: ""` to opt out. Plain `helm install` +ignores the annotation and keeps today's behaviour: the ledger crash-loops +against the unmigrated schema until the Job finishes, then starts. +*/}} +{{- $migrationSyncWave := dig "deploymentSyncWave" "1" (.Values.ledger.migrations | default dict) | toString }} +{{- if and (include "midaz.ledgerMigrationsEnabled" .) $migrationSyncWave }} + annotations: + argocd.argoproj.io/sync-wave: {{ $migrationSyncWave | quote }} +{{- end }} spec: revisionHistoryLimit: {{ .Values.ledger.revisionHistoryLimit | default 10 }} strategy: @@ -49,8 +63,10 @@ spec: TIMEOUT={{ dig "initContainer" "timeoutSeconds" 300 .Values.ledger }}; MONGO_ONBOARDING_PORT_CLEAN=$(echo "$MONGO_ONBOARDING_PORT" | sed 's/[^0-9].*//'); MONGO_TRANSACTION_PORT_CLEAN=$(echo "$MONGO_TRANSACTION_PORT" | sed 's/[^0-9].*//'); + MONGO_CRM_PORT_CLEAN=$(echo "$MONGO_CRM_PORT" | sed 's/[^0-9].*//'); + MONGO_FEES_PORT_CLEAN=$(echo "$MONGO_FEES_PORT" | sed 's/[^0-9].*//'); RABBITMQ_PORT_HOST_CLEAN=$(echo "$RABBITMQ_PORT_HOST" | sed 's/[^0-9].*//'); - for svc in "$DB_ONBOARDING_HOST:$DB_ONBOARDING_PORT" "$DB_ONBOARDING_REPLICA_HOST:$DB_ONBOARDING_REPLICA_PORT" "$DB_TRANSACTION_HOST:$DB_TRANSACTION_PORT" "$DB_TRANSACTION_REPLICA_HOST:$DB_TRANSACTION_REPLICA_PORT" "$RABBITMQ_HOST:$RABBITMQ_PORT_HOST_CLEAN" "$MONGO_ONBOARDING_HOST:$MONGO_ONBOARDING_PORT_CLEAN" "$MONGO_TRANSACTION_HOST:$MONGO_TRANSACTION_PORT_CLEAN"; + for svc in "$DB_ONBOARDING_HOST:$DB_ONBOARDING_PORT" "$DB_ONBOARDING_REPLICA_HOST:$DB_ONBOARDING_REPLICA_PORT" "$DB_TRANSACTION_HOST:$DB_TRANSACTION_PORT" "$DB_TRANSACTION_REPLICA_HOST:$DB_TRANSACTION_REPLICA_PORT" "$RABBITMQ_HOST:$RABBITMQ_PORT_HOST_CLEAN" "$MONGO_ONBOARDING_HOST:$MONGO_ONBOARDING_PORT_CLEAN" "$MONGO_TRANSACTION_HOST:$MONGO_TRANSACTION_PORT_CLEAN" "$MONGO_CRM_HOST:$MONGO_CRM_PORT_CLEAN" "$MONGO_FEES_HOST:$MONGO_FEES_PORT_CLEAN"; do echo "Checking $svc..."; ELAPSED=0; @@ -124,6 +140,8 @@ spec: {{- if or (and (ne (toString $mongo.enabled) "false") (not $mongo.external)) $mongoAuth.existingSecret }} {{- include "midaz.infraSecretRef" (dict "context" $ "subchart" "mongodb" "key" "mongodb-root-password" "envName" "MONGO_ONBOARDING_PASSWORD") | nindent 12 }} {{- include "midaz.infraSecretRef" (dict "context" $ "subchart" "mongodb" "key" "mongodb-root-password" "envName" "MONGO_TRANSACTION_PASSWORD") | nindent 12 }} + {{- include "midaz.infraSecretRef" (dict "context" $ "subchart" "mongodb" "key" "mongodb-root-password" "envName" "MONGO_CRM_PASSWORD") | nindent 12 }} + {{- include "midaz.infraSecretRef" (dict "context" $ "subchart" "mongodb" "key" "mongodb-root-password" "envName" "MONGO_FEES_PASSWORD") | nindent 12 }} {{- else }} {{- if .Values.ledger.secrets.MONGO_ONBOARDING_PASSWORD }} - name: MONGO_ONBOARDING_PASSWORD @@ -139,6 +157,20 @@ spec: name: {{ $secretName }} key: MONGO_TRANSACTION_PASSWORD {{- end }} + {{- if .Values.ledger.secrets.MONGO_CRM_PASSWORD }} + - name: MONGO_CRM_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: MONGO_CRM_PASSWORD + {{- end }} + {{- if .Values.ledger.secrets.MONGO_FEES_PASSWORD }} + - name: MONGO_FEES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: MONGO_FEES_PASSWORD + {{- end }} {{- end }} {{/* Valkey: REDIS_PASSWORD maps to Bitnami key `valkey-password`. */}} {{- $vk := .Values.valkey | default dict }} diff --git a/charts/midaz/templates/ledger/migrations-job.yaml b/charts/midaz/templates/ledger/migrations-job.yaml new file mode 100644 index 000000000..d4ef1c610 --- /dev/null +++ b/charts/midaz/templates/ledger/migrations-job.yaml @@ -0,0 +1,145 @@ +{{- if and .Values.ledger.enabled (include "midaz.ledgerMigrationsEnabled" .) }} +{{- /* +Ledger schema migration runner (onboarding + transaction). + +midaz v4 removed in-process migration from the ledger, so the unified binary +boots against an already-migrated schema. The release publishes a dedicated +`midaz-ledger-migrations` image (FROM migrate/migrate) whose entrypoint applies +both migration sets from DB_ONBOARDING_* / DB_TRANSACTION_* and exits. + +Rendered automatically for 4.x ledger tags and skipped for 3.x, which still +migrates at startup; `ledger.migrations.enabled` pins either behaviour. The Job +name carries the migration image tag because a Job spec is immutable — a stable +name would silently skip the run on upgrade. Re-running is safe: golang-migrate +tracks progress in the schema_migrations table. +*/}} +{{- $migrations := .Values.ledger.migrations }} +{{- $cm := .Values.ledger.configmap | default dict }} +{{- $pg := .Values.postgresql | default dict }} +{{- $pgAuth := $pg.auth | default dict }} +{{- $pgInternal := or (and (ne (toString $pg.enabled) "false") (not $pg.external)) $pgAuth.existingSecret }} +{{- $secretName := ternary .Values.ledger.existingSecretName (include "midaz-ledger.fullname" .) .Values.ledger.useExistingSecret }} +{{- $dbHost := $cm.DB_ONBOARDING_HOST | default "midaz-postgresql-primary" }} +{{- $dbPort := $cm.DB_ONBOARDING_PORT | default "5432" }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "midaz-ledger.migrationsFullname" . }} + labels: + {{- include "midaz.labels" (dict "context" . "component" .Values.ledger.name "name" .Values.ledger.name ) | nindent 4 }} + {{- with $migrations.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + backoffLimit: {{ $migrations.backoffLimit | default 6 }} + activeDeadlineSeconds: {{ $migrations.activeDeadlineSeconds | default 600 }} + ttlSecondsAfterFinished: {{ $migrations.ttlSecondsAfterFinished | default 259200 }} + template: + metadata: + labels: + {{- include "midaz.labels" (dict "context" . "component" .Values.ledger.name "name" .Values.ledger.name ) | nindent 8 }} + spec: + restartPolicy: Never + automountServiceAccountToken: false + {{- with .Values.ledger.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.ledger.podSecurityContext | nindent 8 }} + {{- if $migrations.waitForPostgres.enabled }} + initContainers: + - name: wait-for-postgres + image: {{ $migrations.waitForPostgres.image.repository }}:{{ $migrations.waitForPostgres.image.tag }} + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - -c + - > + TIMEOUT={{ $migrations.activeDeadlineSeconds | default 600 }}; + ELAPSED=0; + echo "Checking {{ $dbHost }}:{{ $dbPort }}..."; + while ! nc -z {{ $dbHost }} {{ $dbPort }}; do + if [ $ELAPSED -ge $TIMEOUT ]; then + echo "Timeout waiting for {{ $dbHost }}:{{ $dbPort }} after ${TIMEOUT}s"; + exit 1; + fi; + echo "{{ $dbHost }}:{{ $dbPort }} is not ready yet, waiting... (${ELAPSED}s/${TIMEOUT}s)"; + sleep 5; + ELAPSED=$((ELAPSED + 5)); + done; + echo "{{ $dbHost }}:{{ $dbPort }} is ready!"; + securityContext: + {{- toYaml .Values.ledger.securityContext | nindent 12 }} + resources: + limits: + cpu: 100m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi + {{- end }} + containers: + - name: migrations + image: {{ $migrations.image.repository }}:{{ include "midaz-ledger.migrationsTag" . }} + imagePullPolicy: {{ $migrations.image.pullPolicy | default "IfNotPresent" }} + securityContext: + {{- toYaml .Values.ledger.securityContext | nindent 12 }} + env: + - name: DB_ONBOARDING_HOST + value: {{ $dbHost | quote }} + - name: DB_ONBOARDING_PORT + value: {{ $dbPort | quote }} + - name: DB_ONBOARDING_NAME + value: {{ $cm.DB_ONBOARDING_NAME | default "onboarding" | quote }} + - name: DB_ONBOARDING_USER + value: {{ $cm.DB_ONBOARDING_USER | default "midaz" | quote }} + - name: DB_ONBOARDING_SSLMODE + value: {{ $cm.DB_ONBOARDING_SSLMODE | default "disable" | quote }} + - name: DB_TRANSACTION_HOST + value: {{ $cm.DB_TRANSACTION_HOST | default $dbHost | quote }} + - name: DB_TRANSACTION_PORT + value: {{ $cm.DB_TRANSACTION_PORT | default "5432" | quote }} + - name: DB_TRANSACTION_NAME + value: {{ $cm.DB_TRANSACTION_NAME | default "transaction" | quote }} + - name: DB_TRANSACTION_USER + value: {{ $cm.DB_TRANSACTION_USER | default "midaz" | quote }} + - name: DB_TRANSACTION_SSLMODE + value: {{ $cm.DB_TRANSACTION_SSLMODE | default "disable" | quote }} + {{/* Same single-source rule as the ledger Deployment: the bundled + PostgreSQL Secret when internal, the ledger Secret when external. */}} + {{- if $pgInternal }} + {{- include "midaz.infraSecretRef" (dict "context" $ "subchart" "postgresql" "key" "password" "envName" "DB_ONBOARDING_PASSWORD") | nindent 12 }} + {{- include "midaz.infraSecretRef" (dict "context" $ "subchart" "postgresql" "key" "password" "envName" "DB_TRANSACTION_PASSWORD") | nindent 12 }} + {{- else }} + {{- if or .Values.ledger.useExistingSecret .Values.ledger.secrets.DB_ONBOARDING_PASSWORD }} + - name: DB_ONBOARDING_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: DB_ONBOARDING_PASSWORD + {{- end }} + {{- if or .Values.ledger.useExistingSecret .Values.ledger.secrets.DB_TRANSACTION_PASSWORD }} + - name: DB_TRANSACTION_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: DB_TRANSACTION_PASSWORD + {{- end }} + {{- end }} + resources: + {{- toYaml $migrations.resources | nindent 12 }} + {{- with .Values.ledger.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ledger.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ledger.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/midaz/templates/ledger/secrets.yaml b/charts/midaz/templates/ledger/secrets.yaml index d872ef3bc..8a72ad9ed 100644 --- a/charts/midaz/templates/ledger/secrets.yaml +++ b/charts/midaz/templates/ledger/secrets.yaml @@ -43,10 +43,53 @@ data: {{- if and (not $mongoInternal) .Values.ledger.secrets.MONGO_TRANSACTION_PASSWORD }} MONGO_TRANSACTION_PASSWORD: {{ .Values.ledger.secrets.MONGO_TRANSACTION_PASSWORD | b64enc | quote }} {{- end }} + {{- if and (not $mongoInternal) .Values.ledger.secrets.MONGO_CRM_PASSWORD }} + MONGO_CRM_PASSWORD: {{ .Values.ledger.secrets.MONGO_CRM_PASSWORD | b64enc | quote }} + {{- end }} + {{- if and (not $mongoInternal) .Values.ledger.secrets.MONGO_FEES_PASSWORD }} + MONGO_FEES_PASSWORD: {{ .Values.ledger.secrets.MONGO_FEES_PASSWORD | b64enc | quote }} + {{- end }} {{- if and (not $vkInternal) .Values.ledger.secrets.REDIS_PASSWORD }} REDIS_PASSWORD: {{ .Values.ledger.secrets.REDIS_PASSWORD | b64enc | quote }} {{- end }} + # ============================================================================= + # LCRYPTO (application-level hash/encrypt keys protecting CRM holder PII) + # ============================================================================= + {{- /* Required from 4.x on with KMS_VENDOR=none: the unified binary serves CRM + in-process and initializes the legacy cipher from these keys at boot, so an + empty pair fails with an AES key-size error. 3.x ledger images have no CRM + and never read them, so the requirement is gated on the image tag. Kept as a + template comment so the rendered Secret (and its checksum) is unchanged for + 3.x releases. */ -}} + {{- $ledgerIsV4 := include "midaz.tagIsV4" (.Values.ledger.image.tag | default .Chart.AppVersion) }} + {{- $requireLcrypto := and $ledgerIsV4 (ne (.Values.ledger.configmap.KMS_VENDOR | default "none") "hashicorp-vault") }} + {{- /* The encrypt key is hex-decoded into aes.NewCipher, so its format is checked + whenever a 4.x ledger would read it. Skipped for 3.x, which never does. */ -}} + {{- if $ledgerIsV4 }} + {{- include "midaz.validateLcryptoKey" (dict "name" "ledger.secrets.LCRYPTO_ENCRYPT_SECRET_KEY" "key" (.Values.ledger.secrets.LCRYPTO_ENCRYPT_SECRET_KEY | default "")) }} + {{- end }} + {{- if $requireLcrypto }} + LCRYPTO_HASH_SECRET_KEY: {{ required "ledger.secrets.LCRYPTO_HASH_SECRET_KEY is required for midaz 4.x ledger images when KMS_VENDOR=none (the CRM cipher fails to initialize without it)" .Values.ledger.secrets.LCRYPTO_HASH_SECRET_KEY | b64enc | quote }} + LCRYPTO_ENCRYPT_SECRET_KEY: {{ required "ledger.secrets.LCRYPTO_ENCRYPT_SECRET_KEY is required for midaz 4.x ledger images when KMS_VENDOR=none (the CRM cipher fails to initialize without it)" .Values.ledger.secrets.LCRYPTO_ENCRYPT_SECRET_KEY | b64enc | quote }} + {{- else }} + {{- if .Values.ledger.secrets.LCRYPTO_HASH_SECRET_KEY }} + LCRYPTO_HASH_SECRET_KEY: {{ .Values.ledger.secrets.LCRYPTO_HASH_SECRET_KEY | b64enc | quote }} + {{- end }} + {{- if .Values.ledger.secrets.LCRYPTO_ENCRYPT_SECRET_KEY }} + LCRYPTO_ENCRYPT_SECRET_KEY: {{ .Values.ledger.secrets.LCRYPTO_ENCRYPT_SECRET_KEY | b64enc | quote }} + {{- end }} + {{- end }} + + # ============================================================================= + # KMS (Vault AppRole credential; only when KMS_VENDOR=hashicorp-vault) + # ============================================================================= + {{- if eq (.Values.ledger.configmap.KMS_VENDOR | default "none") "hashicorp-vault" }} + KMS_VAULT_SECRET_ID: {{ required "ledger.secrets.KMS_VAULT_SECRET_ID is required when KMS_VENDOR=hashicorp-vault (or use ledger.useExistingSecret)" .Values.ledger.secrets.KMS_VAULT_SECRET_ID | b64enc | quote }} + {{- else if .Values.ledger.secrets.KMS_VAULT_SECRET_ID }} + KMS_VAULT_SECRET_ID: {{ .Values.ledger.secrets.KMS_VAULT_SECRET_ID | b64enc | quote }} + {{- end }} + # ============================================================================= # SHARED SECRETS (RabbitMQ — operator-provided, see README "Known limitation") # ============================================================================= @@ -56,7 +99,7 @@ data: # ============================================================================= # MULTI-TENANT SECRETS # ============================================================================= - {{- if eq (.Values.ledger.configmap.MULTI_TENANT_ENABLED | default "false" | toString) "true" }} + {{- if include "midaz.isTrue" (.Values.ledger.configmap.MULTI_TENANT_ENABLED | default "false") }} MULTI_TENANT_SERVICE_API_KEY: {{ required "ledger.secrets.MULTI_TENANT_SERVICE_API_KEY is required when MULTI_TENANT_ENABLED=true" .Values.ledger.secrets.MULTI_TENANT_SERVICE_API_KEY | b64enc | quote }} {{- if .Values.ledger.secrets.MULTI_TENANT_REDIS_PASSWORD }} MULTI_TENANT_REDIS_PASSWORD: {{ .Values.ledger.secrets.MULTI_TENANT_REDIS_PASSWORD | b64enc | quote }} diff --git a/charts/midaz/templates/tracer/configmap.yaml b/charts/midaz/templates/tracer/configmap.yaml new file mode 100644 index 000000000..d09a2a7b7 --- /dev/null +++ b/charts/midaz/templates/tracer/configmap.yaml @@ -0,0 +1,99 @@ +{{- if .Values.tracer.enabled }} +{{- include "midaz-tracer.validate" . }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "midaz-tracer.fullname" . }} + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 4 }} +data: + # -- Default Environment variables for Tracer + ALLOW_INSECURE_TLS: {{ .Values.tracer.configmap.ALLOW_INSECURE_TLS | default "true" | quote }} + ENV_NAME: {{ .Values.tracer.configmap.ENV_NAME | default "development" | quote }} + + # APP + SERVER_PORT: {{ .Values.tracer.configmap.SERVER_PORT | default "4020" | quote }} + SERVER_ADDRESS: {{ .Values.tracer.configmap.SERVER_ADDRESS | default ":4020" | quote }} + VERSION: {{ .Values.tracer.image.tag | default .Chart.AppVersion | quote }} + + # LOG LEVEL + LOG_LEVEL: {{ .Values.tracer.configmap.LOG_LEVEL | default "info" | quote }} + + # AUTHENTICATION + API_KEY_ENABLED: {{ .Values.tracer.configmap.API_KEY_ENABLED | default "false" | quote }} + + # CORS + CORS_ALLOWED_ORIGINS: {{ .Values.tracer.configmap.CORS_ALLOWED_ORIGINS | default "*" | quote }} + + # AUTH PLUGIN (Access Manager) + PLUGIN_AUTH_ENABLED: {{ .Values.tracer.configmap.PLUGIN_AUTH_ENABLED | default "false" | quote }} + PLUGIN_AUTH_ADDRESS: {{ .Values.tracer.configmap.PLUGIN_AUTH_ADDRESS | default "http://plugin-access-manager-auth:4000" | quote }} + + # RESERVATION gRPC SEAM (opt-in; empty keeps the seam off, as the app does) + {{- with .Values.tracer.configmap.TRACER_GRPC_PORT }} + TRACER_GRPC_PORT: {{ . | quote }} + {{- end }} + + # PostgreSQL Database + DB_HOST: {{ include "midaz-tracer.dbHost" . | quote }} + DB_PORT: {{ .Values.tracer.configmap.DB_PORT | default "5432" | quote }} + DB_NAME: {{ .Values.tracer.configmap.DB_NAME | default "tracer" | quote }} + DB_USER: {{ .Values.tracer.configmap.DB_USER | default "tracer" | quote }} + DB_SSL_MODE: {{ .Values.tracer.configmap.DB_SSL_MODE | default "disable" | quote }} + {{/* No MIGRATIONS_PATH: the v4 tracer does not read it. Schema is applied by + the midaz-tracer-migrations Job (templates/tracer/migrations-job.yaml). */}} + + # GO RUNTIME + # GOMEMLIMIT must track resources.limits.memory (~90%); the image default of + # 1800MiB assumes a 2Gi container and would let the heap outgrow the cgroup. + GOMEMLIMIT: {{ .Values.tracer.configmap.GOMEMLIMIT | default "460MiB" | quote }} + + # SWAGGER + SWAGGER_TITLE: {{ .Values.tracer.configmap.SWAGGER_TITLE | default "Tracer API" | quote }} + SWAGGER_DESCRIPTION: {{ .Values.tracer.configmap.SWAGGER_DESCRIPTION | default "Real-time transaction validation and fraud prevention API" | quote }} + SWAGGER_HOST: {{ .Values.tracer.configmap.SWAGGER_HOST | default ":4020" | quote }} + SWAGGER_BASE_PATH: {{ .Values.tracer.configmap.SWAGGER_BASE_PATH | default "/" | quote }} + SWAGGER_SCHEMES: {{ .Values.tracer.configmap.SWAGGER_SCHEMES | default "http" | quote }} + + # CEL Expression Engine + CEL_COST_LIMIT: {{ .Values.tracer.configmap.CEL_COST_LIMIT | default "10000" | quote }} + + # OPEN TELEMETRY + OTEL_RESOURCE_SERVICE_NAME: {{ .Values.tracer.configmap.OTEL_RESOURCE_SERVICE_NAME | default "midaz-tracer" | quote }} + OTEL_LIBRARY_NAME: {{ .Values.tracer.configmap.OTEL_LIBRARY_NAME | default "github.com/LerianStudio/tracer" | quote }} + OTEL_RESOURCE_SERVICE_VERSION: {{ .Values.tracer.image.tag | default .Chart.AppVersion | quote }} + OTEL_RESOURCE_DEPLOYMENT_ENVIRONMENT: {{ .Values.tracer.configmap.ENV_NAME | default "development" | quote }} + OTEL_EXPORTER_OTLP_ENDPOINT: {{ .Values.tracer.configmap.OTEL_EXPORTER_OTLP_ENDPOINT | default "" | quote }} + ENABLE_TELEMETRY: {{ .Values.tracer.configmap.ENABLE_TELEMETRY | default "false" | quote }} + + # CLEANUP WORKER + CLEANUP_WORKER_ENABLED: {{ .Values.tracer.configmap.CLEANUP_WORKER_ENABLED | default "false" | quote }} + CLEANUP_INTERVAL_HOURS: {{ .Values.tracer.configmap.CLEANUP_INTERVAL_HOURS | default "24" | quote }} + + # MULTI-TENANT + MULTI_TENANT_ENABLED: {{ .Values.tracer.configmap.MULTI_TENANT_ENABLED | default "false" | quote }} + {{- if include "midaz.isTrue" (.Values.tracer.configmap.MULTI_TENANT_ENABLED | default "false") }} + MULTI_TENANT_URL: {{ required "tracer.configmap.MULTI_TENANT_URL is required when MULTI_TENANT_ENABLED=true" .Values.tracer.configmap.MULTI_TENANT_URL | quote }} + MULTI_TENANT_ALLOW_INSECURE_HTTP: {{ .Values.tracer.configmap.MULTI_TENANT_ALLOW_INSECURE_HTTP | default "false" | quote }} + MULTI_TENANT_REDIS_HOST: {{ required "tracer.configmap.MULTI_TENANT_REDIS_HOST is required when MULTI_TENANT_ENABLED=true" .Values.tracer.configmap.MULTI_TENANT_REDIS_HOST | quote }} + MULTI_TENANT_REDIS_PORT: {{ .Values.tracer.configmap.MULTI_TENANT_REDIS_PORT | default "6379" | quote }} + MULTI_TENANT_REDIS_TLS: {{ .Values.tracer.configmap.MULTI_TENANT_REDIS_TLS | default "true" | quote }} + MULTI_TENANT_MAX_TENANT_POOLS: {{ .Values.tracer.configmap.MULTI_TENANT_MAX_TENANT_POOLS | default "100" | quote }} + MULTI_TENANT_IDLE_TIMEOUT_SEC: {{ .Values.tracer.configmap.MULTI_TENANT_IDLE_TIMEOUT_SEC | default "300" | quote }} + MULTI_TENANT_TIMEOUT: {{ .Values.tracer.configmap.MULTI_TENANT_TIMEOUT | default "30" | quote }} + MULTI_TENANT_CIRCUIT_BREAKER_THRESHOLD: {{ .Values.tracer.configmap.MULTI_TENANT_CIRCUIT_BREAKER_THRESHOLD | default "5" | quote }} + MULTI_TENANT_CIRCUIT_BREAKER_TIMEOUT_SEC: {{ .Values.tracer.configmap.MULTI_TENANT_CIRCUIT_BREAKER_TIMEOUT_SEC | default "30" | quote }} + MULTI_TENANT_CACHE_TTL_SEC: {{ .Values.tracer.configmap.MULTI_TENANT_CACHE_TTL_SEC | default "120" | quote }} + MULTI_TENANT_CONNECTIONS_CHECK_INTERVAL_SEC: {{ .Values.tracer.configmap.MULTI_TENANT_CONNECTIONS_CHECK_INTERVAL_SEC | default "30" | quote }} + {{- if .Values.tracer.configmap.MULTI_TENANT_MAX_OPEN_CONNS_PER_TENANT }} + MULTI_TENANT_MAX_OPEN_CONNS_PER_TENANT: {{ .Values.tracer.configmap.MULTI_TENANT_MAX_OPEN_CONNS_PER_TENANT | quote }} + {{- end }} + {{- if .Values.tracer.configmap.MULTI_TENANT_MAX_IDLE_CONNS_PER_TENANT }} + MULTI_TENANT_MAX_IDLE_CONNS_PER_TENANT: {{ .Values.tracer.configmap.MULTI_TENANT_MAX_IDLE_CONNS_PER_TENANT | quote }} + {{- end }} + {{- end }} + # Extra Env Vars + {{- with .Values.tracer.extraEnvVars }} + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/charts/midaz/templates/tracer/deployment.yaml b/charts/midaz/templates/tracer/deployment.yaml new file mode 100644 index 000000000..13c5d3d8d --- /dev/null +++ b/charts/midaz/templates/tracer/deployment.yaml @@ -0,0 +1,143 @@ +{{- if .Values.tracer.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "midaz-tracer.fullname" . }} + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 4 }} +{{- /* +Migration gate — see the ledger Deployment for the reasoning. The migration Job +stays in the default sync wave with the bundled PostgreSQL and its Secret, this +Deployment moves one wave later, and Argo CD applies it only once the Job is +Complete. `tracer.migrations.deploymentSyncWave: ""` opts out. +*/}} +{{- $migrationSyncWave := dig "deploymentSyncWave" "1" (.Values.tracer.migrations | default dict) | toString }} +{{- if and .Values.tracer.migrations.enabled $migrationSyncWave }} + annotations: + argocd.argoproj.io/sync-wave: {{ $migrationSyncWave | quote }} +{{- end }} +spec: + revisionHistoryLimit: {{ .Values.tracer.revisionHistoryLimit | default 10 }} + strategy: + type: {{ .Values.tracer.deploymentUpdate.type }} + {{- if eq .Values.tracer.deploymentUpdate.type "RollingUpdate" }} + rollingUpdate: + maxSurge: {{ .Values.tracer.deploymentUpdate.maxSurge }} + maxUnavailable: {{ .Values.tracer.deploymentUpdate.maxUnavailable }} + {{- end }} + {{- if not .Values.tracer.autoscaling.enabled }} + replicas: {{ .Values.tracer.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "midaz-tracer.selectorLabels" (dict "context" . "name" .Values.tracer.name) | nindent 6 }} + template: + metadata: + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 8 }} + {{- with .Values.tracer.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.tracer.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tracer.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ include "midaz-tracer.fullname" . }} + image: {{ .Values.tracer.image.repository }}:{{ .Values.tracer.image.tag | default .Chart.AppVersion }} + imagePullPolicy: {{ .Values.tracer.image.pullPolicy }} + {{- with .Values.tracer.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.tracer.service.port }} + protocol: TCP + {{- if .Values.tracer.configmap.TRACER_GRPC_PORT }} + {{/* TRACER_GRPC_PORT is a listen address (":4021"); the container port is + its numeric half, kept in sync with the Service via service.grpcPort. */}} + - name: grpc + containerPort: {{ .Values.tracer.service.grpcPort | default 4021 }} + protocol: TCP + {{- end }} + envFrom: + {{- if not .Values.tracer.useExistingSecret }} + - secretRef: + name: {{ include "midaz-tracer.fullname" . }} + {{- else }} + - secretRef: + name: {{ required "tracer.existingSecretName is required when tracer.useExistingSecret=true" .Values.tracer.existingSecretName }} + {{- end }} + - configMapRef: + name: {{ include "midaz-tracer.fullname" . }} + env: + {{/* existingSecretName presence is enforced by midaz-tracer.validate; ternary + cannot carry `required` because Go templates evaluate both arms. */}} + {{- $secretName := ternary .Values.tracer.existingSecretName (include "midaz-tracer.fullname" .) .Values.tracer.useExistingSecret }} + {{/* PostgreSQL: DB_PASSWORD is single-sourced from the Bitnami postgresql subchart Secret when internal. */}} + {{- $pg := .Values.postgresql | default dict }} + {{- $pgAuth := $pg.auth | default dict }} + {{- if or (and (ne (toString $pg.enabled) "false") (not $pg.external)) $pgAuth.existingSecret }} + {{- include "midaz.infraSecretRef" (dict "context" $ "subchart" "postgresql" "key" "password" "envName" "DB_PASSWORD") | nindent 12 }} + {{- else if .Values.tracer.secrets.DB_PASSWORD }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: DB_PASSWORD + {{- end }} + {{- $otel := index .Values "otel-collector-lerian" }} + {{- if $otel.enabled }} + - name: "POD_IP" + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: "HOST_IP" + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: "OTEL_EXPORTER_OTLP_ENDPOINT" + value: "$(HOST_IP):4317" + - name: "OTEL_RESOURCE_ATTRIBUTES" + value: "k8s.pod.ip=$(POD_IP)" + {{- end }} + resources: + {{- toYaml .Values.tracer.resources | nindent 12 }} + readinessProbe: + httpGet: + path: {{ .Values.tracer.readinessProbe.path | default "/readyz" }} + port: {{ .Values.tracer.service.port }} + initialDelaySeconds: {{ .Values.tracer.readinessProbe.initialDelaySeconds | default 5 }} + periodSeconds: {{ .Values.tracer.readinessProbe.periodSeconds | default 10 }} + timeoutSeconds: {{ .Values.tracer.readinessProbe.timeoutSeconds | default 5 }} + successThreshold: {{ .Values.tracer.readinessProbe.successThreshold | default 1 }} + failureThreshold: {{ .Values.tracer.readinessProbe.failureThreshold | default 3 }} + livenessProbe: + httpGet: + path: {{ .Values.tracer.livenessProbe.path | default "/health" }} + port: {{ .Values.tracer.service.port }} + initialDelaySeconds: {{ .Values.tracer.livenessProbe.initialDelaySeconds | default 15 }} + periodSeconds: {{ .Values.tracer.livenessProbe.periodSeconds | default 20 }} + timeoutSeconds: {{ .Values.tracer.livenessProbe.timeoutSeconds | default 5 }} + successThreshold: {{ .Values.tracer.livenessProbe.successThreshold | default 1 }} + failureThreshold: {{ .Values.tracer.livenessProbe.failureThreshold | default 3 }} + {{- with .Values.tracer.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tracer.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tracer.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/midaz/templates/tracer/hpa.yaml b/charts/midaz/templates/tracer/hpa.yaml new file mode 100644 index 000000000..6eda2bc77 --- /dev/null +++ b/charts/midaz/templates/tracer/hpa.yaml @@ -0,0 +1,32 @@ +{{- if and .Values.tracer.enabled .Values.tracer.autoscaling.enabled (or .Values.tracer.autoscaling.targetCPUUtilizationPercentage .Values.tracer.autoscaling.targetMemoryUtilizationPercentage) }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "midaz-tracer.fullname" . }} + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "midaz-tracer.fullname" . }} + minReplicas: {{ .Values.tracer.autoscaling.minReplicas }} + maxReplicas: {{ .Values.tracer.autoscaling.maxReplicas }} + metrics: + {{- if .Values.tracer.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.tracer.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.tracer.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.tracer.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/midaz/templates/tracer/ingress.yaml b/charts/midaz/templates/tracer/ingress.yaml new file mode 100644 index 000000000..e31e51ce7 --- /dev/null +++ b/charts/midaz/templates/tracer/ingress.yaml @@ -0,0 +1,61 @@ +{{- if and .Values.tracer.enabled .Values.tracer.ingress.enabled -}} +{{- $fullName := include "midaz-tracer.fullname" . -}} +{{- $svcPort := .Values.tracer.service.port -}} +{{- if and .Values.tracer.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.tracer.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.tracer.ingress.annotations "kubernetes.io/ingress.class" .Values.tracer.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 4 }} + {{- with .Values.tracer.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.tracer.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.tracer.ingress.className }} + {{- end }} + {{- if .Values.tracer.ingress.tls }} + tls: + {{- range .Values.tracer.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.tracer.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/midaz/templates/tracer/migrations-job.yaml b/charts/midaz/templates/tracer/migrations-job.yaml new file mode 100644 index 000000000..a5ed06b9c --- /dev/null +++ b/charts/midaz/templates/tracer/migrations-job.yaml @@ -0,0 +1,130 @@ +{{- if and .Values.tracer.enabled .Values.tracer.migrations.enabled }} +{{- /* +Tracer schema migration runner. + +midaz v4 removed in-process migration from the tracer: the service boots against +an already-migrated database, and the release publishes a dedicated +`midaz-tracer-migrations` image (FROM migrate/migrate) whose entrypoint applies +the migration set from DB_* (or DATABASE_URL) and exits. + +The Job name carries the migration image tag because a Job spec is immutable: a +stable name would leave the original Job in place on upgrade and silently skip +the new migrations. Re-running is safe — golang-migrate keeps its progress in +the schema_migrations table, not on disk. +*/}} +{{- $migrations := .Values.tracer.migrations }} +{{- $cm := .Values.tracer.configmap | default dict }} +{{- $pg := .Values.postgresql | default dict }} +{{- $pgAuth := $pg.auth | default dict }} +{{- $pgInternal := or (and (ne (toString $pg.enabled) "false") (not $pg.external)) $pgAuth.existingSecret }} +{{- $secretName := ternary .Values.tracer.existingSecretName (include "midaz-tracer.fullname" .) .Values.tracer.useExistingSecret }} +{{- $dbHost := include "midaz-tracer.dbHost" . }} +{{- $dbPort := $cm.DB_PORT | default "5432" }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "midaz-tracer.migrationsFullname" . }} + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 4 }} + {{- with $migrations.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + backoffLimit: {{ $migrations.backoffLimit | default 6 }} + activeDeadlineSeconds: {{ $migrations.activeDeadlineSeconds | default 600 }} + ttlSecondsAfterFinished: {{ $migrations.ttlSecondsAfterFinished | default 259200 }} + template: + metadata: + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 8 }} + spec: + restartPolicy: Never + automountServiceAccountToken: false + {{- with .Values.tracer.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tracer.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if $migrations.waitForPostgres.enabled }} + initContainers: + - name: wait-for-postgres + image: {{ $migrations.waitForPostgres.image.repository }}:{{ $migrations.waitForPostgres.image.tag }} + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - -c + - > + TIMEOUT={{ $migrations.activeDeadlineSeconds | default 600 }}; + ELAPSED=0; + echo "Checking {{ $dbHost }}:{{ $dbPort }}..."; + while ! nc -z {{ $dbHost }} {{ $dbPort }}; do + if [ $ELAPSED -ge $TIMEOUT ]; then + echo "Timeout waiting for {{ $dbHost }}:{{ $dbPort }} after ${TIMEOUT}s"; + exit 1; + fi; + echo "{{ $dbHost }}:{{ $dbPort }} is not ready yet, waiting... (${ELAPSED}s/${TIMEOUT}s)"; + sleep 5; + ELAPSED=$((ELAPSED + 5)); + done; + echo "{{ $dbHost }}:{{ $dbPort }} is ready!"; + {{- with .Values.tracer.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + limits: + cpu: 100m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi + {{- end }} + containers: + - name: migrations + image: {{ $migrations.image.repository }}:{{ include "midaz-tracer.migrationsTag" . }} + imagePullPolicy: {{ $migrations.image.pullPolicy | default "IfNotPresent" }} + {{- with .Values.tracer.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: DB_HOST + value: {{ $dbHost | quote }} + - name: DB_PORT + value: {{ $dbPort | quote }} + - name: DB_NAME + value: {{ $cm.DB_NAME | default "tracer" | quote }} + - name: DB_USER + value: {{ $cm.DB_USER | default "midaz" | quote }} + - name: DB_SSL_MODE + value: {{ $cm.DB_SSL_MODE | default "disable" | quote }} + {{/* Same single-source rule as the tracer Deployment: the bundled + PostgreSQL Secret when internal, the tracer Secret when external. */}} + {{- if $pgInternal }} + {{- include "midaz.infraSecretRef" (dict "context" $ "subchart" "postgresql" "key" "password" "envName" "DB_PASSWORD") | nindent 12 }} + {{- else if or .Values.tracer.useExistingSecret .Values.tracer.secrets.DB_PASSWORD }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: DB_PASSWORD + {{- end }} + resources: + {{- toYaml $migrations.resources | nindent 12 }} + {{- with .Values.tracer.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tracer.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tracer.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/midaz/templates/tracer/pdb.yaml b/charts/midaz/templates/tracer/pdb.yaml new file mode 100644 index 000000000..7034db771 --- /dev/null +++ b/charts/midaz/templates/tracer/pdb.yaml @@ -0,0 +1,25 @@ +{{- if and .Values.tracer.enabled .Values.tracer.pdb.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "midaz-tracer.fullname" . }} + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 4 }} + {{- with .Values.tracer.pdb.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} +spec: + {{- if hasKey .Values.tracer.pdb "maxUnavailable" }} + maxUnavailable: {{ .Values.tracer.pdb.maxUnavailable }} + {{- else if hasKey .Values.tracer.pdb "minAvailable" }} + minAvailable: {{ .Values.tracer.pdb.minAvailable }} + {{- else }} + minAvailable: 1 + {{- end }} + selector: + matchLabels: + {{- include "midaz-tracer.selectorLabels" (dict "context" . "name" .Values.tracer.name) | nindent 6 }} +{{- end }} diff --git a/charts/midaz/templates/tracer/secrets.yaml b/charts/midaz/templates/tracer/secrets.yaml new file mode 100644 index 000000000..1dd0eaeba --- /dev/null +++ b/charts/midaz/templates/tracer/secrets.yaml @@ -0,0 +1,42 @@ +{{- if and .Values.tracer.enabled (not .Values.tracer.useExistingSecret) }} +{{- /* +Boot-contract validation (API key, CORS, multi-tenant, existing secret) lives in +the midaz-tracer.validate helper, included from the ConfigMap so it also runs on +the useExistingSecret path this template skips. +*/ -}} +{{- $pg := .Values.postgresql | default dict }} +{{- $pgAuth := $pg.auth | default dict }} +{{- $pgInternal := or (and (ne (toString $pg.enabled) "false") (not $pg.external)) $pgAuth.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "midaz-tracer.fullname" . }} + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 4 }} +type: Opaque +data: + # -- Default sensitive variables for Tracer + + # PostgreSQL Secrets + # DB_PASSWORD is single-sourced from the Bitnami postgresql subchart Secret + # (-postgresql, key `password`) and injected via secretKeyRef on the + # Tracer Deployment. It is only emitted here when PostgreSQL is EXTERNAL + # (postgresql disabled or .external=true) without an existingSecret override, + # and the operator supplied tracer.secrets.DB_PASSWORD. + {{- if and (not $pgInternal) .Values.tracer.secrets.DB_PASSWORD }} + DB_PASSWORD: {{ .Values.tracer.secrets.DB_PASSWORD | b64enc | quote }} + {{- end }} + + # API KEY (read by the app as $API_KEY when API_KEY_ENABLED=true) + {{- if .Values.tracer.secrets.API_KEY }} + API_KEY: {{ .Values.tracer.secrets.API_KEY | b64enc | quote }} + {{- end }} + + # MULTI-TENANT SECRETS (operator-provided when MULTI_TENANT_ENABLED=true) + {{- if .Values.tracer.secrets.MULTI_TENANT_SERVICE_API_KEY }} + MULTI_TENANT_SERVICE_API_KEY: {{ .Values.tracer.secrets.MULTI_TENANT_SERVICE_API_KEY | b64enc | quote }} + {{- end }} + {{- if .Values.tracer.secrets.MULTI_TENANT_REDIS_PASSWORD }} + MULTI_TENANT_REDIS_PASSWORD: {{ .Values.tracer.secrets.MULTI_TENANT_REDIS_PASSWORD | b64enc | quote }} + {{- end }} +{{- end }} diff --git a/charts/midaz/templates/tracer/service.yaml b/charts/midaz/templates/tracer/service.yaml new file mode 100644 index 000000000..6cb819487 --- /dev/null +++ b/charts/midaz/templates/tracer/service.yaml @@ -0,0 +1,27 @@ +{{- if .Values.tracer.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "midaz-tracer.fullname" . }} + labels: + {{- include "midaz-tracer.labels" (dict "context" . "name" .Values.tracer.name ) | nindent 4 }} + {{- with .Values.tracer.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.tracer.service.type }} + ports: + - port: {{ .Values.tracer.service.port }} + targetPort: http + protocol: TCP + name: http + {{- if .Values.tracer.configmap.TRACER_GRPC_PORT }} + - port: {{ .Values.tracer.service.grpcPort | default 4021 }} + targetPort: grpc + protocol: TCP + name: grpc + {{- end }} + selector: + {{- include "midaz-tracer.selectorLabels" (dict "context" . "name" .Values.tracer.name) | nindent 4 }} +{{- end }} diff --git a/charts/midaz/values.yaml b/charts/midaz/values.yaml index 2120d15c0..1e2d00269 100644 --- a/charts/midaz/values.yaml +++ b/charts/midaz/values.yaml @@ -100,6 +100,8 @@ global: db: "transaction" - role: "readWrite" db: "crm" + - role: "readWrite" + db: "fees" ledger: # -- Readiness probe configuration. All fields override chart defaults. readinessProbe: {} @@ -122,6 +124,56 @@ ledger: pullPolicy: IfNotPresent # -- Image tag used for deployment tag: "3.8.0" + # -- Dedicated migration runner (`midaz-ledger-migrations`, built FROM + # migrate/migrate) applying the onboarding and transaction migration sets. + migrations: + # -- Run the migration Job. Unset means auto: on for 4.x ledger tags, which + # removed in-process migration, off for 3.x, which still migrates at + # startup. Set false to keep it off when the schema is applied out-of-band. + enabled: + image: + # -- Repository for the ledger migration-runner image + repository: lerianstudio/midaz-ledger-migrations + # -- Image pull policy + pullPolicy: IfNotPresent + # -- Migration image tag. Empty tracks `ledger.image.tag` so the schema and + # the binary reading it cannot drift. + tag: "" + # -- Extra Job annotations. The Job is a plain Sync-phase resource by + # default because the bundled PostgreSQL Secret it reads is only created + # during Sync; against a pre-provisioned database, set + # `argocd.argoproj.io/hook: PreSync` here to order it ahead of the Deployment. + annotations: {} + # -- Argo CD sync wave stamped on the ledger Deployment while this Job + # renders, so the Deployment is applied only once the Job reports Complete + # (the Job itself stays in the default wave, with the bundled PostgreSQL + # Secret it reads). Set to "" to drop the annotation and let both apply + # together. Ignored by plain `helm install`. + deploymentSyncWave: "1" + # -- Retries before the Job is marked failed + backoffLimit: 6 + # -- Hard timeout for the whole Job + activeDeadlineSeconds: 600 + # -- How long the finished Job is kept for inspection + ttlSecondsAfterFinished: 259200 + # -- Wait for the PostgreSQL endpoint before running migrate + waitForPostgres: + # -- Enable or disable the wait-for-postgres init container + enabled: true + image: + # -- Repository for the wait-for-postgres init container image + repository: busybox + # -- Image tag for the wait-for-postgres init container image + tag: "1.37" + resources: + # -- CPU and memory limits for the migration Job + limits: + cpu: 500m + memory: 256Mi + # -- Minimum CPU and memory requests for the migration Job + requests: + cpu: 50m + memory: 64Mi # -- Secrets for pulling images from a private registry imagePullSecrets: [] # -- Overrides the default generated name by Helm @@ -266,6 +318,37 @@ ledger: MONGO_TRANSACTION_NAME: "transaction" MONGO_TRANSACTION_USER: "midaz" MONGO_TRANSACTION_PORT: "27017" + # -- MongoDB CRM module. The midaz v4 ledger binary serves CRM in-process, + # so it opens this database itself. Passwords live in ledger.secrets. + MONGO_CRM_URI: "mongodb" + MONGO_CRM_HOST: "midaz-mongodb" + MONGO_CRM_NAME: "crm" + MONGO_CRM_USER: "midaz" + MONGO_CRM_PORT: "27017" + # -- MongoDB Fees module (also in-process in the v4 ledger binary) + MONGO_FEES_URI: "mongodb" + MONGO_FEES_HOST: "midaz-mongodb" + MONGO_FEES_NAME: "fees" + MONGO_FEES_USER: "midaz" + MONGO_FEES_PORT: "27017" + # -- KMS vendor selector for CRM field encryption (midaz pkg/crypto). + # "none" keeps legacy mode: local symmetric keys from LCRYPTO_* in the + # ledger Secret. "hashicorp-vault" switches to envelope encryption and + # then requires KMS_VAULT_ADDR, KMS_VAULT_ROLE_ID, KMS_VAULT_AUTH_METHOD + # here plus KMS_VAULT_SECRET_ID in ledger.secrets. + KMS_VENDOR: "none" + # -- Tracer reservation seam (ledger acting as tracer client). Empty + # TRACER_BASE_URL disables the integration; set it to the tracer Service + # when tracer.enabled=true. + TRACER_BASE_URL: "" + # -- Reservation transport: "rest" or "grpc". The bundled tracer exposes + # only its HTTP API by default (the gRPC seam stays off until + # tracer.configmap.TRACER_GRPC_PORT is set), so the in-chart path defaults + # to REST. Switch both together to use gRPC, which is the seam's + # production transport. + TRACER_TRANSPORT: "rest" + # -- Fee engine default currency + DEFAULT_CURRENCY: "USD" # -- Redis (shared) REDIS_HOST: "midaz-valkey-primary:6379" # -- RabbitMQ configuration @@ -317,6 +400,21 @@ ledger: DB_TRANSACTION_PASSWORD: "" DB_TRANSACTION_REPLICA_PASSWORD: "" MONGO_TRANSACTION_PASSWORD: "" + # -- CRM and Fees module passwords. The unified ledger binary (midaz v4) + # opens these Mongo databases in-process; same single-source rule as the + # modules above. Rendered into the ledger Secret, never the ConfigMap. + MONGO_CRM_PASSWORD: "" + MONGO_FEES_PASSWORD: "" + # -- CRM holder-field crypto (lib-crypto). Operator-provided key material + # protecting PII at rest. Required when the ledger serves CRM and + # KMS_VENDOR is unset or "none" (legacy mode, local symmetric keys). + LCRYPTO_HASH_SECRET_KEY: "" + LCRYPTO_ENCRYPT_SECRET_KEY: "" + # -- Vault AppRole SecretID, the credential half of the KMS auth pair. + # Only set when ledger.configmap.KMS_VENDOR="hashicorp-vault" (envelope + # encryption). The RoleID counterpart is not secret and stays in the + # ConfigMap. + KMS_VAULT_SECRET_ID: "" # -- Shared passwords. # REDIS_PASSWORD is single-sourced from the Bitnami valkey subchart Secret # (key `valkey-password`); only set it for an EXTERNAL Valkey/Redis without an @@ -512,6 +610,264 @@ crm: # -- Existing secrets name useExistingSecret: false existingSecretName: "" +tracer: + # -- Readiness probe configuration. All fields override chart defaults. + readinessProbe: {} + # -- Liveness probe configuration. All fields override chart defaults. + livenessProbe: {} + # -- Service name + name: tracer + # -- Enable or disable the Tracer service (disabled by default; opt-in per env) + enabled: false + # -- Number of replicas for the Tracer service + replicaCount: 1 + # -- Number of old ReplicaSets to retain for deployment rollback + revisionHistoryLimit: 10 + image: + # -- Repository for the Tracer service container image. In midaz v4 tracer + # ships from the monorepo (`components/tracer`) and the release pipeline + # publishes it as `midaz-tracer`; the standalone `lerianstudio/tracer` 1.x + # image predates the configuration contract this chart renders. + repository: lerianstudio/midaz-tracer + # -- Image pull policy + pullPolicy: Always + # -- Image tag used for deployment + tag: "4.0.0-beta.24" + # -- Dedicated migration runner (`midaz-tracer-migrations`, built FROM + # migrate/migrate). The v4 tracer no longer migrates at startup: it boots + # against an already-migrated schema, so this Job has to apply it. + migrations: + # -- Enable or disable the tracer migration Job + enabled: true + image: + # -- Repository for the tracer migration-runner image + repository: lerianstudio/midaz-tracer-migrations + # -- Image pull policy + pullPolicy: IfNotPresent + # -- Migration image tag. Empty tracks `tracer.image.tag` so the schema and + # the binary reading it cannot drift. + tag: "" + # -- Extra Job annotations. The Job is a plain Sync-phase resource by + # default because the bundled PostgreSQL Secret it reads is only created + # during Sync; against a pre-provisioned database, set + # `argocd.argoproj.io/hook: PreSync` here to order it ahead of the Deployment. + annotations: {} + # -- Argo CD sync wave stamped on the tracer Deployment while this Job + # renders, so the Deployment is applied only once the Job reports Complete + # (the Job itself stays in the default wave, with the bundled PostgreSQL + # Secret it reads). Set to "" to drop the annotation and let both apply + # together. Ignored by plain `helm install`. + deploymentSyncWave: "1" + # -- Retries before the Job is marked failed (also absorbs a database that + # is reachable but not yet provisioned by the bootstrap Job) + backoffLimit: 6 + # -- Hard timeout for the whole Job + activeDeadlineSeconds: 600 + # -- How long the finished Job is kept for inspection + ttlSecondsAfterFinished: 259200 + # -- Wait for the PostgreSQL endpoint before running migrate + waitForPostgres: + # -- Enable or disable the wait-for-postgres init container + enabled: true + image: + # -- Repository for the wait-for-postgres init container image + repository: busybox + # -- Image tag for the wait-for-postgres init container image + tag: "1.37" + resources: + # -- CPU and memory limits for the migration Job + limits: + cpu: 500m + memory: 256Mi + # -- Minimum CPU and memory requests for the migration Job + requests: + cpu: 50m + memory: 64Mi + # -- Secrets for pulling images from a private registry + imagePullSecrets: [] + # -- Overrides the default generated name by Helm + nameOverride: "" + # -- Overrides the full name generated by Helm + fullnameOverride: "" + # -- Pod annotations for additional metadata + podAnnotations: {} + podSecurityContext: {} + # fsGroup: 2000 + + securityContext: + # -- Defines the group ID for the user running the process inside the container + runAsGroup: 1000 + # -- Defines the user ID for the process running inside the container + runAsUser: 1000 + # -- Ensures the process does not run as root + runAsNonRoot: true + capabilities: + drop: + - ALL + # -- Defines the root filesystem as read-only + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + # -- PodDisruptionBudget configuration + pdb: + # -- Enable or disable PodDisruptionBudget + enabled: true + # -- Minimum number of available pods. Setting `maxUnavailable` in an + # override takes precedence over this value. + minAvailable: 1 + # -- Annotations for the PodDisruptionBudget + annotations: {} + # -- Deployment update strategy + deploymentUpdate: + # -- Type of deployment strategy + type: RollingUpdate + # -- Maximum number of pods that can be created over the desired number of pods + maxSurge: 1 + # -- Maximum number of pods that can be unavailable during the update + maxUnavailable: 1 + service: + # -- Kubernetes service type + type: ClusterIP + # -- Service port (HTTP API) + port: 4020 + # -- gRPC reservation seam port. Only exposed when + # `tracer.configmap.TRACER_GRPC_PORT` is set; the seam is off in the app + # until then, so the port would otherwise route to a closed socket. + grpcPort: 4021 + ingress: + # -- Enable or disable ingress + enabled: false + # -- Ingress class name + className: "" + # -- Additional ingress annotations + annotations: {} + hosts: + - host: "" + paths: + - path: / + pathType: Prefix + # -- TLS configuration for ingress + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + resources: + # -- CPU and memory limits for pods + limits: + cpu: 500m + memory: 512Mi + # -- Minimum CPU and memory requests + requests: + cpu: 100m + memory: 128Mi + autoscaling: + # -- Enable or disable horizontal pod autoscaling + enabled: true + # -- Minimum number of replicas + minReplicas: 1 + # -- Maximum number of replicas + maxReplicas: 5 + # -- Target CPU utilization percentage for autoscaling + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + # -- Node selector for scheduling pods on specific nodes + nodeSelector: {} + # -- Tolerations for scheduling on tainted nodes + tolerations: {} + # -- Affinity rules for pod scheduling + affinity: {} + # -- ConfigMap for environment variables and configurations + # @default -- templates/tracer/configmap.yaml + configmap: + ALLOW_INSECURE_TLS: "true" + # -- Default Environment + ENV_NAME: "development" + # -- Application server + SERVER_PORT: "4020" + SERVER_ADDRESS: ":4020" + LOG_LEVEL: "info" + # -- Authentication + API_KEY_ENABLED: "false" + # -- CORS configuration + CORS_ALLOWED_ORIGINS: "*" + # -- Auth Plugin (Access Manager) + PLUGIN_AUTH_ENABLED: "false" + PLUGIN_AUTH_ADDRESS: "http://plugin-access-manager-auth:4000" + # -- Listen address for the reservation gRPC seam the ledger can call + # (e.g. ":4021"). Empty keeps the seam off, which is why the ledger side + # (`ledger.configmap.TRACER_TRANSPORT`) defaults to the REST transport. + # Setting it here also publishes `service.grpcPort` on the tracer Service. + TRACER_GRPC_PORT: "" + # -- PostgreSQL configuration. Shares the bundled midaz PostgreSQL: the + # `tracer` database is created by files/midaz/init.sql (internal) or the + # external bootstrap Job, and is owned by the same `midaz` role whose + # password the subchart Secret holds. A dedicated `tracer` role would need + # its own Secret, which nothing in this chart provisions. + # Empty resolves to the bundled PostgreSQL primary Service of THIS release + # (e.g. `review-postgresql-primary`), so a non-`midaz` release name still + # connects. Set it explicitly for an external PostgreSQL. + DB_HOST: "" + DB_PORT: "5432" + DB_NAME: "tracer" + DB_USER: "midaz" + DB_SSL_MODE: "disable" + # -- Soft heap limit for the Go runtime, ~90% of resources.limits.memory. + # The image ships GOMEMLIMIT=1800MiB (a 2Gi container); without this + # override the GC lets the heap grow past the 512Mi cgroup limit and the + # kernel OOM-kills the pod. Keep the two in sync when changing limits. + GOMEMLIMIT: "460MiB" + # -- Swagger documentation + SWAGGER_TITLE: "Tracer API" + SWAGGER_DESCRIPTION: "Real-time transaction validation and fraud prevention API" + SWAGGER_HOST: ":4020" + SWAGGER_BASE_PATH: "/" + SWAGGER_SCHEMES: "http" + # -- CEL expression engine cost limit + CEL_COST_LIMIT: "10000" + # -- OpenTelemetry + ENABLE_TELEMETRY: "false" + OTEL_LIBRARY_NAME: "github.com/LerianStudio/tracer" + OTEL_RESOURCE_SERVICE_NAME: "midaz-tracer" + OTEL_EXPORTER_OTLP_ENDPOINT: "" + # -- Cleanup worker + CLEANUP_WORKER_ENABLED: "false" + CLEANUP_INTERVAL_HOURS: "24" + # -- Multi-tenant (when true, MULTI_TENANT_URL, MULTI_TENANT_REDIS_HOST and + # secrets.MULTI_TENANT_SERVICE_API_KEY become required) + MULTI_TENANT_ENABLED: "false" + MULTI_TENANT_URL: "" + MULTI_TENANT_ALLOW_INSECURE_HTTP: "false" + MULTI_TENANT_REDIS_HOST: "" + MULTI_TENANT_REDIS_PORT: "6379" + MULTI_TENANT_REDIS_TLS: "true" + MULTI_TENANT_MAX_TENANT_POOLS: "100" + MULTI_TENANT_IDLE_TIMEOUT_SEC: "300" + MULTI_TENANT_TIMEOUT: "30" + MULTI_TENANT_CIRCUIT_BREAKER_THRESHOLD: "5" + MULTI_TENANT_CIRCUIT_BREAKER_TIMEOUT_SEC: "30" + MULTI_TENANT_CACHE_TTL_SEC: "120" + MULTI_TENANT_CONNECTIONS_CHECK_INTERVAL_SEC: "30" + # MULTI_TENANT_MAX_OPEN_CONNS_PER_TENANT: "" + # MULTI_TENANT_MAX_IDLE_CONNS_PER_TENANT: "" + # -- Extra environment variables + extraEnvVars: {} + # -- Secrets for storing sensitive data + # @default -- templates/tracer/secrets.yaml + secrets: + # -- PostgreSQL password. Single-sourced from the Bitnami postgresql subchart + # Secret (key `password`) when internal; only set for an EXTERNAL PostgreSQL + # (subchart disabled / .external=true) without an existingSecret override. + DB_PASSWORD: "" + # -- API Key value (read by the app as $API_KEY when API_KEY_ENABLED=true). + API_KEY: "" + # -- Tenant Manager service API key (required when MULTI_TENANT_ENABLED=true). + # MULTI_TENANT_SERVICE_API_KEY: "" + # -- Optional Redis password for the tenant connection-pool registry. + # MULTI_TENANT_REDIS_PASSWORD: "" + # -- Existing secrets name + useExistingSecret: false + existingSecretName: "" grafana: # OpenTelemetry (OTel) dependency for observability. # This component is responsible for collecting and exporting telemetry data