diff --git a/.github/configs/helm-render-values/br-ccs.yaml b/.github/configs/helm-render-values/br-ccs.yaml new file mode 100644 index 000000000..29b88f43b --- /dev/null +++ b/.github/configs/helm-render-values/br-ccs.yaml @@ -0,0 +1,39 @@ +# Render-gate fixture for br-ccs — exercises the productized (lerian-common) +# paths in their ENABLED state so the render gate catches regressions there: +# - multiTenant.env + fail-fast multiTenant.secret (MT on, API key supplied) +# - otel.env driven by global.observability +# - datastore.value broker mask + RABBITMQ_DEFAULT_PASS fail-fast (rmq on) +# - serviceDiscovery.env (SD on) and streaming.env + streaming.secret (streaming on, SASL) +# Values are placeholders; the gate only checks that the chart renders. +brCcs: + multiTenant: + enabled: true + serviceDiscovery: + enabled: true + streaming: + enabled: true + configmap: + RABBITMQ_ENABLED: "true" + STREAMING_BROKERS: "kafka:9092" + STREAMING_SASL_MECHANISM: "PLAIN" + STREAMING_SASL_USERNAME: "render-fixture-user" + STREAMING_TLS_ENABLED: "true" + secrets: + CCS_CRYPTO_MASTER_KEY: "render-fixture-crypto-key" + MULTI_TENANT_SERVICE_API_KEY: "render-fixture-mt-api-key" + RABBITMQ_DEFAULT_PASS: "render-fixture-rmq-pass" + STREAMING_SASL_PASSWORD: "render-fixture-stream-pass" + SD_TOKEN: "render-fixture-sd-token" +global: + observability: + enabled: true + otlpEndpoint: "otel-collector:4317" + deploymentEnvironment: "staging" + multiTenant: + url: "http://tenant-manager:8080" + redisHost: "tenant-manager-redis" + serviceDiscovery: + address: "consul.prod:8500" + tls: "true" + streaming: + brokers: "kafka:9092" diff --git a/charts/br-ccs/CHANGELOG.md b/charts/br-ccs/CHANGELOG.md new file mode 100644 index 000000000..fe681bb62 --- /dev/null +++ b/charts/br-ccs/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to the br-ccs Helm chart are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2026-07-07 + +### Added + +- Initial Helm chart for the br-ccs service (BACEN CCS regulatory integration). +- Single Deployment running `CCS_RUN_MODE=all` (HTTP API + background workers in one process). +- ClusterIP Service on port 4030 (`SERVER_ADDRESS=:4030`). +- Health probes wired to the verified application endpoints: liveness `GET /health`, + readiness `GET /readyz`. +- Full env-var coverage from `config/.env.example` split across ConfigMap (non-secret) + and Secret (credentials, crypto keys, object-storage keys, M2M API keys). +- PostgreSQL migrations Job using the dedicated `br-ccs-migrations` image + (golang-migrate runner; PreSync for external Postgres, PostSync for the bundled + subchart), plus a migration-only Secret hook for the external chart-managed path. +- Optional subchart dependencies: `postgresql`, `valkey`, `rabbitmq` (all `.enabled`-gated). +- External-infra bootstrap Jobs for PostgreSQL and RabbitMQ (opt-in via `global.*Definitions.enabled`). +- HPA, PodDisruptionBudget, Ingress, ServiceAccount templates. +- Non-root, read-only-root-filesystem, drop-ALL security contexts on all containers. diff --git a/charts/br-ccs/Chart.lock b/charts/br-ccs/Chart.lock new file mode 100644 index 000000000..1e135303b --- /dev/null +++ b/charts/br-ccs/Chart.lock @@ -0,0 +1,15 @@ +dependencies: +- name: lerian-common-helm + repository: file://../lerian-common + version: 1.4.0 +- name: valkey + repository: oci://registry-1.docker.io/bitnamicharts + version: 2.4.7 +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 16.3.5 +- name: rabbitmq + repository: https://groundhog2k.github.io/helm-charts + version: 2.1.11 +digest: sha256:59a194a9612d9b864d3b2a3cdc79e9112d3f7f618e1e66127b52bc6f73e1e945 +generated: "2026-08-04T17:37:06.756256-03:00" diff --git a/charts/br-ccs/Chart.yaml b/charts/br-ccs/Chart.yaml new file mode 100644 index 000000000..4810239bc --- /dev/null +++ b/charts/br-ccs/Chart.yaml @@ -0,0 +1,43 @@ +apiVersion: v2 +name: br-ccs-helm +description: A Helm chart for br-ccs — Lerian's BACEN CCS (Cadastro de Clientes + do Sistema Financeiro Nacional) regulatory integration service +type: application +annotations: + lerian.studio/chart-type: single-service +home: https://github.com/LerianStudio/helm +sources: + - https://github.com/LerianStudio/helm/tree/main/charts/br-ccs + - https://github.com/LerianStudio/br-ccs +maintainers: + - name: "Lerian Studio" + email: "support@lerian.studio" + +version: 1.0.0-beta.4 + +appVersion: "1.0.0" + +keywords: + - ccs + - bacen + - regulatory + - lerian + - ledger + - brazil +icon: https://avatars.githubusercontent.com/u/148895005?s=200&v=4 +dependencies: + - name: lerian-common-helm + version: "1.4.0" + repository: "file://../lerian-common" + - name: valkey + version: "2.4.7" + repository: "oci://registry-1.docker.io/bitnamicharts" + condition: valkey.enabled + - name: postgresql + version: "16.3.5" + repository: "https://charts.bitnami.com/bitnami" + condition: postgresql.enabled + - name: rabbitmq + version: "2.1.11" + repository: "https://groundhog2k.github.io/helm-charts" + condition: rabbitmq.enabled diff --git a/charts/br-ccs/README.md b/charts/br-ccs/README.md new file mode 100644 index 000000000..7422cd71e --- /dev/null +++ b/charts/br-ccs/README.md @@ -0,0 +1,93 @@ +# br-ccs Helm Chart + +Helm chart for **br-ccs** — Lerian's Go service for the BACEN **CCS (Cadastro de +Clientes do Sistema Financeiro Nacional)** regulatory integration. It ingests +customer-relationship data (from Fetcher), computes the daily delta, renders the +BACEN XML layouts, transmits via **STA**, and reconciles the responses, honoring +judicial secrecy (liminares / LC 105) and LGPD. + +- Chart name: `br-ccs-helm` +- Chart type: `single-service` +- Source: https://github.com/LerianStudio/br-ccs + +## Chart Contract + +- Chart type: `single-service` +- Required secrets: `brCcs.secrets.CCS_CRYPTO_MASTER_KEY` (AES-256-GCM, 64 hex chars — empty fails fast at boot). `POSTGRES_PASSWORD` and `REDIS_PASSWORD` are single-sourced from the bundled `-{postgresql,valkey}` Secrets via `secretKeyRef` when those subcharts are enabled; supply them (or `.auth.existingSecret`) for external infra. `FETCHER_CRYPTO_KEY` is optional (empty = passthrough). See `docs/helm-chart-standard.md`. +- Dependency notes: Bundled PostgreSQL and Valkey subcharts are used unless external services are configured; RabbitMQ is optional and disabled by default. No MongoDB. +- Production overrides: Disable the bundled subcharts (`.enabled=false`, `.external=true`), set `POSTGRES_HOST`/`REDIS_HOST`, and supply secrets via a secrets manager or `brCcs.useExistingSecret`; override image tags, ingress, resources, and integration endpoints (Fetcher/STA/Reporter). +- Source/license: Source is in `github.com/LerianStudio/helm` and `github.com/LerianStudio/br-ccs`; license is Apache-2.0. + +## TL;DR + +```bash +helm dependency build charts/br-ccs +helm install br-ccs charts/br-ccs \ + --set brCcs.configmap.ENV_NAME=production \ + --set brCcs.secrets.POSTGRES_PASSWORD=... \ + --set brCcs.secrets.REDIS_PASSWORD=... \ + --set brCcs.secrets.CCS_CRYPTO_MASTER_KEY=$(openssl rand -hex 32) +``` + +## Service topology + +| Aspect | Value | +|--------|-------| +| Container port / Service port | `4030` (`SERVER_ADDRESS=:4030`) | +| Service type | `ClusterIP` | +| Run mode | Single Deployment, `CCS_RUN_MODE=all` (API + workers) | +| Liveness probe | `GET /health` | +| Readiness probe | `GET /readyz` (PROJECT_RULES §13.4) | +| Persistence | PostgreSQL (no MongoDB) | +| Messaging | RabbitMQ (optional, disabled by default) | +| Cache / idempotency / rate-limit | Redis / Valkey | + +## Dependencies + +All bundled subcharts are `.enabled`-gated. For BYOC / production, disable them +and point the app at external infrastructure (see `values-template.yaml`). + +| Subchart | Version | Repository | +|----------|---------|------------| +| postgresql | 16.3.5 | https://charts.bitnami.com/bitnami | +| valkey | 2.4.7 | oci://registry-1.docker.io/bitnamicharts | +| rabbitmq | 2.1.11 | https://groundhog2k.github.io/helm-charts | + +## Configuration + +Non-secret configuration lives under `brCcs.configmap`; credentials and keys +under `brCcs.secrets`. Every variable from the application's +`config/.env.example` is mapped. Optional knobs not modeled explicitly can be +set via `brCcs.extraEnvVars`. + +### Required secrets + +| Key | Required | Notes | +|-----|----------|-------| +| `POSTGRES_PASSWORD` | Yes | Single-sourced from the bundled subchart Secret when `postgresql.enabled=true`; supply here for external Postgres. | +| `REDIS_PASSWORD` | Yes | Single-sourced from the bundled valkey Secret when `valkey.enabled=true`; supply here for external Redis. | +| `CCS_CRYPTO_MASTER_KEY` | Yes | AES-256-GCM master key, 64 hex chars (`openssl rand -hex 32`). Empty fails fast at boot. | +| `FETCHER_CRYPTO_KEY` | No | Optional. Empty = passthrough / plaintext (dev/staging); set to the Fetcher `APP_ENC_KEY` to decrypt snapshots. | + +### Migrations + +`brCcs.migrations.enabled=true` (default) runs the dedicated `br-ccs-migrations` +image as a Helm/ArgoCD hook: + +- **External Postgres** → PreSync (`pre-install,pre-upgrade`), backed by a + migration-only Secret carrying just `POSTGRES_PASSWORD`. +- **Bundled Postgres subchart** → PostSync (`post-install,post-upgrade`), reading + the app Secret after the database is provisioned. + +## Security + +All containers run non-root (`runAsUser: 1000`, `runAsNonRoot: true`), with +`readOnlyRootFilesystem: true`, `allowPrivilegeEscalation: false`, +`capabilities.drop: [ALL]`, and `seccompProfile: RuntimeDefault`. + +## Validation + +```bash +helm lint charts/br-ccs +helm template charts/br-ccs +``` diff --git a/charts/br-ccs/README.params.md b/charts/br-ccs/README.params.md new file mode 100644 index 000000000..683396332 --- /dev/null +++ b/charts/br-ccs/README.params.md @@ -0,0 +1,125 @@ +# Parameters + +## Parameters + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `global.externalPostgresDefinitions` | string | `{}` | Bootstrap job for external PostgreSQL: creates databases, roles, and grants privileges | +| `global.externalPostgresDefinitions.enabled` | bool | `false` | Enable or disable the PostgreSQL bootstrap job | +| `global.externalPostgresDefinitions.connection` | string | `{}` | PostgreSQL connection settings | +| `global.externalPostgresDefinitions.connection.host` | string | `"br-ccs-postgresql-primary"` | PostgreSQL host | +| `global.externalPostgresDefinitions.connection.port` | string | `"5432"` | PostgreSQL port | +| `global.externalPostgresDefinitions.postgresAdminLogin` | string | `{}` | Admin credentials for PostgreSQL | +| `global.externalPostgresDefinitions.postgresAdminLogin.useExistingSecret.name` | string | `""` | Name of existing secret containing DB_USER_ADMIN and DB_ADMIN_PASSWORD keys | +| `global.externalPostgresDefinitions.postgresAdminLogin.username` | string | `"postgres"` | Admin username (ignored if useExistingSecret.name is set) | +| `global.externalPostgresDefinitions.postgresAdminLogin.password` | string | `""` | Admin password (ignored if useExistingSecret.name is set) | +| `global.externalPostgresDefinitions.brCcsCredentials` | string | `{}` | Credentials for the br-ccs role created by the job | +| `global.externalPostgresDefinitions.brCcsCredentials.useExistingSecret.name` | string | `""` | Name of existing secret containing DB_PASSWORD_BR_CCS key | +| `global.externalPostgresDefinitions.brCcsCredentials.password` | string | `""` | Password for the br-ccs role (ignored if useExistingSecret.name is set) | +| `global.externalRabbitmqDefinitions` | string | `{}` | Bootstrap job for external RabbitMQ: creates users, vhosts, and permissions | +| `global.externalRabbitmqDefinitions.enabled` | bool | `false` | Enable or disable the RabbitMQ bootstrap job | +| `global.externalRabbitmqDefinitions.connection` | string | `{}` | RabbitMQ connection settings | +| `global.externalRabbitmqDefinitions.connection.protocol` | string | `"http"` | RabbitMQ protocol (http or https) | +| `global.externalRabbitmqDefinitions.connection.host` | string | `"br-ccs-rabbitmq"` | RabbitMQ host (management API endpoint) | +| `global.externalRabbitmqDefinitions.connection.port` | string | `"15672"` | RabbitMQ HTTP management port | +| `global.externalRabbitmqDefinitions.connection.portAmqp` | string | `"5672"` | RabbitMQ AMQP port (for connectivity check) | +| `global.externalRabbitmqDefinitions.connection.skipTlsVerify` | bool | `false` | Skip TLS verification for self-signed certificates (not recommended for production) | +| `global.externalRabbitmqDefinitions.rabbitmqAdminLogin` | string | `{}` | Admin credentials for RabbitMQ management API | +| `global.externalRabbitmqDefinitions.rabbitmqAdminLogin.useExistingSecret.name` | string | `""` | Name of existing secret containing RABBITMQ_ADMIN_USER and RABBITMQ_ADMIN_PASS keys | +| `global.externalRabbitmqDefinitions.rabbitmqAdminLogin.username` | string | `"admin"` | Admin username (ignored if useExistingSecret.name is set) | +| `global.externalRabbitmqDefinitions.rabbitmqAdminLogin.password` | string | `""` | Admin password (ignored if useExistingSecret.name is set) | +| `global.externalRabbitmqDefinitions.brCcsCredentials` | string | `{}` | Credentials for the br-ccs user created by the job | +| `global.externalRabbitmqDefinitions.brCcsCredentials.useExistingSecret.name` | string | `""` | Name of existing secret containing RABBITMQ_BR_CCS_PASS key | +| `global.externalRabbitmqDefinitions.brCcsCredentials.password` | string | `""` | Password for the br-ccs user (ignored if useExistingSecret.name is set) | +| `global.observability` | string | `{}` | Env-wide observability, consumed by lerian-common.otel.env. Declare once at the umbrella level; a component brCcs.configmap. still overrides per-service. Precedence: brCcs.configmap. > global.observability. > chart default. | +| `global.observability.enabled` | bool | `false` | Enable telemetry export (ENABLE_TELEMETRY). Unset → chart default "false". | +| `global.observability.otlpEndpoint` | string | `""` | OTLP collector endpoint (OTEL_EXPORTER_OTLP_ENDPOINT). Unset → chart default "". | +| `global.observability.deploymentEnvironment` | string | `"production"` | Deployment environment tag (OTEL_RESOURCE_DEPLOYMENT_ENVIRONMENT). Unset → "production". | +| `global.datastores` | object | `{}` | Env-wide datastore mask, consumed by lerian-common.datastore.value. Declare a SHARED instance once here; a DEDICATED per-service instance goes under brCcs.datastores; a component brCcs.configmap. still overrides everything. Precedence: brCcs.configmap. > brCcs.datastores.. > global.datastores.. > chart default. Leave empty ({}) to keep the bundled-subchart / native-key defaults. | +| `global.multiTenant` | object | `{}` | Env-wide multi-tenant infra (tenant-manager URL + its Redis), consumed by lerian-common.multiTenant.env. Declare once; a component brCcs.configmap. still overrides. Only consulted when multi-tenancy is enabled (see brCcs.multiTenant.enabled). Leave empty ({}) for the native-key / chart defaults. | +| `global.serviceDiscovery` | object | `{}` | Env-wide service discovery (Consul), consumed by lerian-common.serviceDiscovery.env. Only used when SD is enabled (brCcs.serviceDiscovery.enabled). A component brCcs.configmap.SD_* still overrides. Leave empty ({}) for the chart defaults. | +| `global.streaming` | object | `{}` | Env-wide streaming (lib-streaming → Kafka), consumed by lerian-common.streaming.env. Only used when streaming is enabled (brCcs.streaming.enabled). SASL/broker contract; STREAMING_SASL_PASSWORD is a secret. Leave empty ({}) for the chart defaults. | +| `brCcs.readinessProbe` | object | `{}` | Readiness probe configuration. All fields override chart defaults. | +| `brCcs.livenessProbe` | object | `{}` | Liveness probe configuration. All fields override chart defaults. | +| `brCcs.multiTenant` | string | `{}` | Multi-tenancy toggle (grouped API for MULTI_TENANT_ENABLED). The tenant-manager URL + Redis infra come from global.multiTenant (or brCcs.configmap.MULTI_TENANT_*). Precedence for the toggle: brCcs.configmap.MULTI_TENANT_ENABLED > brCcs.multiTenant.enabled > "false". | +| `brCcs.multiTenant.enabled` | bool | `false` | Enable multi-tenancy (MULTI_TENANT_ENABLED) | +| `brCcs.datastores` | object | `{}` | Dedicated datastore mask for THIS service (see global.datastores for the shared form + precedence). Same fields as global.datastores; wins over global, loses to brCcs.configmap.. Leave empty ({}) to keep the bundled-subchart / native defaults. | +| `brCcs.serviceDiscovery` | string | `{}` | Service discovery toggle (grouped API for SD_ENABLED). Infra comes from global.serviceDiscovery (or brCcs.configmap.SD_*). SD_TOKEN (ACL) is a secret. | +| `brCcs.serviceDiscovery.enabled` | bool | `false` | Enable Consul service discovery (SD_ENABLED) | +| `brCcs.streaming` | string | `{}` | Streaming toggle (grouped API for STREAMING_ENABLED). Broker/SASL infra comes from global.streaming (or brCcs.configmap.STREAMING_*). SASL password is a secret. | +| `brCcs.streaming.enabled` | bool | `false` | Enable lib-streaming (STREAMING_ENABLED) | +| `brCcs.name` | string | `br-ccs` | Service name | +| `brCcs.enabled` | bool | `true` | Enable or disable the br-ccs service | +| `brCcs.replicaCount` | int | `2` | Number of replicas for the br-ccs service | +| `brCcs.revisionHistoryLimit` | int | `10` | Number of old ReplicaSets to retain for deployment rollback | +| `brCcs.image.repository` | string | `ghcr.io/lerianstudio/br-ccs` | Repository for the br-ccs service container image | +| `brCcs.image.pullPolicy` | string | `IfNotPresent` | Image pull policy | +| `brCcs.image.tag` | string | `"1.0.0"` | Image tag used for deployment | +| `brCcs.migrations` | string | `{}` | PostgreSQL migrations job (Helm hook; ArgoCD PreSync for external PG, PostSync for the bundled subchart). Runs the dedicated br-ccs-migrations image (golang-migrate runner; the app image never migrates). The chart-managed Secret is rendered as an earlier hook so it exists before the migration hook — migrations run against the chart-managed Secret with NO pre-existing Secret required. Set useExistingSecret only to read POSTGRES_PASSWORD from an operator-provisioned Secret instead. | +| `brCcs.migrations.enabled` | bool | `true` | Enable or disable the migrations job. | +| `brCcs.migrations.useExistingSecret` | bool | `false` | Optional. When true, migrations read POSTGRES_PASSWORD from a pre-existing Secret (existingSecretName) instead of the chart-managed Secret hook. | +| `brCcs.migrations.existingSecretName` | string | `""` | Name of the pre-existing Secret containing POSTGRES_PASSWORD (only used when useExistingSecret=true). | +| `brCcs.migrations.path` | string | `"/migrations"` | MIGRATIONS_PATH inside the migrations image (embedded at /migrations). | +| `brCcs.migrations.image.repository` | string | `ghcr.io/lerianstudio/br-ccs-migrations` | Repository for the migrations runner image | +| `brCcs.migrations.image.tag` | string | `""` | Tag for the migrations image. Defaults to the app image tag (brCcs.image.tag, or the chart appVersion) when left empty. | +| `brCcs.migrations.image.pullPolicy` | string | `IfNotPresent` | Image pull policy | +| `brCcs.migrations.backoffLimit` | int | `3` | Maximum number of retries before the Job is considered failed | +| `brCcs.imagePullSecrets` | list | `[]` | Secrets for pulling images from a private registry | +| `brCcs.nameOverride` | string | `""` | Overrides the default generated name by Helm | +| `brCcs.fullnameOverride` | string | `""` | Overrides the full name generated by Helm | +| `brCcs.podAnnotations` | object | `{}` | Pod annotations for additional metadata | +| `brCcs.securityContext.runAsGroup` | int | `1000` | Defines the group ID for the user running the process inside the container | +| `brCcs.securityContext.runAsUser` | int | `1000` | Defines the user ID for the process running inside the container | +| `brCcs.securityContext.runAsNonRoot` | bool | `true` | Ensures the process does not run as root | +| `brCcs.securityContext.readOnlyRootFilesystem` | bool | `true` | Defines the root filesystem as read-only | +| `brCcs.pdb` | string | `{}` | PodDisruptionBudget configuration | +| `brCcs.pdb.enabled` | bool | `true` | Enable or disable PodDisruptionBudget | +| `brCcs.pdb.minAvailable` | int | `1` | Minimum number of available pods | +| `brCcs.pdb.maxUnavailable` | int | `1` | Maximum number of unavailable pods | +| `brCcs.pdb.annotations` | object | `{}` | Annotations for the PodDisruptionBudget | +| `brCcs.deploymentUpdate` | string | `{}` | Deployment update strategy | +| `brCcs.deploymentUpdate.type` | string | `RollingUpdate` | Type of deployment strategy | +| `brCcs.deploymentUpdate.maxSurge` | string | `100%` | Maximum number of pods that can be created over the desired number of pods | +| `brCcs.deploymentUpdate.maxUnavailable` | int | `0` | Maximum number of pods that can be unavailable during the update | +| `brCcs.service.type` | string | `ClusterIP` | Kubernetes service type | +| `brCcs.service.port` | int | `4030` | Port for the HTTP API | +| `brCcs.ingress.enabled` | bool | `false` | Enable or disable ingress | +| `brCcs.ingress.className` | string | `""` | Ingress class name | +| `brCcs.ingress.annotations` | object | `{}` | Additional ingress annotations | +| `brCcs.ingress.tls` | list | `[]` | TLS configuration for ingress | +| `brCcs.resources.limits` | string | `{}` | CPU and memory limits for pods | +| `brCcs.resources.requests` | string | `{}` | Minimum CPU and memory requests | +| `brCcs.autoscaling.enabled` | bool | `true` | Enable or disable horizontal pod autoscaling | +| `brCcs.autoscaling.minReplicas` | int | `2` | Minimum number of replicas | +| `brCcs.autoscaling.maxReplicas` | int | `5` | Maximum number of replicas | +| `brCcs.autoscaling.targetCPUUtilizationPercentage` | int | `80` | Target CPU utilization percentage for autoscaling | +| `brCcs.nodeSelector` | object | `{}` | Node selector for scheduling pods on specific nodes | +| `brCcs.tolerations` | list | `[]` | Tolerations for scheduling on tainted nodes | +| `brCcs.affinity` | object | `{}` | Affinity rules for pod scheduling | +| `brCcs.hostAliases` | list | `[]` | Host aliases for custom DNS resolution inside the pod | +| `brCcs.configmap` | object | `templates/configmap.yaml` | Raw ConfigMap env-var escape hatch. The clean, grouped API lives in the brCcs. blocks below (server, cors, postgres, redis, broker, outbox, fetcher, sta, reporter, swagger, rateLimit, pagination, observability, m2m, readiness, shutdown, objectStorage) — each key resolves via cfgValue: brCcs.configmap. (here) > brCcs.. > chart default. Set a raw NATIVE_KEY here only to override a value not exposed as a grouped field, or to pass an optional/opt-in key (e.g. SERVER_TLS_CERT_FILE, RABBITMQ_QUEUE, FETCHER_URL, *_OAUTH2_*, CCS_DETAIL_*). Defaults live in the template, so leaving this {} renders the documented defaults. | +| `brCcs.app` | object | `{}` | Application identity: name / env / version / logLevel / deploymentMode / runMode | +| `brCcs.server` | object | `{}` | HTTP server: address / port / grpcPort / bodyLimitBytes / tlsTerminatedUpstream / trustedProxies | +| `brCcs.cors` | object | `{}` | CORS: allowedOrigins / allowedMethods / allowedHeaders / exposeHeaders / allowCredentials | +| `brCcs.postgres` | object | `{}` | PostgreSQL tuning (host/port/user/ssl come from datastores): name / migrationsPath / systemplaneEnabled / maxOpenConns / maxIdleConns / connMaxLifetimeMins / connMaxIdleTimeMins / connectTimeoutSec / infraConnectTimeoutSec | +| `brCcs.redis` | object | `{}` | Redis tuning (host from datastores): db / protocol / poolSize / minIdleConns / readTimeout / writeTimeout / dialTimeout / poolTimeout / maxRetries / minRetryBackoff / maxRetryBackoff | +| `brCcs.broker` | object | `{}` | RabbitMQ (host/port/user from datastores): enabled / circuitBreakerEnabled / portHost / vhost / exchange / requireHealthAllowedHosts / allowInsecureHealthCheck / allowInsecureTls / publisherConfirmTimeoutMs / publisherRecoveryInitialMs / publisherRecoveryMaxMs / publisherMaxRecoveries / staDlqTtlSeconds | +| `brCcs.outbox` | object | `{}` | Outbox: enabled / tableName / dispatchIntervalSec / batchSize / publishMaxAttempts / publishBackoffMs / retryWindowSec / maxDispatchAttempts / processingTimeoutSec / maxFailedPerBatch / includeTenantMetrics / allowEmptyTenant | +| `brCcs.fetcher` | object | `{}` | Fetcher integration: enabled / httpTimeoutSec / ccsOutboxDispatchIntervalMs / ccsOutboxBatchSize / ccsOutboxMaxAttempts / ccsOutboxDlqRoutingKey | +| `brCcs.sta` | object | `{}` | STA integration: enabled / httpTimeoutSec | +| `brCcs.reporter` | object | `{}` | Reporter integration: enabled / httpTimeoutSec | +| `brCcs.swagger` | object | `{}` | Swagger: enabled / title / version / basePath / leftDelim / rightDelim | +| `brCcs.rateLimit` | object | `{}` | Rate limiting: enabled / max / windowSec / aggressiveMax / aggressiveWindowSec / relaxedMax / relaxedWindowSec / exportMax / exportWindowSec / dispatchMax / dispatchWindowSec / allowFailOpen | +| `brCcs.pagination` | object | `{}` | Pagination: limit / monthDateRange | +| `brCcs.observability` | object | `{}` | Observability: dbMetricsIntervalSec / idempotencyRetryWindowSec | +| `brCcs.m2m` | object | `{}` | M2M (non-secret): credentialCacheTtlSec / awsRegion | +| `brCcs.readiness` | object | `{}` | Readiness / startup probes: probeTimeoutSec / depSlowThresholdMs / startupMaxDurationSec / startupInitialDelayMs / startupMaxDelayMs | +| `brCcs.shutdown` | object | `{}` | Graceful shutdown: drainGracePeriodSec / totalTimeoutSec | +| `brCcs.objectStorage` | string | `{}` | Object storage (non-secret): seaweedfsS3Port / seaweedfsMasterPort / {sta,ccs,fetcher}{Endpoint, Region,Bucket,UsePathStyle,DisableSsl} / outboundRetentionDays / accs009RetentionDays | +| `brCcs.secrets` | string | `templates/secrets.yaml` | Secrets for storing sensitive data. Provide real values via an existing Secret (useExistingSecret) or a secrets manager — NEVER commit real values. | +| `brCcs.useExistingSecret` | bool | `false` | Existing secrets name | +| `brCcs.extraEnvVars` | object | `{}` | Extra environment variables (map of key: value pairs) — escape hatch for optional knobs not modeled above (e.g. RELATIONSHIP_SOURCE_*, CCS_REPORTER_POLL_*). | +| `brCcs.serviceAccount.create` | bool | `true` | Specifies whether a ServiceAccount should be created | +| `brCcs.serviceAccount.annotations` | object | `{}` | Annotations for the ServiceAccount | +| `brCcs.serviceAccount.name` | string | ``br-ccs.fullname`` | Name of the service account | + diff --git a/charts/br-ccs/files/rabbitmq/load_definitions.json b/charts/br-ccs/files/rabbitmq/load_definitions.json new file mode 100644 index 000000000..6ce45fa59 --- /dev/null +++ b/charts/br-ccs/files/rabbitmq/load_definitions.json @@ -0,0 +1,35 @@ +{ + "users": [ + { + "name": "br-ccs", + "tags": "" + } + ], + "vhosts": [ + { + "name": "/" + } + ], + "permissions": [ + { + "user": "br-ccs", + "vhost": "/", + "configure": ".*", + "write": ".*", + "read": ".*" + } + ], + "queues": [], + "exchanges": [ + { + "name": "events", + "vhost": "/", + "type": "topic", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": {} + } + ], + "bindings": [] +} diff --git a/charts/br-ccs/templates/_helpers.tpl b/charts/br-ccs/templates/_helpers.tpl new file mode 100644 index 000000000..73b2c3dbc --- /dev/null +++ b/charts/br-ccs/templates/_helpers.tpl @@ -0,0 +1,153 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "br-ccs.name" -}} +{{- default (default "br-ccs" .Values.nameOverride) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name for br-ccs. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "br-ccs.fullname" -}} +{{- default (include "br-ccs.name" .) .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "br-ccs.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create br-ccs app version +*/}} +{{- define "br-ccs.defaultTag" -}} +{{- default .Chart.AppVersion .Values.brCcs.image.tag }} +{{- end -}} + +{{/* +Return valid br-ccs version label +*/}} +{{- define "br-ccs.versionLabelValue" -}} +{{ regexReplaceAll "[^-A-Za-z0-9_.]" (include "br-ccs.defaultTag" .) "-" | trunc 63 | trimAll "-" | trimAll "_" | trimAll "." | quote }} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "br-ccs.labels" -}} +helm.sh/chart: {{ include "br-ccs.chart" .context }} +{{ include "br-ccs.selectorLabels" (dict "context" .context "component" .component "name" .name) }} +app.kubernetes.io/version: {{ include "br-ccs.versionLabelValue" .context }} +app.kubernetes.io/managed-by: {{ .context.Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "br-ccs.selectorLabels" -}} +app.kubernetes.io/name: {{ include "br-ccs.name" .context }} +app.kubernetes.io/instance: {{ .context.Release.Name }} +{{- if .component }} +app.kubernetes.io/component: {{ .component }} +{{- end }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "br-ccs.serviceAccountName" -}} +{{- if .Values.brCcs.serviceAccount.create }} +{{- default (include "br-ccs.fullname" .) .Values.brCcs.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.brCcs.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Expand the namespace of the release. +Allows overriding it for multi-namespace deployments in combined charts. +*/}} +{{- define "global.namespace" -}} +{{- default .Release.Namespace .Values.namespaceOverride | trunc 63 | trimSuffix "-" -}} +{{- end }} + +{{/* +Enable internal dependencies +These helpers check both .enabled and .external flags +*/}} +{{- define "rabbitmq.enabled" -}} +{{- if and (default true .Values.rabbitmq.enabled) (not .Values.rabbitmq.external) -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{- define "valkey.enabled" -}} +{{- if and (default true .Values.valkey.enabled) (not .Values.valkey.external) -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{- define "postgresql.enabled" -}} +{{- if and (default true .Values.postgresql.enabled) (not .Values.postgresql.external) -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{/* +br-ccs.infraSecretRef — emit a `- name: valueFrom: secretKeyRef: {name,key}` +entry pointing at a Bitnami subchart's generated Secret (or the operator's existingSecret +override). Inputs (dict): context (root .), subchart, key, envName. +*/}} +{{- define "br-ccs.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 }} + +{{/* +br-ccs.migrationPostgresPassword — POSTGRES_PASSWORD for the migration-only Secret. +migration-secret.yaml renders ONLY on the EXTERNAL Postgres path (the bundled subchart path +reads the subchart Secret via secretKeyRef instead), so the operator MUST supply the password. +*/}} +{{- define "br-ccs.migrationPostgresPassword" -}} +{{- $secrets := get (.Values.brCcs | default dict) "secrets" | default dict -}} +{{- required "brCcs.secrets.POSTGRES_PASSWORD is required when migrations run against external PostgreSQL with a chart-managed Secret" (get $secrets "POSTGRES_PASSWORD") -}} +{{- 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-ccs/templates/bootstrap-postgres.yaml b/charts/br-ccs/templates/bootstrap-postgres.yaml new file mode 100644 index 000000000..3a247b10a --- /dev/null +++ b/charts/br-ccs/templates/bootstrap-postgres.yaml @@ -0,0 +1,129 @@ +{{- if .Values.global.externalPostgresDefinitions.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "br-ccs.fullname" . }}-bootstrap-postgres + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" "bootstrap" "name" "postgres") | nindent 4 }} +spec: + ttlSecondsAfterFinished: 300 + completions: 1 + parallelism: 1 + backoffLimit: 3 + template: + spec: + restartPolicy: Never + initContainers: + - name: wait-for-dependencies + image: busybox:1.37 + env: + - name: DB_HOST + value: {{ .Values.global.externalPostgresDefinitions.connection.host | quote }} + - name: DB_PORT + value: {{ .Values.global.externalPostgresDefinitions.connection.port | quote }} + command: + - /bin/sh + - -c + - > + TIMEOUT=300; + ELAPSED=0; + echo "Checking $DB_HOST:$DB_PORT..."; + while ! nc -z "$DB_HOST" "$DB_PORT"; do + if [ $ELAPSED -ge $TIMEOUT ]; then + echo "Timeout waiting for $DB_HOST:$DB_PORT after ${TIMEOUT}s"; + exit 1; + fi; + echo "$DB_HOST:$DB_PORT is not ready yet, waiting... (${ELAPSED}s/${TIMEOUT}s)"; + sleep 5; + ELAPSED=$((ELAPSED + 5)); + done; + echo "$DB_HOST:$DB_PORT is ready!"; + containers: + - name: psql + image: postgres:17 + env: + - name: DB_HOST + value: {{ .Values.global.externalPostgresDefinitions.connection.host | quote }} + - name: DB_PORT + value: {{ .Values.global.externalPostgresDefinitions.connection.port | quote }} + - name: DB_USER_ADMIN + {{- if .Values.global.externalPostgresDefinitions.postgresAdminLogin.useExistingSecret.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.externalPostgresDefinitions.postgresAdminLogin.useExistingSecret.name | quote }} + key: DB_USER_ADMIN + {{- else }} + value: {{ .Values.global.externalPostgresDefinitions.postgresAdminLogin.username | quote }} + {{- end }} + - name: DB_ADMIN_PASSWORD + {{- if .Values.global.externalPostgresDefinitions.postgresAdminLogin.useExistingSecret.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.externalPostgresDefinitions.postgresAdminLogin.useExistingSecret.name | quote }} + key: DB_ADMIN_PASSWORD + {{- else }} + value: {{ .Values.global.externalPostgresDefinitions.postgresAdminLogin.password | quote }} + {{- end }} + - name: DB_PASSWORD_BR_CCS + {{- if .Values.global.externalPostgresDefinitions.brCcsCredentials.useExistingSecret.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.externalPostgresDefinitions.brCcsCredentials.useExistingSecret.name | quote }} + key: DB_PASSWORD_BR_CCS + {{- else }} + value: {{ .Values.global.externalPostgresDefinitions.brCcsCredentials.password | quote }} + {{- end }} + - name: DB_DATABASE + value: postgres + command: + - /bin/bash + - -c + - | + set -euo pipefail + echo "=== br-ccs PostgreSQL Bootstrap ===" + echo "Host: $DB_HOST:$DB_PORT" + echo "" + + # The role/database name contains a hyphen, so it is always quoted in SQL. + echo "Checking existing PostgreSQL objects..." + DB_EXISTS=0 + ROLE_EXISTS=0 + + 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='br-ccs'" | grep -q 1; then + DB_EXISTS=1 + fi + 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='br-ccs'" | grep -q 1; then + ROLE_EXISTS=1 + fi + + if [ "$DB_EXISTS" = "1" ] && [ "$ROLE_EXISTS" = "1" ]; then + echo "PostgreSQL bootstrap already complete (database and role 'br-ccs' exist). Skipping creation." + else + if [ "$ROLE_EXISTS" = "1" ]; then + echo "Role 'br-ccs' already exists. Skipping creation." + else + echo "Creating role 'br-ccs'..." + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -v pw="$DB_PASSWORD_BR_CCS" -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "$DB_DATABASE" -c "CREATE ROLE \"br-ccs\" LOGIN PASSWORD :'pw'" + fi + + if [ "$DB_EXISTS" = "1" ]; then + echo "Database 'br-ccs' already exists. Skipping creation." + else + echo "Creating database 'br-ccs'..." + 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 \"br-ccs\" OWNER \"br-ccs\"" + fi + fi + + echo "Ensuring privileges and schema permissions..." + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "$DB_DATABASE" -c "ALTER USER \"br-ccs\" CREATEDB" || echo "Warning: Could not grant CREATEDB (may require superuser privileges)" + 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 \"br-ccs\" TO \"br-ccs\"" + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "br-ccs" -c "GRANT ALL ON SCHEMA public TO \"br-ccs\"" + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "br-ccs" -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"br-ccs\"" + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "br-ccs" -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO \"br-ccs\"" + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "br-ccs" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"br-ccs\"" + PGPASSWORD="$DB_ADMIN_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER_ADMIN" -d "br-ccs" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO \"br-ccs\"" + + echo "" + echo "=== br-ccs PostgreSQL Bootstrap completed successfully ===" +{{- end }} diff --git a/charts/br-ccs/templates/bootstrap-rabbitmq.yaml b/charts/br-ccs/templates/bootstrap-rabbitmq.yaml new file mode 100644 index 000000000..415c2bb26 --- /dev/null +++ b/charts/br-ccs/templates/bootstrap-rabbitmq.yaml @@ -0,0 +1,199 @@ +{{- if .Values.global.externalRabbitmqDefinitions.enabled }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "br-ccs.fullname" . }}-bootstrap-rabbitmq-definitions + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" "bootstrap" "name" "rabbitmq") | nindent 4 }} +data: + load_definitions.json: | +{{ .Files.Get "files/rabbitmq/load_definitions.json" | indent 4 }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "br-ccs.fullname" . }}-bootstrap-rabbitmq + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" "bootstrap" "name" "rabbitmq") | nindent 4 }} +spec: + ttlSecondsAfterFinished: 300 + completions: 1 + parallelism: 1 + backoffLimit: 3 + template: + spec: + restartPolicy: OnFailure + initContainers: + - name: wait-for-dependencies + image: busybox:1.37 + env: + - name: RABBITMQ_HOST + value: {{ .Values.global.externalRabbitmqDefinitions.connection.host | quote }} + - name: RABBITMQ_PORT + value: {{ .Values.global.externalRabbitmqDefinitions.connection.portAmqp | quote }} + command: + - /bin/sh + - -c + - > + TIMEOUT=300; + ELAPSED=0; + echo "Checking $RABBITMQ_HOST:$RABBITMQ_PORT..."; + while ! nc -z "$RABBITMQ_HOST" "$RABBITMQ_PORT"; do + if [ $ELAPSED -ge $TIMEOUT ]; then + echo "Timeout waiting for $RABBITMQ_HOST:$RABBITMQ_PORT after ${TIMEOUT}s"; + exit 1; + fi; + echo "$RABBITMQ_HOST:$RABBITMQ_PORT is not ready yet, waiting... (${ELAPSED}s/${TIMEOUT}s)"; + sleep 5; + ELAPSED=$((ELAPSED + 5)); + done; + echo "$RABBITMQ_HOST:$RABBITMQ_PORT is ready!"; + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + containers: + - name: apply-definitions + image: ghcr.io/jqlang/jq:1.7.1 + command: + - sh + - -c + - | + set -e + + umask 077 + printf 'user = "%s:%s"\n' "$RABBITMQ_ADMIN_USER" "$RABBITMQ_ADMIN_PASS" > /tmp/curlrc + + CURL_OPTS="-sS" + if [ "$RABBITMQ_SKIP_TLS_VERIFY" = "true" ]; then + CURL_OPTS="$CURL_OPTS -k" + echo "Warning: TLS verification is disabled" + fi + + if [ "$RABBITMQ_PROTOCOL" = "https" ] && [ "$RABBITMQ_PORT" = "443" ]; then + BASE_URL="$RABBITMQ_PROTOCOL://$RABBITMQ_HOST" + elif [ "$RABBITMQ_PROTOCOL" = "http" ] && [ "$RABBITMQ_PORT" = "80" ]; then + BASE_URL="$RABBITMQ_PROTOCOL://$RABBITMQ_HOST" + else + BASE_URL="$RABBITMQ_PROTOCOL://$RABBITMQ_HOST:$RABBITMQ_PORT" + fi + + echo "=== br-ccs RabbitMQ Bootstrap ===" + echo "API URL: $BASE_URL" + echo "" + + echo "Checking if RabbitMQ user 'br-ccs' already exists..." + BR_CCS_EXISTS=$(curl $CURL_OPTS -K /tmp/curlrc \ + "$BASE_URL/api/users/br-ccs" 2>/dev/null || echo "not_found") + + SKIP_DEFINITIONS=false + if echo "$BR_CCS_EXISTS" | grep -q '"name":"br-ccs"'; then + echo "RabbitMQ user 'br-ccs' already exists. Checking exchanges..." + EXCHANGE_EXISTS=$(curl $CURL_OPTS -K /tmp/curlrc \ + "$BASE_URL/api/exchanges/%2F/events" 2>/dev/null || echo "not_found") + if echo "$EXCHANGE_EXISTS" | grep -q '"name":"events"'; then + echo "RabbitMQ definitions already applied (user and exchange exist). Skipping definitions import." + SKIP_DEFINITIONS=true + fi + fi + + if [ "$SKIP_DEFINITIONS" = "false" ]; then + echo "Applying RabbitMQ definitions from file..." + HTTP_CODE=$(curl $CURL_OPTS -o /tmp/response.txt -w "%{http_code}" \ + -K /tmp/curlrc \ + -H "content-type: application/json" \ + -X POST \ + --data-binary @/definitions/load_definitions.json \ + "$BASE_URL/api/definitions") + if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then + echo "Error applying definitions (HTTP $HTTP_CODE):" + cat /tmp/response.txt + exit 1 + fi + echo "Definitions applied successfully." + fi + + echo "Updating RabbitMQ user 'br-ccs' password..." + USER_JSON=$(jq -n --arg pw "$RABBITMQ_BR_CCS_PASS" '{"password": $pw, "tags": ""}') + HTTP_CODE=$(curl $CURL_OPTS -o /tmp/response.txt -w "%{http_code}" \ + -K /tmp/curlrc \ + -H "content-type: application/json" \ + -X PUT \ + --data "$USER_JSON" \ + "$BASE_URL/api/users/br-ccs") + if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then + echo "Error updating br-ccs user (HTTP $HTTP_CODE):" + cat /tmp/response.txt + exit 1 + fi + echo "User password updated successfully." + + echo "" + echo "=== br-ccs RabbitMQ Bootstrap completed successfully ===" + env: + - name: RABBITMQ_PROTOCOL + value: {{ .Values.global.externalRabbitmqDefinitions.connection.protocol | quote }} + - name: RABBITMQ_HOST + value: {{ .Values.global.externalRabbitmqDefinitions.connection.host | quote }} + - name: RABBITMQ_PORT + value: {{ .Values.global.externalRabbitmqDefinitions.connection.port | quote }} + - name: RABBITMQ_SKIP_TLS_VERIFY + value: {{ .Values.global.externalRabbitmqDefinitions.connection.skipTlsVerify | default false | quote }} + - name: RABBITMQ_BR_CCS_PASS + {{- if .Values.global.externalRabbitmqDefinitions.brCcsCredentials.useExistingSecret.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.externalRabbitmqDefinitions.brCcsCredentials.useExistingSecret.name | quote }} + key: RABBITMQ_BR_CCS_PASS + {{- else }} + value: {{ .Values.global.externalRabbitmqDefinitions.brCcsCredentials.password | quote }} + {{- end }} + - name: RABBITMQ_ADMIN_PASS + {{- if .Values.global.externalRabbitmqDefinitions.rabbitmqAdminLogin.useExistingSecret.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.externalRabbitmqDefinitions.rabbitmqAdminLogin.useExistingSecret.name | quote }} + key: RABBITMQ_ADMIN_PASS + {{- else }} + value: {{ .Values.global.externalRabbitmqDefinitions.rabbitmqAdminLogin.password | quote }} + {{- end }} + - name: RABBITMQ_ADMIN_USER + {{- if .Values.global.externalRabbitmqDefinitions.rabbitmqAdminLogin.useExistingSecret.name }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.externalRabbitmqDefinitions.rabbitmqAdminLogin.useExistingSecret.name | quote }} + key: RABBITMQ_ADMIN_USER + {{- else }} + value: {{ .Values.global.externalRabbitmqDefinitions.rabbitmqAdminLogin.username | quote }} + {{- end }} + securityContext: + runAsUser: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + volumeMounts: + - name: definitions + mountPath: /definitions + - name: tmp + mountPath: /tmp + volumes: + - name: definitions + configMap: + name: {{ include "br-ccs.fullname" . }}-bootstrap-rabbitmq-definitions + items: + - key: load_definitions.json + path: load_definitions.json + - name: tmp + emptyDir: {} +{{- end }} diff --git a/charts/br-ccs/templates/configmap.yaml b/charts/br-ccs/templates/configmap.yaml new file mode 100644 index 000000000..d8a254baa --- /dev/null +++ b/charts/br-ccs/templates/configmap.yaml @@ -0,0 +1,410 @@ +{{- if .Values.brCcs.enabled }} +{{- $cm := .Values.brCcs.configmap | default dict }} +{{- /* Grouped param blocks (clean API). Each config key resolves via cfgValue: + configmap. (escape hatch) > brCcs.. > chart default. + Unset groups fall through to the chart default → render stays byte-identical. */ -}} +{{- $app := .Values.brCcs.app | default dict }} +{{- $srv := .Values.brCcs.server | default dict }} +{{- $cors := .Values.brCcs.cors | default dict }} +{{- $mt := .Values.brCcs.multiTenant | default dict }} +{{- $pgp := .Values.brCcs.postgres | default dict }} +{{- $rds := .Values.brCcs.redis | default dict }} +{{- $brk := .Values.brCcs.broker | default dict }} +{{- $ob := .Values.brCcs.outbox | default dict }} +{{- $fet := .Values.brCcs.fetcher | default dict }} +{{- $sta := .Values.brCcs.sta | default dict }} +{{- $rep := .Values.brCcs.reporter | default dict }} +{{- $swg := .Values.brCcs.swagger | default dict }} +{{- $rl := .Values.brCcs.rateLimit | default dict }} +{{- $pag := .Values.brCcs.pagination | default dict }} +{{- $obs := .Values.brCcs.observability | default dict }} +{{- $m2m := .Values.brCcs.m2m | default dict }} +{{- $rdy := .Values.brCcs.readiness | default dict }} +{{- $shd := .Values.brCcs.shutdown | default dict }} +{{- $os := .Values.brCcs.objectStorage | default dict }} +{{- $sd := .Values.brCcs.serviceDiscovery | default dict }} +{{- $strm := .Values.brCcs.streaming | default dict }} +{{- $multiTenantEnabled := eq (include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "MULTI_TENANT_ENABLED" "params" $mt "field" "enabled" "default" "false")) "true" }} +{{- $sdEnabled := eq (include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SD_ENABLED" "params" $sd "field" "enabled" "default" "false")) "true" }} +{{- $streamingEnabled := eq (include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "STREAMING_ENABLED" "params" $strm "field" "enabled" "default" "false")) "true" }} +{{- $pg := .Values.postgresql | default dict }} +{{- $vk := .Values.valkey | default dict }} +{{- $ds := .Values.brCcs.datastores | default dict }} +{{- $pgInternal := and (ne (toString $pg.enabled) "false") (not $pg.external) }} +{{- $vkInternal := and (ne (toString $vk.enabled) "false") (not $vk.external) }} +{{- $namespace := include "global.namespace" . }} +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ include "br-ccs.fullname" . }} + namespace: {{ $namespace }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 4 }} +data: + # ===================================================================== + # APPLICATION IDENTITY + # ===================================================================== + APPLICATION_NAME: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "APPLICATION_NAME" "params" $app "field" "name" "default" "br-ccs") | quote }} + ENV_NAME: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "ENV_NAME" "params" $app "field" "env" "default" "production") | quote }} + VERSION: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "VERSION" "params" $app "field" "version" "default" .Chart.AppVersion) | quote }} + LOG_LEVEL: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "LOG_LEVEL" "params" $app "field" "logLevel" "default" "info") | quote }} + DEPLOYMENT_MODE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "DEPLOYMENT_MODE" "params" $app "field" "deploymentMode" "default" "byoc") | quote }} + CCS_RUN_MODE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_RUN_MODE" "params" $app "field" "runMode" "default" "all") | quote }} + + # ===================================================================== + # HTTP SERVER + # SERVER_PORT is consumed by the in-container /healthcheck binary. + # ===================================================================== + SERVER_ADDRESS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SERVER_ADDRESS" "params" $srv "field" "address" "default" ":4030") | quote }} + SERVER_PORT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SERVER_PORT" "params" $srv "field" "port" "default" "4030") | quote }} + GRPC_PORT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "GRPC_PORT" "params" $srv "field" "grpcPort" "default" "7001") | quote }} + HTTP_BODY_LIMIT_BYTES: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "HTTP_BODY_LIMIT_BYTES" "params" $srv "field" "bodyLimitBytes" "default" "104857600") | quote }} + {{- if $cm.SERVER_TLS_CERT_FILE }} + SERVER_TLS_CERT_FILE: {{ $cm.SERVER_TLS_CERT_FILE | quote }} + {{- end }} + {{- if $cm.SERVER_TLS_KEY_FILE }} + SERVER_TLS_KEY_FILE: {{ $cm.SERVER_TLS_KEY_FILE | quote }} + {{- end }} + TLS_TERMINATED_UPSTREAM: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "TLS_TERMINATED_UPSTREAM" "params" $srv "field" "tlsTerminatedUpstream" "default" "true") | quote }} + TRUSTED_PROXIES: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "TRUSTED_PROXIES" "params" $srv "field" "trustedProxies" "default" "") | quote }} + + # ===================================================================== + # CORS POLICY + # ===================================================================== + CORS_ALLOWED_ORIGINS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CORS_ALLOWED_ORIGINS" "params" $cors "field" "allowedOrigins" "default" "") | quote }} + CORS_ALLOWED_METHODS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CORS_ALLOWED_METHODS" "params" $cors "field" "allowedMethods" "default" "GET,POST,PUT,PATCH,DELETE,OPTIONS") | quote }} + CORS_ALLOWED_HEADERS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CORS_ALLOWED_HEADERS" "params" $cors "field" "allowedHeaders" "default" "Authorization,Content-Type,X-Tenant-ID,X-Idempotency,X-Request-ID") | quote }} + CORS_EXPOSE_HEADERS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CORS_EXPOSE_HEADERS" "params" $cors "field" "exposeHeaders" "default" "") | quote }} + CORS_ALLOW_CREDENTIALS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CORS_ALLOW_CREDENTIALS" "params" $cors "field" "allowCredentials" "default" "false") | quote }} + + # ===================================================================== + # MULTI-TENANCY + # Toggle + DEFAULT_TENANT_ID always render. MT infra endpoints render + # only when MULTI_TENANT_ENABLED=true. + # ===================================================================== + DEFAULT_TENANT_ID: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "DEFAULT_TENANT_ID" "params" $mt "field" "defaultTenantId" "default" "11111111-1111-1111-1111-111111111111") | quote }} + MULTI_TENANT_ENABLED: {{ $multiTenantEnabled | quote }} + {{- /* Multi-tenant infra endpoints derive from global.multiTenant (env-wide) with a + component configmap.MULTI_TENANT_* override; emitted only when enabled. URL and + REDIS_HOST are required when enabled (fail-fast). */ -}} + {{- include "lerian-common.multiTenant.env" (dict + "context" $ "configmap" $cm "enabled" $multiTenantEnabled + "emitRedis" true "emitPool" true "emitCache" true + "requiredUrl" true "requiredRedisHost" true) | nindent 2 }} + + # ===================================================================== + # POSTGRESQL (primary + control-plane) + # HOST/PORT/USER/SSL via the datastore mask; tuning via brCcs.postgres. + # ===================================================================== + {{- if $pgInternal }} + POSTGRES_HOST: {{ include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "postgres" "field" "host" "nativeKey" "POSTGRES_HOST" "default" (printf "%s-postgresql-primary.%s.svc.cluster.local" .Release.Name $namespace)) | quote }} + {{- else }} + POSTGRES_HOST: {{ required "brCcs.configmap.POSTGRES_HOST (or brCcs.datastores.postgres.host / global.datastores.postgres.host) is required when the bundled postgresql subchart is disabled (external infra)" (include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "postgres" "field" "host" "nativeKey" "POSTGRES_HOST")) | quote }} + {{- end }} + POSTGRES_PORT: {{ include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "postgres" "field" "port" "nativeKey" "POSTGRES_PORT" "default" "5432") | quote }} + POSTGRES_USER: {{ include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "postgres" "field" "user" "nativeKey" "POSTGRES_USER" "default" "br-ccs") | quote }} + POSTGRES_NAME: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "POSTGRES_NAME" "params" $pgp "field" "name" "default" "br-ccs") | quote }} + POSTGRES_SSLMODE: {{ include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "postgres" "field" "ssl" "nativeKey" "POSTGRES_SSLMODE" "default" "require") | quote }} + MIGRATIONS_PATH: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "MIGRATIONS_PATH" "params" $pgp "field" "migrationsPath" "default" "migrations") | quote }} + SYSTEMPLANE_ENABLED: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SYSTEMPLANE_ENABLED" "params" $pgp "field" "systemplaneEnabled" "default" "false") | quote }} + POSTGRES_MAX_OPEN_CONNS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "POSTGRES_MAX_OPEN_CONNS" "params" $pgp "field" "maxOpenConns" "default" "25") | quote }} + POSTGRES_MAX_IDLE_CONNS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "POSTGRES_MAX_IDLE_CONNS" "params" $pgp "field" "maxIdleConns" "default" "5") | quote }} + POSTGRES_CONN_MAX_LIFETIME_MINS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "POSTGRES_CONN_MAX_LIFETIME_MINS" "params" $pgp "field" "connMaxLifetimeMins" "default" "30") | quote }} + POSTGRES_CONN_MAX_IDLE_TIME_MINS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "POSTGRES_CONN_MAX_IDLE_TIME_MINS" "params" $pgp "field" "connMaxIdleTimeMins" "default" "5") | quote }} + POSTGRES_CONNECT_TIMEOUT_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "POSTGRES_CONNECT_TIMEOUT_SEC" "params" $pgp "field" "connectTimeoutSec" "default" "10") | quote }} + INFRA_CONNECT_TIMEOUT_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "INFRA_CONNECT_TIMEOUT_SEC" "params" $pgp "field" "infraConnectTimeoutSec" "default" "30") | quote }} + {{- $replicaHost := include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "postgres" "field" "replicaHost" "nativeKey" "POSTGRES_REPLICA_HOST") }} + {{- if $replicaHost }} + POSTGRES_REPLICA_HOST: {{ $replicaHost | quote }} + POSTGRES_REPLICA_PORT: {{ $cm.POSTGRES_REPLICA_PORT | default "5432" | quote }} + POSTGRES_REPLICA_USER: {{ $cm.POSTGRES_REPLICA_USER | quote }} + POSTGRES_REPLICA_NAME: {{ $cm.POSTGRES_REPLICA_NAME | quote }} + POSTGRES_REPLICA_SSLMODE: {{ $cm.POSTGRES_REPLICA_SSLMODE | default "require" | quote }} + {{- end }} + + # ===================================================================== + # REDIS / VALKEY (app-level). HOST via mask; tuning via brCcs.redis. + # ===================================================================== + {{- if $vkInternal }} + REDIS_HOST: {{ include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "redis" "field" "host" "nativeKey" "REDIS_HOST" "default" (printf "%s-valkey-primary.%s.svc.cluster.local:6379" .Release.Name $namespace)) | quote }} + {{- else }} + REDIS_HOST: {{ required "brCcs.configmap.REDIS_HOST (or brCcs.datastores.redis.host / global.datastores.redis.host) is required when the bundled valkey subchart is disabled (external infra)" (include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "redis" "field" "host" "nativeKey" "REDIS_HOST")) | quote }} + {{- end }} + REDIS_DB: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_DB" "params" $rds "field" "db" "default" "0") | quote }} + REDIS_PROTOCOL: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_PROTOCOL" "params" $rds "field" "protocol" "default" "3") | quote }} + REDIS_POOL_SIZE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_POOL_SIZE" "params" $rds "field" "poolSize" "default" "10") | quote }} + REDIS_MIN_IDLE_CONNS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_MIN_IDLE_CONNS" "params" $rds "field" "minIdleConns" "default" "2") | quote }} + REDIS_READ_TIMEOUT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_READ_TIMEOUT" "params" $rds "field" "readTimeout" "default" "3") | quote }} + REDIS_WRITE_TIMEOUT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_WRITE_TIMEOUT" "params" $rds "field" "writeTimeout" "default" "3") | quote }} + REDIS_DIAL_TIMEOUT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_DIAL_TIMEOUT" "params" $rds "field" "dialTimeout" "default" "5") | quote }} + REDIS_POOL_TIMEOUT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_POOL_TIMEOUT" "params" $rds "field" "poolTimeout" "default" "2") | quote }} + REDIS_MAX_RETRIES: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_MAX_RETRIES" "params" $rds "field" "maxRetries" "default" "3") | quote }} + REDIS_MIN_RETRY_BACKOFF: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_MIN_RETRY_BACKOFF" "params" $rds "field" "minRetryBackoff" "default" "8") | quote }} + REDIS_MAX_RETRY_BACKOFF: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REDIS_MAX_RETRY_BACKOFF" "params" $rds "field" "maxRetryBackoff" "default" "1") | quote }} + {{- if $cm.REDIS_TLS }} + REDIS_TLS: {{ $cm.REDIS_TLS | quote }} + {{- end }} + {{- if $cm.REDIS_MASTER_NAME }} + REDIS_MASTER_NAME: {{ $cm.REDIS_MASTER_NAME | quote }} + {{- end }} + + # ===================================================================== + # CIRCUIT BREAKER (prerequisite for object storage adapters) + # ===================================================================== + CIRCUIT_BREAKER_ENABLED: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CIRCUIT_BREAKER_ENABLED" "params" $brk "field" "circuitBreakerEnabled" "default" "true") | quote }} + + # ===================================================================== + # RABBITMQ (optional). HOST/PORT/USER via mask; rest via brCcs.broker. + # ===================================================================== + RABBITMQ_ENABLED: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_ENABLED" "params" $brk "field" "enabled" "default" "false") | quote }} + {{- if eq (toString (include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_ENABLED" "params" $brk "field" "enabled" "default" "false"))) "true" }} + {{- /* RABBITMQ_URL carries the password and is rendered as a Secret (see + templates/secrets.yaml), never as a ConfigMap value. */}} + RABBITMQ_HOST: {{ include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "broker" "field" "host" "nativeKey" "RABBITMQ_HOST" "default" (printf "%s-rabbitmq.%s.svc.cluster.local" .Release.Name $namespace)) | quote }} + RABBITMQ_PORT_AMQP: {{ include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "broker" "field" "port" "nativeKey" "RABBITMQ_PORT_AMQP" "default" "5672") | quote }} + RABBITMQ_PORT_HOST: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_PORT_HOST" "params" $brk "field" "portHost" "default" "15672") | quote }} + RABBITMQ_DEFAULT_USER: {{ include "lerian-common.datastore.value" (dict "context" $ "dedicated" $ds "configmap" $cm "type" "broker" "field" "user" "nativeKey" "RABBITMQ_DEFAULT_USER" "default" "br-ccs") | quote }} + RABBITMQ_VHOST: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_VHOST" "params" $brk "field" "vhost" "default" "/") | quote }} + RABBITMQ_EXCHANGE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_EXCHANGE" "params" $brk "field" "exchange" "default" "events") | quote }} + RABBITMQ_REQUIRE_HEALTH_ALLOWED_HOSTS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_REQUIRE_HEALTH_ALLOWED_HOSTS" "params" $brk "field" "requireHealthAllowedHosts" "default" "false") | quote }} + RABBITMQ_ALLOW_INSECURE_HEALTH_CHECK: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_ALLOW_INSECURE_HEALTH_CHECK" "params" $brk "field" "allowInsecureHealthCheck" "default" "false") | quote }} + RABBITMQ_ALLOW_INSECURE_TLS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_ALLOW_INSECURE_TLS" "params" $brk "field" "allowInsecureTls" "default" "false") | quote }} + RABBITMQ_PUBLISHER_CONFIRM_TIMEOUT_MS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_PUBLISHER_CONFIRM_TIMEOUT_MS" "params" $brk "field" "publisherConfirmTimeoutMs" "default" "5000") | quote }} + RABBITMQ_PUBLISHER_RECOVERY_INITIAL_MS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_PUBLISHER_RECOVERY_INITIAL_MS" "params" $brk "field" "publisherRecoveryInitialMs" "default" "1000") | quote }} + RABBITMQ_PUBLISHER_RECOVERY_MAX_MS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_PUBLISHER_RECOVERY_MAX_MS" "params" $brk "field" "publisherRecoveryMaxMs" "default" "30000") | quote }} + RABBITMQ_PUBLISHER_MAX_RECOVERIES: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RABBITMQ_PUBLISHER_MAX_RECOVERIES" "params" $brk "field" "publisherMaxRecoveries" "default" "10") | quote }} + CCS_STA_DLQ_TTL_SECONDS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_STA_DLQ_TTL_SECONDS" "params" $brk "field" "staDlqTtlSeconds" "default" "30") | quote }} + {{- if $cm.RABBITMQ_QUEUE }} + RABBITMQ_QUEUE: {{ $cm.RABBITMQ_QUEUE | quote }} + {{- end }} + {{- if $cm.RABBITMQ_HEALTH_CHECK_URL }} + RABBITMQ_HEALTH_CHECK_URL: {{ $cm.RABBITMQ_HEALTH_CHECK_URL | quote }} + {{- end }} + {{- if $cm.RABBITMQ_HEALTH_CHECK_ALLOWED_HOSTS }} + RABBITMQ_HEALTH_CHECK_ALLOWED_HOSTS: {{ $cm.RABBITMQ_HEALTH_CHECK_ALLOWED_HOSTS | quote }} + {{- end }} + {{- end }} + + # ===================================================================== + # OUTBOX (brCcs.outbox) + # ===================================================================== + OUTBOX_ENABLED: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_ENABLED" "params" $ob "field" "enabled" "default" "false") | quote }} + OUTBOX_TABLE_NAME: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_TABLE_NAME" "params" $ob "field" "tableName" "default" "outbox_events") | quote }} + OUTBOX_DISPATCH_INTERVAL_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_DISPATCH_INTERVAL_SEC" "params" $ob "field" "dispatchIntervalSec" "default" "2") | quote }} + OUTBOX_BATCH_SIZE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_BATCH_SIZE" "params" $ob "field" "batchSize" "default" "50") | quote }} + OUTBOX_PUBLISH_MAX_ATTEMPTS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_PUBLISH_MAX_ATTEMPTS" "params" $ob "field" "publishMaxAttempts" "default" "3") | quote }} + OUTBOX_PUBLISH_BACKOFF_MS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_PUBLISH_BACKOFF_MS" "params" $ob "field" "publishBackoffMs" "default" "200") | quote }} + OUTBOX_RETRY_WINDOW_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_RETRY_WINDOW_SEC" "params" $ob "field" "retryWindowSec" "default" "300") | quote }} + OUTBOX_MAX_DISPATCH_ATTEMPTS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_MAX_DISPATCH_ATTEMPTS" "params" $ob "field" "maxDispatchAttempts" "default" "10") | quote }} + OUTBOX_PROCESSING_TIMEOUT_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_PROCESSING_TIMEOUT_SEC" "params" $ob "field" "processingTimeoutSec" "default" "600") | quote }} + OUTBOX_MAX_FAILED_PER_BATCH: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_MAX_FAILED_PER_BATCH" "params" $ob "field" "maxFailedPerBatch" "default" "25") | quote }} + OUTBOX_INCLUDE_TENANT_METRICS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_INCLUDE_TENANT_METRICS" "params" $ob "field" "includeTenantMetrics" "default" "false") | quote }} + OUTBOX_ALLOW_EMPTY_TENANT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OUTBOX_ALLOW_EMPTY_TENANT" "params" $ob "field" "allowEmptyTenant" "default" "true") | quote }} + {{- if $cm.OUTBOX_PRIORITY_EVENT_TYPES }} + OUTBOX_PRIORITY_EVENT_TYPES: {{ $cm.OUTBOX_PRIORITY_EVENT_TYPES | quote }} + {{- end }} + + # ===================================================================== + # FETCHER INTEGRATION (brCcs.fetcher) + # ===================================================================== + FETCHER_ENABLED: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "FETCHER_ENABLED" "params" $fet "field" "enabled" "default" "false") | quote }} + {{- if $cm.FETCHER_URL }} + FETCHER_URL: {{ $cm.FETCHER_URL | quote }} + {{- end }} + FETCHER_HTTP_TIMEOUT_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "FETCHER_HTTP_TIMEOUT_SEC" "params" $fet "field" "httpTimeoutSec" "default" "30") | quote }} + CCS_OUTBOX_DISPATCH_INTERVAL_MS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_OUTBOX_DISPATCH_INTERVAL_MS" "params" $fet "field" "ccsOutboxDispatchIntervalMs" "default" "500") | quote }} + CCS_OUTBOX_BATCH_SIZE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_OUTBOX_BATCH_SIZE" "params" $fet "field" "ccsOutboxBatchSize" "default" "50") | quote }} + CCS_OUTBOX_MAX_ATTEMPTS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_OUTBOX_MAX_ATTEMPTS" "params" $fet "field" "ccsOutboxMaxAttempts" "default" "10") | quote }} + CCS_OUTBOX_DLQ_ROUTING_KEY: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_OUTBOX_DLQ_ROUTING_KEY" "params" $fet "field" "ccsOutboxDlqRoutingKey" "default" "ccs.outbox.dlq") | quote }} + CCS_FETCHER_MAX_REDELIVERIES: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_FETCHER_MAX_REDELIVERIES" "params" $fet "field" "maxRedeliveries" "default" "5") | quote }} + CCS_FETCHER_RETRY_BACKOFF_MS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_FETCHER_RETRY_BACKOFF_MS" "params" $fet "field" "retryBackoffMs" "default" "5000") | quote }} + + # ===================================================================== + # STA INTEGRATION (brCcs.sta) + # ===================================================================== + STA_ENABLED: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "STA_ENABLED" "params" $sta "field" "enabled" "default" "false") | quote }} + {{- if $cm.STA_BASE_URL }} + STA_BASE_URL: {{ $cm.STA_BASE_URL | quote }} + {{- end }} + STA_HTTP_TIMEOUT_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "STA_HTTP_TIMEOUT_SEC" "params" $sta "field" "httpTimeoutSec" "default" "30") | quote }} + + # ===================================================================== + # REPORTER INTEGRATION (optional — brCcs.reporter) + # ===================================================================== + REPORTER_ENABLED: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REPORTER_ENABLED" "params" $rep "field" "enabled" "default" "false") | quote }} + {{- if eq (toString (include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REPORTER_ENABLED" "params" $rep "field" "enabled" "default" "false"))) "true" }} + REPORTER_URL: {{ required "brCcs.configmap.REPORTER_URL is required when REPORTER_ENABLED=true" $cm.REPORTER_URL | quote }} + REPORTER_HTTP_TIMEOUT_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "REPORTER_HTTP_TIMEOUT_SEC" "params" $rep "field" "httpTimeoutSec" "default" "30") | quote }} + {{- end }} + + # ===================================================================== + # AUTHENTICATION (inbound). ENABLED + HOST resolve via global.auth + # (globalValue: configmap. > global.auth. > default). HOST is + # emitted only when non-empty (preserves the pre-contract render). + # ===================================================================== + PLUGIN_AUTH_ENABLED: {{ include "lerian-common.globalValue" (dict "context" $ "configmap" $cm "block" "auth" "field" "enabled" "nativeKey" "PLUGIN_AUTH_ENABLED" "default" "false") | quote }} + {{- $pluginAuthHost := include "lerian-common.globalValue" (dict "context" $ "configmap" $cm "block" "auth" "field" "host" "nativeKey" "PLUGIN_AUTH_HOST" "default" "") }} + {{- if $pluginAuthHost }} + PLUGIN_AUTH_HOST: {{ $pluginAuthHost | quote }} + {{- end }} + + # ===================================================================== + # SWAGGER (brCcs.swagger) + # ===================================================================== + SWAGGER_ENABLED: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SWAGGER_ENABLED" "params" $swg "field" "enabled" "default" "true") | quote }} + SWAGGER_TITLE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SWAGGER_TITLE" "params" $swg "field" "title" "default" "br-ccs") | quote }} + SWAGGER_VERSION: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SWAGGER_VERSION" "params" $swg "field" "version" "default" .Chart.AppVersion) | quote }} + SWAGGER_BASE_PATH: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SWAGGER_BASE_PATH" "params" $swg "field" "basePath" "default" "/") | quote }} + SWAGGER_LEFT_DELIM: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SWAGGER_LEFT_DELIM" "params" $swg "field" "leftDelim" "default" "{{") | quote }} + SWAGGER_RIGHT_DELIM: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SWAGGER_RIGHT_DELIM" "params" $swg "field" "rightDelim" "default" "}}") | quote }} + {{- if $cm.SWAGGER_DESCRIPTION }} + SWAGGER_DESCRIPTION: {{ $cm.SWAGGER_DESCRIPTION | quote }} + {{- end }} + + # ===================================================================== + # OPENTELEMETRY (global.observability + per-component identity) + # ===================================================================== + {{- include "lerian-common.otel.env" (dict + "context" $ "configmap" $cm + "enabledDefault" "false" + "endpointDefault" "" + "deploymentEnvironmentDefault" "production") | nindent 2 }} + {{- include "lerian-common.otel.envFlat" (dict + "configmap" $cm + "keys" (list "OTEL_RESOURCE_SERVICE_NAME" "OTEL_LIBRARY_NAME" "OTEL_RESOURCE_SERVICE_VERSION") + "defaults" (dict + "OTEL_RESOURCE_SERVICE_NAME" "br-ccs" + "OTEL_LIBRARY_NAME" "br-ccs" + "OTEL_RESOURCE_SERVICE_VERSION" (.Values.brCcs.image.tag | default .Chart.AppVersion))) | nindent 2 }} + + # ===================================================================== + # SERVICE DISCOVERY (lerian-common.serviceDiscovery.env + global.serviceDiscovery). + # Toggle via brCcs.serviceDiscovery.enabled; SD_TOKEN (ACL) is a Secret. The full + # SD_* sibling block derives only when enabled. + # ===================================================================== + SD_ENABLED: {{ $sdEnabled | quote }} + {{- include "lerian-common.serviceDiscovery.env" (dict + "context" $ + "enabled" $sdEnabled + "configmap" $cm + "name" (include "br-ccs.fullname" .) + "port" .Values.brCcs.service.port + "namespace" $namespace + "ingressHost" (include "lerian-common.firstIngressHost" (dict "ingress" .Values.brCcs.ingress))) | nindent 2 }} + + # ===================================================================== + # STREAMING (lerian-common.streaming.env + global.streaming). Toggle via + # brCcs.streaming.enabled; the full SASL/broker block derives only when enabled. + # STREAMING_SASL_PASSWORD is a Secret (see templates/secrets.yaml). + # ===================================================================== + STREAMING_ENABLED: {{ $streamingEnabled | quote }} + {{- include "lerian-common.streaming.env" (dict + "context" $ + "enabled" $streamingEnabled + "configmap" $cm + "clientId" (include "br-ccs.fullname" .) + "cloudeventsSource" "lerian.br-ccs") | nindent 2 }} + + # ===================================================================== + # RATE LIMITING (brCcs.rateLimit). br-ccs has a bespoke surface (EXPORT/ + # DISPATCH tiers, no REDIS_TIMEOUT_MS), so each knob resolves via cfgValue + # rather than the fixed rateLimit.env bundle — same commons precedence. + # ===================================================================== + RATE_LIMIT_ENABLED: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RATE_LIMIT_ENABLED" "params" $rl "field" "enabled" "default" "true") | quote }} + RATE_LIMIT_MAX: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RATE_LIMIT_MAX" "params" $rl "field" "max" "default" "500") | quote }} + RATE_LIMIT_WINDOW_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RATE_LIMIT_WINDOW_SEC" "params" $rl "field" "windowSec" "default" "60") | quote }} + AGGRESSIVE_RATE_LIMIT_MAX: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "AGGRESSIVE_RATE_LIMIT_MAX" "params" $rl "field" "aggressiveMax" "default" "100") | quote }} + AGGRESSIVE_RATE_LIMIT_WINDOW_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "AGGRESSIVE_RATE_LIMIT_WINDOW_SEC" "params" $rl "field" "aggressiveWindowSec" "default" "60") | quote }} + RELAXED_RATE_LIMIT_MAX: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RELAXED_RATE_LIMIT_MAX" "params" $rl "field" "relaxedMax" "default" "1000") | quote }} + RELAXED_RATE_LIMIT_WINDOW_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "RELAXED_RATE_LIMIT_WINDOW_SEC" "params" $rl "field" "relaxedWindowSec" "default" "60") | quote }} + EXPORT_RATE_LIMIT_MAX: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "EXPORT_RATE_LIMIT_MAX" "params" $rl "field" "exportMax" "default" "60") | quote }} + EXPORT_RATE_LIMIT_WINDOW_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "EXPORT_RATE_LIMIT_WINDOW_SEC" "params" $rl "field" "exportWindowSec" "default" "60") | quote }} + DISPATCH_RATE_LIMIT_MAX: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "DISPATCH_RATE_LIMIT_MAX" "params" $rl "field" "dispatchMax" "default" "30") | quote }} + DISPATCH_RATE_LIMIT_WINDOW_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "DISPATCH_RATE_LIMIT_WINDOW_SEC" "params" $rl "field" "dispatchWindowSec" "default" "60") | quote }} + # Empty (default) keeps the safe fail-closed posture; "true" permits fail-open. + ALLOW_RATELIMIT_FAIL_OPEN: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "ALLOW_RATELIMIT_FAIL_OPEN" "params" $rl "field" "allowFailOpen" "default" "") | quote }} + + # ===================================================================== + # OBSERVABILITY / IDEMPOTENCY / PAGINATION + # ===================================================================== + DB_METRICS_INTERVAL_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "DB_METRICS_INTERVAL_SEC" "params" $obs "field" "dbMetricsIntervalSec" "default" "15") | quote }} + IDEMPOTENCY_RETRY_WINDOW_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "IDEMPOTENCY_RETRY_WINDOW_SEC" "params" $obs "field" "idempotencyRetryWindowSec" "default" "300") | quote }} + MAX_PAGINATION_LIMIT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "MAX_PAGINATION_LIMIT" "params" $pag "field" "limit" "default" "100") | quote }} + MAX_PAGINATION_MONTH_DATE_RANGE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "MAX_PAGINATION_MONTH_DATE_RANGE" "params" $pag "field" "monthDateRange" "default" "3") | quote }} + + # ===================================================================== + # M2M CREDENTIALS (non-secret portion — target service / OAuth2 / scope) + # API keys live in the Secret; SaaS uses AWS Secrets Manager. + # ===================================================================== + M2M_CREDENTIAL_CACHE_TTL_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "M2M_CREDENTIAL_CACHE_TTL_SEC" "params" $m2m "field" "credentialCacheTtlSec" "default" "300") | quote }} + AWS_REGION: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "AWS_REGION" "params" $m2m "field" "awsRegion" "default" "us-east-1") | quote }} + {{- range $k := (list "FETCHER_M2M_TARGET_SERVICE" "FETCHER_OAUTH2_TOKEN_URL" "FETCHER_OAUTH2_SCOPE" "REPORTER_M2M_TARGET_SERVICE" "REPORTER_OAUTH2_TOKEN_URL" "REPORTER_OAUTH2_SCOPE" "STA_M2M_TARGET_SERVICE" "STA_OAUTH2_TOKEN_URL" "STA_OAUTH2_SCOPE") }} + {{- if index $cm $k }} + {{ $k }}: {{ index $cm $k | quote }} + {{- end }} + {{- end }} + + # ===================================================================== + # DETAIL RESPONSE TUNING (optional) + # ===================================================================== + {{- range $k := (list "CCS_DETAIL_MESSAGE_EMIT_ENABLED" "CCS_DETAIL_MESSAGE_VALIDATION_ENABLED" "CCS_BACEN_DESTINATARIO_CNPJ_BASE" "CCS_ACCS005_OVERFLOW_THRESHOLD" "CCS_ACCS005_SECRECY_SITUACAO") }} + {{- if index $cm $k }} + {{ $k }}: {{ index $cm $k | quote }} + {{- end }} + {{- end }} + + # ===================================================================== + # FETCHER SNAPSHOT QUARANTINE (optional — br-ccs-140 Phase 2) + # Opt-in: skip + classify invalid CRM records instead of aborting the + # whole batch, up to an invalid/total ceiling above which it fails HIGH. + # Unset = app defaults (disabled; ceiling 0.05). Render only when set so + # the chart never overrides the app's fail-closed default. + # ===================================================================== + {{- range $k := (list "CCS_SNAPSHOT_QUARANTINE_INVALID_ENABLED" "CCS_SNAPSHOT_QUARANTINE_MAX_INVALID_RATIO") }} + {{- if index $cm $k }} + {{ $k }}: {{ index $cm $k | quote }} + {{- end }} + {{- end }} + + # ===================================================================== + # READINESS PROBE + GRACEFUL SHUTDOWN (brCcs.readiness / brCcs.shutdown) + # ===================================================================== + READYZ_PROBE_TIMEOUT_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "READYZ_PROBE_TIMEOUT_SEC" "params" $rdy "field" "probeTimeoutSec" "default" "5") | quote }} + READYZ_DEP_SLOW_THRESHOLD_MS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "READYZ_DEP_SLOW_THRESHOLD_MS" "params" $rdy "field" "depSlowThresholdMs" "default" "500") | quote }} + STARTUP_SELF_PROBE_MAX_DURATION_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "STARTUP_SELF_PROBE_MAX_DURATION_SEC" "params" $rdy "field" "startupMaxDurationSec" "default" "120") | quote }} + STARTUP_SELF_PROBE_INITIAL_DELAY_MS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "STARTUP_SELF_PROBE_INITIAL_DELAY_MS" "params" $rdy "field" "startupInitialDelayMs" "default" "1000") | quote }} + STARTUP_SELF_PROBE_MAX_DELAY_MS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "STARTUP_SELF_PROBE_MAX_DELAY_MS" "params" $rdy "field" "startupMaxDelayMs" "default" "30000") | quote }} + SHUTDOWN_DRAIN_GRACE_PERIOD_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SHUTDOWN_DRAIN_GRACE_PERIOD_SEC" "params" $shd "field" "drainGracePeriodSec" "default" "15") | quote }} + SHUTDOWN_TOTAL_TIMEOUT_SEC: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SHUTDOWN_TOTAL_TIMEOUT_SEC" "params" $shd "field" "totalTimeoutSec" "default" "30") | quote }} + + # ===================================================================== + # OBJECT STORAGE (non-secret — endpoints/region/bucket/flags — brCcs.objectStorage) + # Access + secret keys live in the Secret. + # ===================================================================== + SEAWEEDFS_S3_PORT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SEAWEEDFS_S3_PORT" "params" $os "field" "seaweedfsS3Port" "default" "8342") | quote }} + SEAWEEDFS_MASTER_PORT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "SEAWEEDFS_MASTER_PORT" "params" $os "field" "seaweedfsMasterPort" "default" "9343") | quote }} + OBJECT_STORAGE_STA_ENDPOINT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_STA_ENDPOINT" "params" $os "field" "staEndpoint" "default" "") | quote }} + OBJECT_STORAGE_STA_REGION: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_STA_REGION" "params" $os "field" "staRegion" "default" "us-east-1") | quote }} + OBJECT_STORAGE_STA_BUCKET: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_STA_BUCKET" "params" $os "field" "staBucket" "default" "") | quote }} + OBJECT_STORAGE_STA_USE_PATH_STYLE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_STA_USE_PATH_STYLE" "params" $os "field" "staUsePathStyle" "default" "true") | quote }} + OBJECT_STORAGE_STA_DISABLE_SSL: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_STA_DISABLE_SSL" "params" $os "field" "staDisableSsl" "default" "true") | quote }} + OBJECT_STORAGE_CCS_ENDPOINT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_CCS_ENDPOINT" "params" $os "field" "ccsEndpoint" "default" "") | quote }} + OBJECT_STORAGE_CCS_REGION: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_CCS_REGION" "params" $os "field" "ccsRegion" "default" "us-east-1") | quote }} + OBJECT_STORAGE_CCS_BUCKET: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_CCS_BUCKET" "params" $os "field" "ccsBucket" "default" "") | quote }} + OBJECT_STORAGE_CCS_USE_PATH_STYLE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_CCS_USE_PATH_STYLE" "params" $os "field" "ccsUsePathStyle" "default" "true") | quote }} + OBJECT_STORAGE_CCS_DISABLE_SSL: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_CCS_DISABLE_SSL" "params" $os "field" "ccsDisableSsl" "default" "true") | quote }} + OBJECT_STORAGE_FETCHER_ENDPOINT: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_FETCHER_ENDPOINT" "params" $os "field" "fetcherEndpoint" "default" "") | quote }} + OBJECT_STORAGE_FETCHER_REGION: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_FETCHER_REGION" "params" $os "field" "fetcherRegion" "default" "us-east-1") | quote }} + OBJECT_STORAGE_FETCHER_BUCKET: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_FETCHER_BUCKET" "params" $os "field" "fetcherBucket" "default" "") | quote }} + OBJECT_STORAGE_FETCHER_USE_PATH_STYLE: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_FETCHER_USE_PATH_STYLE" "params" $os "field" "fetcherUsePathStyle" "default" "true") | quote }} + OBJECT_STORAGE_FETCHER_DISABLE_SSL: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "OBJECT_STORAGE_FETCHER_DISABLE_SSL" "params" $os "field" "fetcherDisableSsl" "default" "true") | quote }} + CCS_OUTBOUND_RETENTION_DAYS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_OUTBOUND_RETENTION_DAYS" "params" $os "field" "outboundRetentionDays" "default" "30") | quote }} + CCS_ACCS009_RETENTION_DAYS: {{ include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "CCS_ACCS009_RETENTION_DAYS" "params" $os "field" "accs009RetentionDays" "default" "30") | quote }} + + # ===================================================================== + # EXTRA OVERRIDES (escape hatch for optional knobs not modeled above) + # ===================================================================== + {{- range $key, $value := .Values.brCcs.extraEnvVars }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/charts/br-ccs/templates/deployment.yaml b/charts/br-ccs/templates/deployment.yaml new file mode 100644 index 000000000..9db8e19b4 --- /dev/null +++ b/charts/br-ccs/templates/deployment.yaml @@ -0,0 +1,181 @@ +{{- if .Values.brCcs.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "br-ccs.fullname" . }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 4 }} +spec: + revisionHistoryLimit: {{ .Values.brCcs.revisionHistoryLimit | default 10 }} + strategy: + type: {{ .Values.brCcs.deploymentUpdate.type }} + {{- if eq .Values.brCcs.deploymentUpdate.type "RollingUpdate" }} + rollingUpdate: + maxSurge: {{ .Values.brCcs.deploymentUpdate.maxSurge }} + maxUnavailable: {{ .Values.brCcs.deploymentUpdate.maxUnavailable }} + {{- end }} + {{- if not .Values.brCcs.autoscaling.enabled }} + replicas: {{ .Values.brCcs.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "br-ccs.selectorLabels" (dict "context" . "name" .Values.brCcs.name ) | nindent 6 }} + template: + metadata: + {{- with .Values.brCcs.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 8 }} + spec: + {{- with .Values.brCcs.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "br-ccs.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.brCcs.podSecurityContext | nindent 8 }} + # br-ccs connects to PostgreSQL (control-plane) and Redis/Valkey at boot in + # both single-tenant and hybrid multi-tenant modes, so the dependency wait + # gate always runs. + initContainers: + - name: wait-for-dependencies + image: busybox:1.37 + envFrom: + - configMapRef: + name: {{ include "br-ccs.fullname" . }} + command: + - /bin/sh + - -c + - | + MAX_ATTEMPTS=60 + SLEEP_SECONDS=5 + # Total timeout: 60 * 5 = 300 seconds (5 minutes) + + wait_for_service() { + local HOST="$1" + local PORT="$2" + local ATTEMPTS=0 + echo "Checking $HOST:$PORT..." + while ! nc -z "$HOST" "$PORT"; do + ATTEMPTS=$((ATTEMPTS + 1)) + if [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; then + echo "Timeout waiting for $HOST:$PORT after $((MAX_ATTEMPTS * SLEEP_SECONDS)) seconds" + exit 1 + fi + echo "$HOST:$PORT is not ready yet, waiting... (attempt $ATTEMPTS/$MAX_ATTEMPTS)" + sleep $SLEEP_SECONDS + done + echo "$HOST:$PORT is ready!" + } + + # Wait for PostgreSQL + wait_for_service "$POSTGRES_HOST" "$POSTGRES_PORT" + + # Wait for Redis/Valkey (REDIS_HOST is host:port) + REDIS_SVC=$(echo "$REDIS_HOST" | cut -d: -f1) + REDIS_PORT_NUM=$(echo "$REDIS_HOST" | cut -d: -f2) + wait_for_service "$REDIS_SVC" "$REDIS_PORT_NUM" + + echo "All dependencies are ready!" + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + containers: + - name: {{ include "br-ccs.fullname" . }} + securityContext: + {{- toYaml .Values.brCcs.securityContext | nindent 12 }} + image: "{{ .Values.brCcs.image.repository }}:{{ .Values.brCcs.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.brCcs.image.pullPolicy }} + envFrom: + - secretRef: + name: {{ if .Values.brCcs.useExistingSecret }}{{ required "brCcs.existingSecretName is required when brCcs.useExistingSecret is true" .Values.brCcs.existingSecretName }}{{ else }}{{ include "br-ccs.fullname" . }}{{ end }} + - configMapRef: + name: {{ include "br-ccs.fullname" . }} + env: + {{- $secretName := ternary .Values.brCcs.existingSecretName (include "br-ccs.fullname" .) .Values.brCcs.useExistingSecret }} + {{- $pg := .Values.postgresql | default dict }} + {{- $pgAuth := $pg.auth | default dict }} + {{- if or (and (ne (toString $pg.enabled) "false") (not $pg.external)) $pgAuth.existingSecret }} + {{- include "br-ccs.infraSecretRef" (dict "context" $ "subchart" "postgresql" "key" "password" "envName" "POSTGRES_PASSWORD") | nindent 10 }} + {{- else if .Values.brCcs.secrets.POSTGRES_PASSWORD }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: POSTGRES_PASSWORD + {{- end }} + {{- $vk := .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-ccs.infraSecretRef" (dict "context" $ "subchart" "valkey" "key" "valkey-password" "envName" "REDIS_PASSWORD") | nindent 10 }} + {{- else if .Values.brCcs.secrets.REDIS_PASSWORD }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: REDIS_PASSWORD + {{- end }} + {{- if eq (toString .Values.brCcs.configmap.ENABLE_TELEMETRY) "true" }} + - name: "HOST_IP" + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: "POD_IP" + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: "OTEL_EXPORTER_OTLP_ENDPOINT" + value: "$(HOST_IP):4317" + - name: "OTEL_RESOURCE_ATTRIBUTES" + value: "k8s.pod.ip=$(POD_IP)" + {{- end }} + ports: + - name: http + containerPort: 4030 + protocol: TCP + livenessProbe: + httpGet: + path: {{ .Values.brCcs.livenessProbe.path | default "/health" }} + port: http + initialDelaySeconds: {{ .Values.brCcs.livenessProbe.initialDelaySeconds | default 15 }} + periodSeconds: {{ .Values.brCcs.livenessProbe.periodSeconds | default 20 }} + timeoutSeconds: {{ .Values.brCcs.livenessProbe.timeoutSeconds | default 5 }} + successThreshold: {{ .Values.brCcs.livenessProbe.successThreshold | default 1 }} + failureThreshold: {{ .Values.brCcs.livenessProbe.failureThreshold | default 3 }} + readinessProbe: + httpGet: + path: {{ .Values.brCcs.readinessProbe.path | default "/readyz" }} + port: http + initialDelaySeconds: {{ .Values.brCcs.readinessProbe.initialDelaySeconds | default 5 }} + periodSeconds: {{ .Values.brCcs.readinessProbe.periodSeconds | default 10 }} + timeoutSeconds: {{ .Values.brCcs.readinessProbe.timeoutSeconds | default 5 }} + successThreshold: {{ .Values.brCcs.readinessProbe.successThreshold | default 1 }} + failureThreshold: {{ .Values.brCcs.readinessProbe.failureThreshold | default 3 }} + resources: + {{- toYaml .Values.brCcs.resources | nindent 12 }} + {{- with .Values.brCcs.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.brCcs.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.brCcs.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.brCcs.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/br-ccs/templates/hpa.yaml b/charts/br-ccs/templates/hpa.yaml new file mode 100644 index 000000000..998949990 --- /dev/null +++ b/charts/br-ccs/templates/hpa.yaml @@ -0,0 +1,33 @@ +{{- if and .Values.brCcs.enabled .Values.brCcs.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "br-ccs.fullname" . }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "br-ccs.fullname" . }} + minReplicas: {{ .Values.brCcs.autoscaling.minReplicas }} + maxReplicas: {{ .Values.brCcs.autoscaling.maxReplicas }} + metrics: + {{- if .Values.brCcs.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.brCcs.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.brCcs.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.brCcs.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/br-ccs/templates/ingress.yaml b/charts/br-ccs/templates/ingress.yaml new file mode 100644 index 000000000..5575ffaf8 --- /dev/null +++ b/charts/br-ccs/templates/ingress.yaml @@ -0,0 +1,62 @@ +{{- if .Values.brCcs.ingress.enabled -}} +{{- $ingressName := include "br-ccs.fullname" . -}} +{{- $svcPort := .Values.brCcs.service.port -}} +{{- if and .Values.brCcs.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.brCcs.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.brCcs.ingress.annotations "kubernetes.io/ingress.class" .Values.brCcs.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: {{ $ingressName }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 4 }} + {{- with .Values.brCcs.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.brCcs.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.brCcs.ingress.className }} + {{- end }} + {{- if .Values.brCcs.ingress.tls }} + tls: + {{- range .Values.brCcs.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.brCcs.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: {{ $ingressName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $ingressName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/br-ccs/templates/migration-secret.yaml b/charts/br-ccs/templates/migration-secret.yaml new file mode 100644 index 000000000..ded00d6bc --- /dev/null +++ b/charts/br-ccs/templates/migration-secret.yaml @@ -0,0 +1,33 @@ +{{- $brCcs := .Values.brCcs | default dict }} +{{- $migrations := get $brCcs "migrations" | default dict }} +{{- $postgresql := .Values.postgresql | default dict }} +{{- $migrationsEnabled := and (eq (lower (toString (get $brCcs "enabled" | default false))) "true") (eq (lower (toString (get $migrations "enabled" | default false))) "true") }} +{{- $internalPg := and (eq (lower (toString (get $postgresql "enabled" | default false))) "true") (not (eq (lower (toString (get $postgresql "external" | default false))) "true")) }} +{{- $migUseExisting := eq (lower (toString (get $migrations "useExistingSecret" | default false))) "true" }} +{{- $appUseExisting := eq (lower (toString (get $brCcs "useExistingSecret" | default false))) "true" }} +{{- /* Minimal, migration-only Secret. Rendered ONLY for: migrations enabled, chart-managed + credential (no existing Secret), EXTERNAL Postgres. In that case the migration runs as a + pre-install/pre-upgrade hook BEFORE the normal application Secret exists, so it needs its + own credential available early. It carries ONLY POSTGRES_PASSWORD — the full application + Secret stays a normal (non-hook) resource so sensitive runtime keys are never orphaned. */}} +{{- if and $migrationsEnabled (not $internalPg) (not $migUseExisting) (not $appUseExisting) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-migrations" (include "br-ccs.fullname" .) | trunc 63 | trimSuffix "-" }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" "migrations" "name" (get $brCcs "name")) | nindent 4 }} + annotations: + # Pre-install/pre-upgrade hook (weight -5) so it exists before the migration Job + # (weight -1). Holds ONLY POSTGRES_PASSWORD. + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation + "argocd.argoproj.io/hook": PreSync + "argocd.argoproj.io/sync-wave": "-5" + "argocd.argoproj.io/hook-delete-policy": BeforeHookCreation +type: Opaque +stringData: + POSTGRES_PASSWORD: {{ include "br-ccs.migrationPostgresPassword" . | quote }} +{{- end }} diff --git a/charts/br-ccs/templates/migrations.yaml b/charts/br-ccs/templates/migrations.yaml new file mode 100644 index 000000000..734cd71ab --- /dev/null +++ b/charts/br-ccs/templates/migrations.yaml @@ -0,0 +1,166 @@ +{{- $brCcs := .Values.brCcs | default dict }} +{{- $migrations := get $brCcs "migrations" | default dict }} +{{- $configmap := get $brCcs "configmap" | default dict }} +{{- $postgresql := .Values.postgresql | default dict }} +{{- $migrationImage := get $migrations "image" | default dict }} +{{- $defaultMigrationResources := dict "requests" (dict "cpu" "50m" "memory" "64Mi") "limits" (dict "cpu" "250m" "memory" "256Mi") }} +{{- $migrationsEnabled := and (eq (lower (toString (get $brCcs "enabled" | default false))) "true") (eq (lower (toString (get $migrations "enabled" | default false))) "true") }} +{{- if $migrationsEnabled }} +{{- /* Internal (chart-managed) Postgres support. When postgresql.enabled=true and not + external, the database is provisioned during the Sync phase (bitnami), so the + migration runs as a POST hook (after the DB exists) with a wait-for-Postgres + initContainer. External/pre-existing Postgres keeps the PRE hook (DB already exists). */}} +{{- $internalPg := and (eq (lower (toString (get $postgresql "enabled" | default false))) "true") (not (eq (lower (toString (get $postgresql "external" | default false))) "true")) }} +{{- $appUseExisting := eq (lower (toString (get $brCcs "useExistingSecret" | default false))) "true" }} +{{- $migUseExisting := eq (lower (toString (get $migrations "useExistingSecret" | default false))) "true" }} +{{- $existingSecretName := "" }} +{{- if $migUseExisting }} +{{- $existingSecretName = required "brCcs.migrations.existingSecretName is required when brCcs.migrations.useExistingSecret=true" (get $migrations "existingSecretName") }} +{{- else if $appUseExisting }} +{{- $existingSecretName = required "brCcs.existingSecretName is required when brCcs.useExistingSecret=true" (get $brCcs "existingSecretName") }} +{{- else if $internalPg }} +{{- $existingSecretName = include "br-ccs.fullname" . }} +{{- else }} +{{- $existingSecretName = printf "%s-migrations" (include "br-ccs.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- $namespace := include "global.namespace" . }} +{{- $migrationTag := get $migrationImage "tag" | default "" }} +{{- $migrationDigest := get $migrationImage "digest" | default "" }} +{{- if and (empty $migrationTag) (empty $migrationDigest) }} +{{- $migrationTag = .Values.brCcs.image.tag | default .Chart.AppVersion }} +{{- end }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ printf "%s-migrations" (include "br-ccs.fullname" .) | trunc 63 | trimSuffix "-" }} + namespace: {{ $namespace }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" "migrations" "name" (get $brCcs "name") ) | nindent 4 }} + annotations: + {{- if $internalPg }} + # Internal Postgres is provisioned during Sync — run AFTER it exists and is reachable. + "helm.sh/hook": post-install,post-upgrade + "argocd.argoproj.io/hook": PostSync + {{- else }} + # External Postgres already exists — run BEFORE the app rolls out (schema-first). + "helm.sh/hook": pre-install,pre-upgrade + "argocd.argoproj.io/hook": PreSync + {{- end }} + "helm.sh/hook-weight": "-1" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + "argocd.argoproj.io/hook-delete-policy": BeforeHookCreation,HookSucceeded + {{- with (get $migrations "annotations") }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + backoffLimit: {{ get $migrations "backoffLimit" | default 3 }} + activeDeadlineSeconds: {{ get $migrations "activeDeadlineSeconds" | default 600 }} + ttlSecondsAfterFinished: {{ get $migrations "ttlSecondsAfterFinished" | default 600 }} + template: + metadata: + labels: + {{- include "br-ccs.labels" (dict "context" . "component" "migrations" "name" (get $brCcs "name") ) | nindent 8 }} + {{- with (get $migrations "podAnnotations") }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Never + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + {{- with (get $brCcs "imagePullSecrets") }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with (get $migrations "serviceAccountName") }} + serviceAccountName: {{ . | quote }} + {{- end }} + {{- if $internalPg }} + initContainers: + - name: wait-for-postgres + image: busybox:1.37 + command: + - /bin/sh + - -c + - > + echo "Waiting for $POSTGRES_HOST:$POSTGRES_PORT..."; + ELAPSED=0; + while ! nc -z "$POSTGRES_HOST" "$POSTGRES_PORT"; do + if [ $ELAPSED -ge 300 ]; then echo "Timeout after 300s"; exit 1; fi; + echo "not ready, waiting (${ELAPSED}s/300s)"; sleep 5; ELAPSED=$((ELAPSED + 5)); + done; + echo "Postgres is ready"; + env: + - name: POSTGRES_HOST + value: {{ get $configmap "POSTGRES_HOST" | default (printf "%s-postgresql-primary.%s.svc.cluster.local" .Release.Name $namespace) | quote }} + - name: POSTGRES_PORT + value: {{ get $configmap "POSTGRES_PORT" | default "5432" | quote }} + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + {{- end }} + containers: + - name: migrations + {{- $migrationRepository := get $migrationImage "repository" | default "ghcr.io/lerianstudio/br-ccs-migrations" }} + {{- if $migrationDigest }} + image: "{{ $migrationRepository }}@{{ $migrationDigest }}" + {{- else }} + image: "{{ $migrationRepository }}:{{ $migrationTag }}" + {{- end }} + imagePullPolicy: {{ get $migrationImage "pullPolicy" | default "IfNotPresent" }} + env: + - name: MIGRATIONS_PATH + value: {{ get $migrations "path" | default "/migrations" | quote }} + - name: POSTGRES_HOST + value: {{ get $configmap "POSTGRES_HOST" | default (printf "%s-postgresql-primary.%s.svc.cluster.local" .Release.Name $namespace) | quote }} + - name: POSTGRES_PORT + value: {{ get $configmap "POSTGRES_PORT" | default "5432" | quote }} + - name: POSTGRES_USER + value: {{ get $configmap "POSTGRES_USER" | default "br-ccs" | quote }} + - name: POSTGRES_NAME + value: {{ get $configmap "POSTGRES_NAME" | default "br-ccs" | quote }} + - name: POSTGRES_SSLMODE + value: {{ get $configmap "POSTGRES_SSLMODE" | default "require" | quote }} + - name: POSTGRES_CONNECT_TIMEOUT_SEC + value: {{ get $configmap "POSTGRES_CONNECT_TIMEOUT_SEC" | default "10" | quote }} + {{- if $internalPg }} + {{- include "br-ccs.infraSecretRef" (dict "context" $ "subchart" "postgresql" "key" "password" "envName" "POSTGRES_PASSWORD") | nindent 12 }} + {{- else }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $existingSecretName }} + key: POSTGRES_PASSWORD + {{- end }} + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + resources: + {{- toYaml (get $migrations "resources" | default $defaultMigrationResources) | nindent 12 }} + {{- with (get $brCcs "nodeSelector") }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with (get $brCcs "affinity") }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with (get $brCcs "tolerations") }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/br-ccs/templates/pdb.yaml b/charts/br-ccs/templates/pdb.yaml new file mode 100644 index 000000000..d8ba6c5e6 --- /dev/null +++ b/charts/br-ccs/templates/pdb.yaml @@ -0,0 +1,24 @@ +{{- if and .Values.brCcs.enabled .Values.brCcs.pdb.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "br-ccs.fullname" . }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 4 }} + {{- with .Values.brCcs.pdb.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} +spec: + {{- with .Values.brCcs.pdb.maxUnavailable }} + maxUnavailable: {{ . }} + {{- else }} + minAvailable: {{ .Values.brCcs.pdb.minAvailable | default 1 }} + {{- end }} + selector: + matchLabels: + {{- include "br-ccs.selectorLabels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 6 }} +{{- end }} diff --git a/charts/br-ccs/templates/rabbitmq_load_definitions.yaml b/charts/br-ccs/templates/rabbitmq_load_definitions.yaml new file mode 100644 index 000000000..d992d181f --- /dev/null +++ b/charts/br-ccs/templates/rabbitmq_load_definitions.yaml @@ -0,0 +1,12 @@ +{{- if .Values.rabbitmq.enabled }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ include "br-ccs.fullname" . }}-rabbitmq-load-definition + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" "rabbitmq" "name" "load-definition") | nindent 4 }} +data: + load_definition.json: | + {{ .Files.Get "files/rabbitmq/load_definitions.json" | b64enc | nindent 4 }} +{{- end }} diff --git a/charts/br-ccs/templates/secrets.yaml b/charts/br-ccs/templates/secrets.yaml new file mode 100644 index 000000000..12dd8f50a --- /dev/null +++ b/charts/br-ccs/templates/secrets.yaml @@ -0,0 +1,150 @@ +{{- if .Values.brCcs.enabled }} +{{- if not .Values.brCcs.useExistingSecret }} +{{- $s := .Values.brCcs.secrets | default dict }} +{{- $cm := .Values.brCcs.configmap | default dict }} +{{- $mtEnabled := eq (include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "MULTI_TENANT_ENABLED" "params" (.Values.brCcs.multiTenant | default dict) "field" "enabled" "default" "false")) "true" }} +{{- $rmqEnabled := eq (toString ($cm.RABBITMQ_ENABLED | default "false")) "true" }} +{{- $streamingEnabled := eq (include "lerian-common.cfgValue" (dict "configmap" $cm "nativeKey" "STREAMING_ENABLED" "params" (.Values.brCcs.streaming | default dict) "field" "enabled" "default" "false")) "true" }} +{{- /* Resolve the streaming SASL mechanism/username the same way streaming.env does + (configmap.STREAMING_SASL_* > global.streaming.*) so streaming.secret's fail-fast + agrees with the ConfigMap. */ -}} +{{- $gStream := (.Values.global | default dict).streaming | default dict }} +{{- $streamMech := $gStream.saslMechanism | default "" }}{{- if hasKey $cm "STREAMING_SASL_MECHANISM" }}{{- $streamMech = index $cm "STREAMING_SASL_MECHANISM" }}{{- end }} +{{- $streamUser := $gStream.saslUsername | default "" }}{{- if hasKey $cm "STREAMING_SASL_USERNAME" }}{{- $streamUser = index $cm "STREAMING_SASL_USERNAME" }}{{- end }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "br-ccs.fullname" . }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 4 }} +type: Opaque +stringData: + # Infra passwords (PostgreSQL / Valkey) are single-sourced from the bundled + # Bitnami subchart Secrets and read via secretKeyRef on the deployment. They + # are written here ONLY for an EXTERNAL infra component without an + # .auth.existingSecret. + {{- $pg := .Values.postgresql | default dict }} + {{- $pgAuth := $pg.auth | default dict }} + {{- if and (not (and (ne (toString $pg.enabled) "false") (not $pg.external))) (not $pgAuth.existingSecret) $s.POSTGRES_PASSWORD }} + POSTGRES_PASSWORD: {{ $s.POSTGRES_PASSWORD | quote }} + {{- if $s.POSTGRES_REPLICA_PASSWORD }} + POSTGRES_REPLICA_PASSWORD: {{ $s.POSTGRES_REPLICA_PASSWORD | quote }} + {{- end }} + {{- end }} + + # Redis/Valkey password (external-only; bundled valkey is read from its subchart Secret) + {{- $vk := .Values.valkey | default dict }} + {{- $vkAuth := $vk.auth | default dict }} + {{- if and (not (and (ne (toString $vk.enabled) "false") (not $vk.external) $vkAuth.enabled)) (not $vkAuth.existingSecret) $s.REDIS_PASSWORD }} + REDIS_PASSWORD: {{ $s.REDIS_PASSWORD | quote }} + {{- end }} + {{- if $s.REDIS_CA_CERT }} + REDIS_CA_CERT: {{ $s.REDIS_CA_CERT | quote }} + {{- end }} + + # Multi-tenant (tenant-manager) secrets. The uniform SERVICE_API_KEY (required) + # + REDIS_PASSWORD (optional) come from lerian-common.multiTenant.secret, which + # fails fast when MT is enabled but the API key is empty. REDIS_CA_CERT is a + # br-ccs extra kept inline. + {{- with (include "lerian-common.multiTenant.secret" (dict + "context" . "secrets" $s + "secretName" (include "br-ccs.fullname" .) + "valuesPrefix" "brCcs.secrets." "mode" "stringData" + "enabled" $mtEnabled + "useExistingSecret" .Values.brCcs.useExistingSecret)) }} + {{- . | nindent 2 }} + {{- end }} + {{- if $s.MULTI_TENANT_REDIS_CA_CERT }} + MULTI_TENANT_REDIS_CA_CERT: {{ $s.MULTI_TENANT_REDIS_CA_CERT | quote }} + {{- end }} + + # RabbitMQ credentials. When RABBITMQ_ENABLED=true and no full RABBITMQ_URL is + # supplied, the app assembles the dial string from HOST/PORT/USER/PASS, so + # RABBITMQ_DEFAULT_PASS is required (fail-fast). If RABBITMQ_URL is set (it + # embeds the password) the pass is optional. + {{- if and $rmqEnabled (not $s.RABBITMQ_URL) }} + RABBITMQ_DEFAULT_PASS: {{ required "brCcs.secrets.RABBITMQ_DEFAULT_PASS is required when RABBITMQ_ENABLED=true (or provide brCcs.secrets.RABBITMQ_URL instead)" $s.RABBITMQ_DEFAULT_PASS | quote }} + {{- else if $s.RABBITMQ_DEFAULT_PASS }} + RABBITMQ_DEFAULT_PASS: {{ $s.RABBITMQ_DEFAULT_PASS | quote }} + {{- end }} + # Full amqp:// dial string. Embeds the password, so it is a Secret (never a + # ConfigMap value). When set, the app dials via this instead of assembling the + # URL from RABBITMQ_HOST/PORT/USER/PASS. + {{- if $s.RABBITMQ_URL }} + RABBITMQ_URL: {{ $s.RABBITMQ_URL | quote }} + {{- end }} + + # Service-discovery ACL token (optional; only relevant when SD is enabled). + {{- if $s.SD_TOKEN }} + SD_TOKEN: {{ $s.SD_TOKEN | quote }} + {{- end }} + + # Streaming SASL credentials (lerian-common.streaming.secret). Emitted only when + # streaming is enabled with a SASL mechanism; STREAMING_SASL_PASSWORD is then + # required (fail-fast), STREAMING_TLS_CA_CERT optional. + {{- with (include "lerian-common.streaming.secret" (dict + "context" . "secrets" $s + "secretName" (include "br-ccs.fullname" .) + "valuesPrefix" "brCcs.secrets." "mode" "stringData" + "enabled" $streamingEnabled + "useExistingSecret" .Values.brCcs.useExistingSecret + "saslMechanism" $streamMech "saslUsername" $streamUser)) }} + {{- . | nindent 2 }} + {{- end }} + + # Crypto keys (REQUIRED in production — empty CCS_CRYPTO_MASTER_KEY fails fast + # at boot by design; supply via a secrets manager or an existing Secret). + {{- if $s.CCS_CRYPTO_MASTER_KEY }} + CCS_CRYPTO_MASTER_KEY: {{ $s.CCS_CRYPTO_MASTER_KEY | quote }} + {{- end }} + {{- if $s.FETCHER_CRYPTO_KEY }} + FETCHER_CRYPTO_KEY: {{ $s.FETCHER_CRYPTO_KEY | quote }} + {{- end }} + {{- /* STA business-event HMAC master-key catalogue ("v1:,..."). Held as a + Secret (not a ConfigMap) because it is HMAC key material — the app reads + STA_EVENT_HMAC_MASTER_KEYS via the envFrom secretRef and fails fast at + boot when STA_EVENT_HMAC_VERIFY_ENABLED is on but the catalogue is + missing/malformed. Inert while verification stays disabled. */}} + {{- if $s.STA_EVENT_HMAC_MASTER_KEYS }} + STA_EVENT_HMAC_MASTER_KEYS: {{ $s.STA_EVENT_HMAC_MASTER_KEYS | quote }} + {{- end }} + + # License (lib-license-go). Read as env via the envFrom secretRef. LICENSE_KEY + # authorizes the app against the Lerian license gateway at boot (fail-closed); + # ORGANIZATION_IDS selects the enforcement scope (e.g. "global"). Held as a + # Secret (not a ConfigMap) so the key material is not exposed in plain + # manifests, matching the br-slc / br-sta / fetcher convention. + {{- if $s.LICENSE_KEY }} + LICENSE_KEY: {{ $s.LICENSE_KEY | quote }} + {{- end }} + {{- if $s.ORGANIZATION_IDS }} + ORGANIZATION_IDS: {{ $s.ORGANIZATION_IDS | quote }} + {{- end }} + + # Object storage credentials (per bucket) + {{- range $k := (list "OBJECT_STORAGE_STA_ACCESS_KEY" "OBJECT_STORAGE_STA_SECRET_KEY" "OBJECT_STORAGE_CCS_ACCESS_KEY" "OBJECT_STORAGE_CCS_SECRET_KEY" "OBJECT_STORAGE_FETCHER_ACCESS_KEY" "OBJECT_STORAGE_FETCHER_SECRET_KEY") }} + {{- if index $s $k }} + {{ $k }}: {{ index $s $k | quote }} + {{- end }} + {{- end }} + + # Per-upstream M2M API keys (on-prem / local fallback) + {{- range $k := (list "FETCHER_API_KEY" "REPORTER_API_KEY" "STA_API_KEY") }} + {{- if index $s $k }} + {{ $k }}: {{ index $s $k | quote }} + {{- end }} + {{- end }} + + # Per-upstream M2M OAuth2 client credentials (single-tenant). The client_id/client_secret + # pair authenticates br-ccs to each upstream (Fetcher / Reporter / STA) via the + # plugin-auth token endpoint. Multi-tenant (SaaS) resolves per-tenant credentials from + # AWS Secrets Manager instead and ignores these. Held as a Secret (never a ConfigMap) so + # the client secrets are not exposed in plain manifests, matching the *_API_KEY convention. + {{- range $k := (list "FETCHER_CLIENT_ID" "FETCHER_CLIENT_SECRET" "REPORTER_CLIENT_ID" "REPORTER_CLIENT_SECRET" "STA_CLIENT_ID" "STA_CLIENT_SECRET") }} + {{- if index $s $k }} + {{ $k }}: {{ index $s $k | quote }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/br-ccs/templates/service.yaml b/charts/br-ccs/templates/service.yaml new file mode 100644 index 000000000..366257905 --- /dev/null +++ b/charts/br-ccs/templates/service.yaml @@ -0,0 +1,22 @@ +{{- if .Values.brCcs.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "br-ccs.fullname" . }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 4 }} + {{- with .Values.brCcs.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.brCcs.service.type }} + ports: + - port: {{ .Values.brCcs.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "br-ccs.selectorLabels" (dict "context" . "name" .Values.brCcs.name) | nindent 4 }} +{{- end }} diff --git a/charts/br-ccs/templates/serviceaccount.yaml b/charts/br-ccs/templates/serviceaccount.yaml new file mode 100644 index 000000000..216c612a3 --- /dev/null +++ b/charts/br-ccs/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if and .Values.brCcs.enabled .Values.brCcs.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "br-ccs.serviceAccountName" . }} + namespace: {{ include "global.namespace" . }} + labels: + {{- include "br-ccs.labels" (dict "context" . "component" .Values.brCcs.name "name" .Values.brCcs.name ) | nindent 4 }} + {{- with .Values.brCcs.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/br-ccs/values-template.yaml b/charts/br-ccs/values-template.yaml new file mode 100644 index 000000000..83efc8371 --- /dev/null +++ b/charts/br-ccs/values-template.yaml @@ -0,0 +1,182 @@ +# Template values for br-ccs deployment. +# Copy this file and fill in the required values for your environment. +# REQUIRED fields must be set before deployment. + +brCcs: + name: br-ccs + enabled: true + replicaCount: 2 + + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: "" # Set your hostname (e.g., br-ccs.example.com) + paths: + - path: / + pathType: Prefix + tls: [] + migrations: + # -- Enable or disable the migrations job + enabled: true + configmap: + # Chart scope: infrastructure connection + multi-tenant toggle + integration + # master switches + object-storage endpoints. Operational tuning (timeouts, + # retries, rate limits, poll cadence, detail-message flags) ships with + # lib-commons / systemplane defaults or via brCcs.extraEnvVars. + + # ================================================================= + # Application identity + # ================================================================= + ENV_NAME: "" # REQUIRED - Environment name (e.g., production, staging) + DEPLOYMENT_MODE: "byoc" # local | byoc | saas (saas enforces TLS on deps) + CCS_RUN_MODE: "all" # manager | worker | all + SERVER_ADDRESS: ":4030" + SERVER_PORT: "4030" + + # ================================================================= + # TLS / proxy — TRUSTED_PROXIES REQUIRED when ENV_NAME=production + # ================================================================= + TLS_TERMINATED_UPSTREAM: "true" + TRUSTED_PROXIES: "" # e.g. 10.0.0.0/8,172.16.0.0/12 + + # ================================================================= + # Multi-tenancy + # ================================================================= + DEFAULT_TENANT_ID: "11111111-1111-1111-1111-111111111111" + MULTI_TENANT_ENABLED: "false" + # When MULTI_TENANT_ENABLED=true, also set: + # MULTI_TENANT_URL: "" + # MULTI_TENANT_REDIS_HOST: "" + + # ================================================================= + # PostgreSQL primary + # POSTGRES_HOST is REQUIRED when the bundled postgresql subchart is + # disabled or external; leave empty to use the bundled subchart Service. + # ================================================================= + POSTGRES_HOST: "" # REQUIRED for external infra (postgresql disabled/external) + POSTGRES_PORT: "5432" + POSTGRES_USER: "br-ccs" + POSTGRES_NAME: "br-ccs" + POSTGRES_SSLMODE: "require" + + # ================================================================= + # Redis/Valkey (app-level) + # REDIS_HOST is REQUIRED when the bundled valkey subchart is disabled + # or external; leave empty to use the bundled subchart Service. + # ================================================================= + REDIS_HOST: "" # REQUIRED for external infra (valkey disabled/external); host:port + + # ================================================================= + # Circuit breaker (prerequisite for object storage) + # ================================================================= + CIRCUIT_BREAKER_ENABLED: "true" + + # ================================================================= + # Integrations (master switches; URLs required when enabled) + # ================================================================= + FETCHER_ENABLED: "false" + # FETCHER_URL: "" + STA_ENABLED: "false" + # STA_BASE_URL: "" + REPORTER_ENABLED: "false" + # REPORTER_URL: "" + + # ================================================================= + # Authentication (inbound) + # ================================================================= + PLUGIN_AUTH_ENABLED: "false" + # PLUGIN_AUTH_HOST: "" # REQUIRED when PLUGIN_AUTH_ENABLED=true + + # ================================================================= + # OpenTelemetry + # ================================================================= + ENABLE_TELEMETRY: "false" + OTEL_EXPORTER_OTLP_ENDPOINT: "" # Set if ENABLE_TELEMETRY=true + + # ================================================================= + # Object storage (per bucket; empty *_BUCKET marks readyz dep skipped) + # ================================================================= + OBJECT_STORAGE_STA_ENDPOINT: "" + OBJECT_STORAGE_STA_BUCKET: "" + OBJECT_STORAGE_CCS_ENDPOINT: "" + OBJECT_STORAGE_CCS_BUCKET: "" + OBJECT_STORAGE_FETCHER_ENDPOINT: "" + OBJECT_STORAGE_FETCHER_BUCKET: "" + + secrets: + # PostgreSQL - REQUIRED + POSTGRES_PASSWORD: "" # REQUIRED - PostgreSQL primary password + + # Redis/Valkey - REQUIRED + REDIS_PASSWORD: "" # REQUIRED - Redis/Valkey password + + # Crypto keys - REQUIRED in production (empty CCS_CRYPTO_MASTER_KEY fails fast at boot) + CCS_CRYPTO_MASTER_KEY: "" # AES-256-GCM master key, 64 hex chars (openssl rand -hex 32) + FETCHER_CRYPTO_KEY: "" # Fetcher snapshot decryption key (base64; = Fetcher APP_ENC_KEY) + + # License (lib-license-go; validated at boot, fail-closed) + # LICENSE_KEY: "" # Lerian license key + # ORGANIZATION_IDS: "" # enforcement scope (e.g. "global") + + # Object storage credentials (set alongside the matching *_BUCKET) + # OBJECT_STORAGE_STA_ACCESS_KEY: "" + # OBJECT_STORAGE_STA_SECRET_KEY: "" + # OBJECT_STORAGE_CCS_ACCESS_KEY: "" + # OBJECT_STORAGE_CCS_SECRET_KEY: "" + # OBJECT_STORAGE_FETCHER_ACCESS_KEY: "" + # OBJECT_STORAGE_FETCHER_SECRET_KEY: "" + + # Per-upstream M2M API keys (on-prem / local literal-Bearer fallback) + # FETCHER_API_KEY: "" + # REPORTER_API_KEY: "" + # STA_API_KEY: "" + + # Per-upstream M2M OAuth2 client credentials (single-tenant; via plugin-auth token endpoint) + # FETCHER_CLIENT_ID: "" + # FETCHER_CLIENT_SECRET: "" + # REPORTER_CLIENT_ID: "" + # REPORTER_CLIENT_SECRET: "" + # STA_CLIENT_ID: "" + # STA_CLIENT_SECRET: "" + + # Multi-tenant (tenant-manager) + # MULTI_TENANT_SERVICE_API_KEY: "" + + # RabbitMQ (when RABBITMQ_ENABLED=true) + # RABBITMQ_DEFAULT_PASS: "" + + useExistingSecret: false + existingSecretName: "" + +# ============================================================================== +# INFRASTRUCTURE DEPENDENCIES +# ============================================================================== +# br-ccs is PostgreSQL-only (no MongoDB): +# +# PostgreSQL: +# - Database: br-ccs +# - User: br-ccs (full privileges on the br-ccs database) +# - Schema: public (default). Multi-tenant mode uses schema-per-tenant via +# search_path; there is no tenant_id column. +# +# Redis/Valkey: +# - Used for: rate limiting, idempotency keys, caching. No pre-config needed. +# +# RabbitMQ (OPTIONAL - event bus, disabled by default): +# - Exchange: events (topic, durable) +# ============================================================================== + +# Disable internal dependencies for external infrastructure +valkey: + enabled: false + external: true + +postgresql: + enabled: false + external: true + +rabbitmq: + enabled: false + external: true diff --git a/charts/br-ccs/values.schema.json b/charts/br-ccs/values.schema.json new file mode 100644 index 000000000..88de85f33 --- /dev/null +++ b/charts/br-ccs/values.schema.json @@ -0,0 +1,973 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "Values schema (generated by productize-chart-env/gen-schema.py)", + "type": "object", + "properties": { + "nameOverride": { + "type": "string", + "default": "br-ccs" + }, + "fullnameOverride": { + "type": "string", + "default": "" + }, + "namespaceOverride": { + "type": "string", + "default": "" + }, + "global": { + "type": "object", + "additionalProperties": true + }, + "brCcs": { + "type": "object", + "properties": { + "readinessProbe": { + "type": "object", + "additionalProperties": true, + "description": "Readiness probe configuration. All fields override chart defaults." + }, + "livenessProbe": { + "type": "object", + "additionalProperties": true, + "description": "Liveness probe configuration. All fields override chart defaults." + }, + "multiTenant": { + "type": "object", + "properties": { + "defaultTenantId": { + "default": "11111111-1111-1111-1111-111111111111" + }, + "enabled": { + "type": "boolean", + "default": false, + "description": "Enable multi-tenancy (MULTI_TENANT_ENABLED)" + } + }, + "additionalProperties": false, + "description": "Multi-tenancy toggle (grouped API for MULTI_TENANT_ENABLED). The tenant-manager" + }, + "datastores": { + "type": "object", + "additionalProperties": true, + "description": "Dedicated datastore mask for THIS service (see global.datastores for the shared" + }, + "serviceDiscovery": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": false, + "description": "Enable Consul service discovery (SD_ENABLED)" + } + }, + "additionalProperties": false, + "description": "Service discovery toggle (grouped API for SD_ENABLED). Infra comes from" + }, + "streaming": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": false, + "description": "Enable lib-streaming (STREAMING_ENABLED)" + } + }, + "additionalProperties": false, + "description": "Streaming toggle (grouped API for STREAMING_ENABLED). Broker/SASL infra comes" + }, + "name": { + "default": "br-ccs", + "description": "Service name" + }, + "enabled": { + "default": true, + "description": "Enable or disable the br-ccs service" + }, + "replicaCount": { + "default": 2, + "description": "Number of replicas for the br-ccs service" + }, + "revisionHistoryLimit": { + "default": 10, + "description": "Number of old ReplicaSets to retain for deployment rollback" + }, + "image": { + "type": "object", + "properties": { + "repository": { + "default": "ghcr.io/lerianstudio/br-ccs", + "description": "Repository for the br-ccs service container image" + }, + "pullPolicy": { + "default": "IfNotPresent", + "description": "Image pull policy" + }, + "tag": { + "default": "1.0.0", + "description": "Image tag used for deployment" + } + }, + "additionalProperties": true + }, + "migrations": { + "type": "object", + "description": "PostgreSQL migrations job (Helm hook; ArgoCD PreSync for external PG,", + "properties": { + "enabled": { + "default": true, + "description": "Enable or disable the migrations job." + }, + "useExistingSecret": { + "default": false, + "description": "Optional. When true, migrations read POSTGRES_PASSWORD from a pre-existing" + }, + "existingSecretName": { + "default": "", + "description": "Name of the pre-existing Secret containing POSTGRES_PASSWORD" + }, + "path": { + "default": "/migrations", + "description": "MIGRATIONS_PATH inside the migrations image (embedded at /migrations)." + }, + "image": { + "type": "object", + "properties": { + "repository": { + "default": "ghcr.io/lerianstudio/br-ccs-migrations", + "description": "Repository for the migrations runner image" + }, + "tag": { + "default": "", + "description": "Tag for the migrations image. Defaults to the app image tag" + }, + "pullPolicy": { + "default": "IfNotPresent", + "description": "Image pull policy" + } + }, + "additionalProperties": true + }, + "backoffLimit": { + "default": 3, + "description": "Maximum number of retries before the Job is considered failed" + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "default": "250m" + }, + "memory": { + "default": "256Mi" + } + }, + "additionalProperties": true + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "default": "50m" + }, + "memory": { + "default": "64Mi" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "imagePullSecrets": { + "type": "array", + "description": "Secrets for pulling images from a private registry" + }, + "nameOverride": { + "default": "", + "description": "Overrides the default generated name by Helm" + }, + "fullnameOverride": { + "default": "", + "description": "Overrides the full name generated by Helm" + }, + "podAnnotations": { + "type": "object", + "additionalProperties": true, + "description": "Pod annotations for additional metadata" + }, + "podSecurityContext": { + "type": "object", + "additionalProperties": true + }, + "securityContext": { + "type": "object", + "properties": { + "runAsGroup": { + "default": 1000, + "description": "Defines the group ID for the user running the process inside the container" + }, + "runAsUser": { + "default": 1000, + "description": "Defines the user ID for the process running inside the container" + }, + "runAsNonRoot": { + "default": true, + "description": "Ensures the process does not run as root" + }, + "capabilities": { + "type": "object", + "properties": { + "drop": { + "type": "array" + } + }, + "additionalProperties": true + }, + "allowPrivilegeEscalation": { + "default": false + }, + "readOnlyRootFilesystem": { + "default": true, + "description": "Defines the root filesystem as read-only" + }, + "seccompProfile": { + "type": "object", + "properties": { + "type": { + "default": "RuntimeDefault" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "pdb": { + "type": "object", + "description": "PodDisruptionBudget configuration", + "properties": { + "enabled": { + "default": true, + "description": "Enable or disable PodDisruptionBudget" + }, + "minAvailable": { + "default": 1, + "description": "Minimum number of available pods" + }, + "maxUnavailable": { + "default": 1, + "description": "Maximum number of unavailable pods" + }, + "annotations": { + "type": "object", + "additionalProperties": true, + "description": "Annotations for the PodDisruptionBudget" + } + }, + "additionalProperties": true + }, + "deploymentUpdate": { + "type": "object", + "description": "Deployment update strategy", + "properties": { + "type": { + "default": "RollingUpdate", + "description": "Type of deployment strategy" + }, + "maxSurge": { + "default": "100%", + "description": "Maximum number of pods that can be created over the desired number of pods" + }, + "maxUnavailable": { + "default": 0, + "description": "Maximum number of pods that can be unavailable during the update" + } + }, + "additionalProperties": true + }, + "service": { + "type": "object", + "properties": { + "type": { + "default": "ClusterIP", + "description": "Kubernetes service type" + }, + "port": { + "default": 4030, + "description": "Port for the HTTP API" + }, + "annotations": { + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "ingress": { + "type": "object", + "properties": { + "enabled": { + "default": false, + "description": "Enable or disable ingress" + }, + "className": { + "default": "", + "description": "Ingress class name" + }, + "annotations": { + "type": "object", + "additionalProperties": true, + "description": "Additional ingress annotations" + }, + "hosts": { + "type": "array" + }, + "tls": { + "type": "array", + "description": "TLS configuration for ingress" + } + }, + "additionalProperties": true + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "description": "CPU and memory limits for pods", + "properties": { + "cpu": { + "default": "1500m" + }, + "memory": { + "default": "512Mi" + } + }, + "additionalProperties": true + }, + "requests": { + "type": "object", + "description": "Minimum CPU and memory requests", + "properties": { + "cpu": { + "default": "500m" + }, + "memory": { + "default": "256Mi" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "autoscaling": { + "type": "object", + "properties": { + "enabled": { + "default": true, + "description": "Enable or disable horizontal pod autoscaling" + }, + "minReplicas": { + "default": 2, + "description": "Minimum number of replicas" + }, + "maxReplicas": { + "default": 5, + "description": "Maximum number of replicas" + }, + "targetCPUUtilizationPercentage": { + "default": 80, + "description": "Target CPU utilization percentage for autoscaling" + }, + "targetMemoryUtilizationPercentage": { + "default": 80 + } + }, + "additionalProperties": true + }, + "nodeSelector": { + "type": "object", + "additionalProperties": true, + "description": "Node selector for scheduling pods on specific nodes" + }, + "tolerations": { + "type": "array", + "description": "Tolerations for scheduling on tainted nodes" + }, + "affinity": { + "type": "object", + "additionalProperties": true, + "description": "Affinity rules for pod scheduling" + }, + "hostAliases": { + "type": "array", + "description": "Host aliases for custom DNS resolution inside the pod" + }, + "configmap": { + "type": "object", + "additionalProperties": true, + "description": "Raw ConfigMap env-var escape hatch. The clean, grouped API lives in the" + }, + "app": { + "type": "object", + "properties": { + "deploymentMode": { + "default": "byoc" + }, + "env": { + "default": "production" + }, + "logLevel": { + "default": "info" + }, + "name": { + "default": "br-ccs" + }, + "runMode": { + "default": "all" + }, + "version": { + "default": "" + } + }, + "additionalProperties": false, + "description": "Application identity: name / env / version / logLevel / deploymentMode / runMode" + }, + "server": { + "type": "object", + "properties": { + "address": { + "default": ":4030" + }, + "bodyLimitBytes": { + "default": "104857600" + }, + "grpcPort": { + "default": "7001" + }, + "port": { + "default": "4030" + }, + "tlsTerminatedUpstream": { + "default": "true" + }, + "trustedProxies": { + "default": "" + } + }, + "additionalProperties": false, + "description": "HTTP server: address / port / grpcPort / bodyLimitBytes / tlsTerminatedUpstream / trustedProxies" + }, + "cors": { + "type": "object", + "properties": { + "allowCredentials": { + "default": "false" + }, + "allowedHeaders": { + "default": "Authorization,Content-Type,X-Tenant-ID,X-Idempotency,X-Request-ID" + }, + "allowedMethods": { + "default": "GET,POST,PUT,PATCH,DELETE,OPTIONS" + }, + "allowedOrigins": { + "default": "" + }, + "exposeHeaders": { + "default": "" + } + }, + "additionalProperties": false, + "description": "CORS: allowedOrigins / allowedMethods / allowedHeaders / exposeHeaders / allowCredentials" + }, + "postgres": { + "type": "object", + "properties": { + "connMaxIdleTimeMins": { + "default": "5" + }, + "connMaxLifetimeMins": { + "default": "30" + }, + "connectTimeoutSec": { + "default": "10" + }, + "infraConnectTimeoutSec": { + "default": "30" + }, + "maxIdleConns": { + "default": "5" + }, + "maxOpenConns": { + "default": "25" + }, + "migrationsPath": { + "default": "migrations" + }, + "name": { + "default": "br-ccs" + }, + "systemplaneEnabled": { + "default": "false" + } + }, + "additionalProperties": false, + "description": "PostgreSQL tuning (host/port/user/ssl come from datastores): name / migrationsPath /" + }, + "redis": { + "type": "object", + "properties": { + "db": { + "default": "0" + }, + "dialTimeout": { + "default": "5" + }, + "maxRetries": { + "default": "3" + }, + "maxRetryBackoff": { + "default": "1" + }, + "minIdleConns": { + "default": "2" + }, + "minRetryBackoff": { + "default": "8" + }, + "poolSize": { + "default": "10" + }, + "poolTimeout": { + "default": "2" + }, + "protocol": { + "default": "3" + }, + "readTimeout": { + "default": "3" + }, + "writeTimeout": { + "default": "3" + } + }, + "additionalProperties": false, + "description": "Redis tuning (host from datastores): db / protocol / poolSize / minIdleConns / readTimeout /" + }, + "broker": { + "type": "object", + "properties": { + "allowInsecureHealthCheck": { + "default": "false" + }, + "allowInsecureTls": { + "default": "false" + }, + "circuitBreakerEnabled": { + "default": "true" + }, + "enabled": { + "default": "false" + }, + "exchange": { + "default": "events" + }, + "portHost": { + "default": "15672" + }, + "publisherConfirmTimeoutMs": { + "default": "5000" + }, + "publisherMaxRecoveries": { + "default": "10" + }, + "publisherRecoveryInitialMs": { + "default": "1000" + }, + "publisherRecoveryMaxMs": { + "default": "30000" + }, + "requireHealthAllowedHosts": { + "default": "false" + }, + "staDlqTtlSeconds": { + "default": "30" + }, + "vhost": { + "default": "/" + } + }, + "additionalProperties": false, + "description": "RabbitMQ (host/port/user from datastores): enabled / circuitBreakerEnabled / portHost / vhost /" + }, + "outbox": { + "type": "object", + "properties": { + "allowEmptyTenant": { + "default": "true" + }, + "batchSize": { + "default": "50" + }, + "dispatchIntervalSec": { + "default": "2" + }, + "enabled": { + "default": "false" + }, + "includeTenantMetrics": { + "default": "false" + }, + "maxDispatchAttempts": { + "default": "10" + }, + "maxFailedPerBatch": { + "default": "25" + }, + "processingTimeoutSec": { + "default": "600" + }, + "publishBackoffMs": { + "default": "200" + }, + "publishMaxAttempts": { + "default": "3" + }, + "retryWindowSec": { + "default": "300" + }, + "tableName": { + "default": "outbox_events" + } + }, + "additionalProperties": false, + "description": "Outbox: enabled / tableName / dispatchIntervalSec / batchSize / publishMaxAttempts /" + }, + "fetcher": { + "type": "object", + "properties": { + "ccsOutboxBatchSize": { + "default": "50" + }, + "ccsOutboxDispatchIntervalMs": { + "default": "500" + }, + "ccsOutboxDlqRoutingKey": { + "default": "ccs.outbox.dlq" + }, + "ccsOutboxMaxAttempts": { + "default": "10" + }, + "enabled": { + "default": "false" + }, + "httpTimeoutSec": { + "default": "30" + }, + "maxRedeliveries": { + "default": "5" + }, + "retryBackoffMs": { + "default": "5000" + } + }, + "additionalProperties": false, + "description": "Fetcher integration: enabled / httpTimeoutSec / ccsOutboxDispatchIntervalMs /" + }, + "sta": { + "type": "object", + "properties": { + "enabled": { + "default": "false" + }, + "httpTimeoutSec": { + "default": "30" + } + }, + "additionalProperties": false, + "description": "STA integration: enabled / httpTimeoutSec" + }, + "reporter": { + "type": "object", + "properties": { + "enabled": { + "default": "false" + }, + "httpTimeoutSec": { + "default": "30" + } + }, + "additionalProperties": false, + "description": "Reporter integration: enabled / httpTimeoutSec" + }, + "swagger": { + "type": "object", + "properties": { + "basePath": { + "default": "/" + }, + "enabled": { + "default": "true" + }, + "leftDelim": { + "default": "{{" + }, + "rightDelim": { + "default": "}}" + }, + "title": { + "default": "br-ccs" + }, + "version": { + "default": "" + } + }, + "additionalProperties": false, + "description": "Swagger: enabled / title / version / basePath / leftDelim / rightDelim" + }, + "rateLimit": { + "type": "object", + "properties": { + "aggressiveMax": { + "default": "100" + }, + "aggressiveWindowSec": { + "default": "60" + }, + "allowFailOpen": { + "default": "" + }, + "dispatchMax": { + "default": "30" + }, + "dispatchWindowSec": { + "default": "60" + }, + "enabled": { + "default": "true" + }, + "exportMax": { + "default": "60" + }, + "exportWindowSec": { + "default": "60" + }, + "max": { + "default": "500" + }, + "relaxedMax": { + "default": "1000" + }, + "relaxedWindowSec": { + "default": "60" + }, + "windowSec": { + "default": "60" + } + }, + "additionalProperties": false, + "description": "Rate limiting: enabled / max / windowSec / aggressiveMax / aggressiveWindowSec /" + }, + "pagination": { + "type": "object", + "properties": { + "limit": { + "default": "100" + }, + "monthDateRange": { + "default": "3" + } + }, + "additionalProperties": false, + "description": "Pagination: limit / monthDateRange" + }, + "observability": { + "type": "object", + "properties": { + "dbMetricsIntervalSec": { + "default": "15" + }, + "idempotencyRetryWindowSec": { + "default": "300" + } + }, + "additionalProperties": false, + "description": "Observability: dbMetricsIntervalSec / idempotencyRetryWindowSec" + }, + "m2m": { + "type": "object", + "properties": { + "awsRegion": { + "default": "us-east-1" + }, + "credentialCacheTtlSec": { + "default": "300" + } + }, + "additionalProperties": false, + "description": "M2M (non-secret): credentialCacheTtlSec / awsRegion" + }, + "readiness": { + "type": "object", + "properties": { + "depSlowThresholdMs": { + "default": "500" + }, + "probeTimeoutSec": { + "default": "5" + }, + "startupInitialDelayMs": { + "default": "1000" + }, + "startupMaxDelayMs": { + "default": "30000" + }, + "startupMaxDurationSec": { + "default": "120" + } + }, + "additionalProperties": false, + "description": "Readiness / startup probes: probeTimeoutSec / depSlowThresholdMs / startupMaxDurationSec /" + }, + "shutdown": { + "type": "object", + "properties": { + "drainGracePeriodSec": { + "default": "15" + }, + "totalTimeoutSec": { + "default": "30" + } + }, + "additionalProperties": false, + "description": "Graceful shutdown: drainGracePeriodSec / totalTimeoutSec" + }, + "objectStorage": { + "type": "object", + "properties": { + "accs009RetentionDays": { + "default": "30" + }, + "ccsBucket": { + "type": "string", + "default": "lerian-ccs" + }, + "ccsDisableSsl": { + "default": "true" + }, + "ccsEndpoint": { + "default": "" + }, + "ccsRegion": { + "default": "us-east-1" + }, + "ccsUsePathStyle": { + "default": "true" + }, + "fetcherBucket": { + "type": "string", + "default": "lerian-fetcher" + }, + "fetcherDisableSsl": { + "default": "true" + }, + "fetcherEndpoint": { + "default": "" + }, + "fetcherRegion": { + "default": "us-east-1" + }, + "fetcherUsePathStyle": { + "default": "true" + }, + "outboundRetentionDays": { + "default": "30" + }, + "seaweedfsMasterPort": { + "default": "9343" + }, + "seaweedfsS3Port": { + "default": "8342" + }, + "staBucket": { + "type": "string", + "default": "sta-files" + }, + "staDisableSsl": { + "default": "true" + }, + "staEndpoint": { + "default": "" + }, + "staRegion": { + "default": "us-east-1" + }, + "staUsePathStyle": { + "default": "true" + } + }, + "additionalProperties": false, + "description": "Object storage (non-secret): seaweedfsS3Port / seaweedfsMasterPort / {sta,ccs,fetcher}{Endpoint," + }, + "secrets": { + "type": "object", + "additionalProperties": true, + "description": "Secrets for storing sensitive data. Provide real values via an existing" + }, + "useExistingSecret": { + "default": false, + "description": "Existing secrets name" + }, + "existingSecretName": { + "default": "" + }, + "extraEnvVars": { + "type": "object", + "additionalProperties": true, + "description": "Extra environment variables (map of key: value pairs) — escape hatch for" + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "default": true, + "description": "Specifies whether a ServiceAccount should be created" + }, + "annotations": { + "type": "object", + "additionalProperties": true, + "description": "Annotations for the ServiceAccount" + }, + "name": { + "default": "", + "description": "Name of the service account" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "valkey": { + "type": "object", + "additionalProperties": true + }, + "postgresql": { + "type": "object", + "additionalProperties": true + }, + "rabbitmq": { + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": true +} diff --git a/charts/br-ccs/values.yaml b/charts/br-ccs/values.yaml new file mode 100644 index 000000000..87154e8d3 --- /dev/null +++ b/charts/br-ccs/values.yaml @@ -0,0 +1,531 @@ +# Default values for br-ccs. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +# +# Env-var coverage note: every variable in the application's config/.env.example +# is mapped either into brCcs.configmap (non-secret) or brCcs.secrets +# (credentials / keys), or is provided by a bundled subchart. Operational tuning +# knobs that ship with lib-commons / systemplane defaults are exposed as +# optional entries and can be set under brCcs.configmap as needed. +nameOverride: "br-ccs" +fullnameOverride: "" +namespaceOverride: "" +global: + # -- Bootstrap job for external PostgreSQL: creates databases, roles, and grants privileges + externalPostgresDefinitions: + # -- Enable or disable the PostgreSQL bootstrap job + enabled: false + # -- PostgreSQL connection settings + connection: + # -- PostgreSQL host + host: "br-ccs-postgresql-primary" + # -- PostgreSQL port + port: "5432" + # -- Admin credentials for PostgreSQL + postgresAdminLogin: + useExistingSecret: + # -- Name of existing secret containing DB_USER_ADMIN and DB_ADMIN_PASSWORD keys + name: "" + # -- Admin username (ignored if useExistingSecret.name is set) + username: "postgres" + # -- Admin password (ignored if useExistingSecret.name is set) + password: "" + # -- Credentials for the br-ccs role created by the job + brCcsCredentials: + useExistingSecret: + # -- Name of existing secret containing DB_PASSWORD_BR_CCS key + name: "" + # -- Password for the br-ccs role (ignored if useExistingSecret.name is set) + password: "" + # -- Bootstrap job for external RabbitMQ: creates users, vhosts, and permissions + externalRabbitmqDefinitions: + # -- Enable or disable the RabbitMQ bootstrap job + enabled: false + # -- RabbitMQ connection settings + connection: + # -- RabbitMQ protocol (http or https) + protocol: "http" + # -- RabbitMQ host (management API endpoint) + host: "br-ccs-rabbitmq" + # -- RabbitMQ HTTP management port + port: "15672" + # -- RabbitMQ AMQP port (for connectivity check) + portAmqp: "5672" + # -- Skip TLS verification for self-signed certificates (not recommended for production) + skipTlsVerify: false + # -- Admin credentials for RabbitMQ management API + rabbitmqAdminLogin: + useExistingSecret: + # -- Name of existing secret containing RABBITMQ_ADMIN_USER and RABBITMQ_ADMIN_PASS keys + name: "" + # -- Admin username (ignored if useExistingSecret.name is set) + username: "admin" + # -- Admin password (ignored if useExistingSecret.name is set) + password: "" + # -- Credentials for the br-ccs user created by the job + brCcsCredentials: + useExistingSecret: + # -- Name of existing secret containing RABBITMQ_BR_CCS_PASS key + name: "" + # -- Password for the br-ccs user (ignored if useExistingSecret.name is set) + password: "" + # -- Env-wide observability, consumed by lerian-common.otel.env. Declare once at + # the umbrella level; a component brCcs.configmap. still overrides per-service. + # Precedence: brCcs.configmap. > global.observability. > chart default. + observability: + # -- Enable telemetry export (ENABLE_TELEMETRY). Unset → chart default "false". + enabled: false + # -- OTLP collector endpoint (OTEL_EXPORTER_OTLP_ENDPOINT). Unset → chart default "". + otlpEndpoint: "" + # -- Deployment environment tag (OTEL_RESOURCE_DEPLOYMENT_ENVIRONMENT). Unset → "production". + deploymentEnvironment: "production" + # -- Env-wide datastore mask, consumed by lerian-common.datastore.value. Declare a + # SHARED instance once here; a DEDICATED per-service instance goes under + # brCcs.datastores; a component brCcs.configmap. still overrides everything. + # Precedence: brCcs.configmap. > brCcs.datastores.. + # > global.datastores.. > chart default. + # Leave empty ({}) to keep the bundled-subchart / native-key defaults. + datastores: {} + # postgres: + # host: "" # POSTGRES_HOST (host only; required when the subchart is off) + # port: "5432" # POSTGRES_PORT + # user: "br-ccs" # POSTGRES_USER + # ssl: "require" # POSTGRES_SSLMODE + # replicaHost: "" # POSTGRES_REPLICA_HOST (enables the replica block when set) + # redis: + # host: "" # REDIS_HOST (host:port; required when the subchart is off) + # broker: + # host: "" # RABBITMQ_HOST + # port: "5672" # RABBITMQ_PORT_AMQP + # user: "br-ccs" # RABBITMQ_DEFAULT_USER + # -- Env-wide multi-tenant infra (tenant-manager URL + its Redis), consumed by + # lerian-common.multiTenant.env. Declare once; a component brCcs.configmap. + # still overrides. Only consulted when multi-tenancy is enabled (see + # brCcs.multiTenant.enabled). Leave empty ({}) for the native-key / chart defaults. + multiTenant: {} + # url: "" # MULTI_TENANT_URL (required when MT enabled) + # redisHost: "" # MULTI_TENANT_REDIS_HOST (required when MT enabled) + # redisPort: "6379" # MULTI_TENANT_REDIS_PORT + # redisTls: "false" # MULTI_TENANT_REDIS_TLS + # -- Env-wide service discovery (Consul), consumed by lerian-common.serviceDiscovery.env. + # Only used when SD is enabled (brCcs.serviceDiscovery.enabled). A component + # brCcs.configmap.SD_* still overrides. Leave empty ({}) for the chart defaults. + serviceDiscovery: {} + # address: "localhost:8500" # SD_ADDRESS + # tls: "false" # SD_TLS + # workload: "" # SD_WORKLOAD (per-env isolation) + # -- Env-wide streaming (lib-streaming → Kafka), consumed by lerian-common.streaming.env. + # Only used when streaming is enabled (brCcs.streaming.enabled). SASL/broker contract; + # STREAMING_SASL_PASSWORD is a secret. Leave empty ({}) for the chart defaults. + streaming: {} + # brokers: "" # STREAMING_BROKERS (required when enabled) + # tlsEnabled: "false" # STREAMING_TLS_ENABLED + # saslMechanism: "" # STREAMING_SASL_MECHANISM (PLAIN|SCRAM-SHA-256|SCRAM-SHA-512) + # saslUsername: "" # STREAMING_SASL_USERNAME +brCcs: + # -- Readiness probe configuration. All fields override chart defaults. + readinessProbe: {} + # -- Liveness probe configuration. All fields override chart defaults. + livenessProbe: {} + # -- Multi-tenancy toggle (grouped API for MULTI_TENANT_ENABLED). The tenant-manager + # URL + Redis infra come from global.multiTenant (or brCcs.configmap.MULTI_TENANT_*). + # Precedence for the toggle: brCcs.configmap.MULTI_TENANT_ENABLED > brCcs.multiTenant.enabled > "false". + multiTenant: + # -- Enable multi-tenancy (MULTI_TENANT_ENABLED) + enabled: false + # -- Dedicated datastore mask for THIS service (see global.datastores for the shared + # form + precedence). Same fields as global.datastores; wins over global, loses to + # brCcs.configmap.. Leave empty ({}) to keep the bundled-subchart / native defaults. + datastores: {} + # -- Service discovery toggle (grouped API for SD_ENABLED). Infra comes from + # global.serviceDiscovery (or brCcs.configmap.SD_*). SD_TOKEN (ACL) is a secret. + serviceDiscovery: + # -- Enable Consul service discovery (SD_ENABLED) + enabled: false + # -- Streaming toggle (grouped API for STREAMING_ENABLED). Broker/SASL infra comes + # from global.streaming (or brCcs.configmap.STREAMING_*). SASL password is a secret. + streaming: + # -- Enable lib-streaming (STREAMING_ENABLED) + enabled: false + # -- Service name + name: br-ccs + # -- Enable or disable the br-ccs service + enabled: true + # -- Number of replicas for the br-ccs service + replicaCount: 2 + # -- Number of old ReplicaSets to retain for deployment rollback + revisionHistoryLimit: 10 + image: + # -- Repository for the br-ccs service container image + repository: ghcr.io/lerianstudio/br-ccs + # -- Image pull policy + pullPolicy: IfNotPresent + # -- Image tag used for deployment + tag: "1.0.0" + # -- PostgreSQL migrations job (Helm hook; ArgoCD PreSync for external PG, + # PostSync for the bundled subchart). Runs the dedicated br-ccs-migrations + # image (golang-migrate runner; the app image never migrates). The + # chart-managed Secret is rendered as an earlier hook so it exists before the + # migration hook — migrations run against the chart-managed Secret with NO + # pre-existing Secret required. Set useExistingSecret only to read + # POSTGRES_PASSWORD from an operator-provisioned Secret instead. + migrations: + # -- Enable or disable the migrations job. + enabled: true + # -- Optional. When true, migrations read POSTGRES_PASSWORD from a pre-existing + # Secret (existingSecretName) instead of the chart-managed Secret hook. + useExistingSecret: false + # -- Name of the pre-existing Secret containing POSTGRES_PASSWORD + # (only used when useExistingSecret=true). + existingSecretName: "" + # -- MIGRATIONS_PATH inside the migrations image (embedded at /migrations). + path: "/migrations" + image: + # -- Repository for the migrations runner image + repository: ghcr.io/lerianstudio/br-ccs-migrations + # -- Tag for the migrations image. Defaults to the app image tag + # (brCcs.image.tag, or the chart appVersion) when left empty. + tag: "" + # -- Image pull policy + pullPolicy: IfNotPresent + # -- Maximum number of retries before the Job is considered failed + backoffLimit: 3 + resources: + limits: + cpu: 250m + memory: 256Mi + 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 + allowPrivilegeEscalation: false + # -- Defines the root filesystem as read-only + readOnlyRootFilesystem: true + seccompProfile: + type: RuntimeDefault + # -- PodDisruptionBudget configuration + pdb: + # -- Enable or disable PodDisruptionBudget + enabled: true + # -- Minimum number of available pods + minAvailable: 1 + # -- Maximum number of unavailable pods + maxUnavailable: 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: 100% + # -- Maximum number of pods that can be unavailable during the update + maxUnavailable: 0 + service: + # -- Kubernetes service type + type: ClusterIP + # -- Port for the HTTP API + port: 4030 + annotations: {} + 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: 1500m + memory: 512Mi + # -- Minimum CPU and memory requests + requests: + cpu: 500m + memory: 256Mi + autoscaling: + # -- Enable or disable horizontal pod autoscaling + enabled: true + # -- Minimum number of replicas + minReplicas: 2 + # -- 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: {} + # -- Host aliases for custom DNS resolution inside the pod + hostAliases: [] + # -- Raw ConfigMap env-var escape hatch. The clean, grouped API lives in the + # brCcs. blocks below (server, cors, postgres, redis, broker, outbox, + # fetcher, sta, reporter, swagger, rateLimit, pagination, observability, m2m, + # readiness, shutdown, objectStorage) — each key resolves via cfgValue: + # brCcs.configmap. (here) > brCcs.. > chart default. + # Set a raw NATIVE_KEY here only to override a value not exposed as a grouped + # field, or to pass an optional/opt-in key (e.g. SERVER_TLS_CERT_FILE, + # RABBITMQ_QUEUE, FETCHER_URL, *_OAUTH2_*, CCS_DETAIL_*). Defaults live in the + # template, so leaving this {} renders the documented defaults. + # @default -- templates/configmap.yaml + configmap: {} + # -- Application identity: name / env / version / logLevel / deploymentMode / runMode + app: {} + # -- HTTP server: address / port / grpcPort / bodyLimitBytes / tlsTerminatedUpstream / trustedProxies + server: {} + # -- CORS: allowedOrigins / allowedMethods / allowedHeaders / exposeHeaders / allowCredentials + cors: {} + # -- PostgreSQL tuning (host/port/user/ssl come from datastores): name / migrationsPath / + # systemplaneEnabled / maxOpenConns / maxIdleConns / connMaxLifetimeMins / connMaxIdleTimeMins / + # connectTimeoutSec / infraConnectTimeoutSec + postgres: {} + # -- Redis tuning (host from datastores): db / protocol / poolSize / minIdleConns / readTimeout / + # writeTimeout / dialTimeout / poolTimeout / maxRetries / minRetryBackoff / maxRetryBackoff + redis: {} + # -- RabbitMQ (host/port/user from datastores): enabled / circuitBreakerEnabled / portHost / vhost / + # exchange / requireHealthAllowedHosts / allowInsecureHealthCheck / allowInsecureTls / + # publisherConfirmTimeoutMs / publisherRecoveryInitialMs / publisherRecoveryMaxMs / + # publisherMaxRecoveries / staDlqTtlSeconds + broker: {} + # -- Outbox: enabled / tableName / dispatchIntervalSec / batchSize / publishMaxAttempts / + # publishBackoffMs / retryWindowSec / maxDispatchAttempts / processingTimeoutSec / + # maxFailedPerBatch / includeTenantMetrics / allowEmptyTenant + outbox: {} + # -- Fetcher integration: enabled / httpTimeoutSec / ccsOutboxDispatchIntervalMs / + # ccsOutboxBatchSize / ccsOutboxMaxAttempts / ccsOutboxDlqRoutingKey + fetcher: {} + # -- STA integration: enabled / httpTimeoutSec + sta: {} + # -- Reporter integration: enabled / httpTimeoutSec + reporter: {} + # -- Swagger: enabled / title / version / basePath / leftDelim / rightDelim + swagger: {} + # -- Rate limiting: enabled / max / windowSec / aggressiveMax / aggressiveWindowSec / + # relaxedMax / relaxedWindowSec / exportMax / exportWindowSec / dispatchMax / + # dispatchWindowSec / allowFailOpen + rateLimit: {} + # -- Pagination: limit / monthDateRange + pagination: {} + # -- Observability: dbMetricsIntervalSec / idempotencyRetryWindowSec + observability: {} + # -- M2M (non-secret): credentialCacheTtlSec / awsRegion + m2m: {} + # -- Readiness / startup probes: probeTimeoutSec / depSlowThresholdMs / startupMaxDurationSec / + # startupInitialDelayMs / startupMaxDelayMs + readiness: {} + # -- Graceful shutdown: drainGracePeriodSec / totalTimeoutSec + shutdown: {} + # -- Object storage (non-secret): seaweedfsS3Port / seaweedfsMasterPort / {sta,ccs,fetcher}{Endpoint, + # Region,Bucket,UsePathStyle,DisableSsl} / outboundRetentionDays / accs009RetentionDays + objectStorage: + staBucket: "sta-files" + ccsBucket: "lerian-ccs" + fetcherBucket: "lerian-fetcher" + + # -- Secrets for storing sensitive data. Provide real values via an existing + # Secret (useExistingSecret) or a secrets manager — NEVER commit real values. + # @default -- templates/secrets.yaml + secrets: + # PostgreSQL credentials + POSTGRES_PASSWORD: "" + # POSTGRES_REPLICA_PASSWORD: "" + + # Redis / Valkey password + REDIS_PASSWORD: "" + # REDIS_CA_CERT: "" + + # Multi-tenant (tenant-manager) secrets + # MULTI_TENANT_SERVICE_API_KEY: "" + # MULTI_TENANT_REDIS_PASSWORD: "" + # MULTI_TENANT_REDIS_CA_CERT: "" + + # RabbitMQ credentials (when RABBITMQ_ENABLED=true) + # RABBITMQ_DEFAULT_PASS: "" + # RABBITMQ_URL — full amqp:// dial string (embeds the password; any '@' in + # the password must be percent-encoded as %40). When set, the app dials via + # this instead of assembling from RABBITMQ_HOST/PORT/USER/PASS. + # RABBITMQ_URL: "" + + # Crypto keys (REQUIRED in production) + # CCS_CRYPTO_MASTER_KEY — AES-256-GCM master key, 64 hex chars (openssl rand -hex 32). + # FETCHER_CRYPTO_KEY — Fetcher snapshot decryption key (base64; same as Fetcher APP_ENC_KEY). + CCS_CRYPTO_MASTER_KEY: "" + FETCHER_CRYPTO_KEY: "" + # STA_EVENT_HMAC_MASTER_KEYS — STA business-event HMAC master-key catalogue, + # format "v1:,v2:" (hex per version; must match the key/version the + # br-sta business publisher signs with). Only consumed when + # STA_EVENT_HMAC_VERIFY_ENABLED=true; leave empty while verification is off. + # STA_EVENT_HMAC_MASTER_KEYS: "" + + # License (lib-license-go) — validated at boot against the Lerian license + # gateway (fail-closed). ORGANIZATION_IDS selects enforcement scope (e.g. + # "global"). Held as a Secret; wire via a secrets manager or existing Secret. + # LICENSE_KEY: "" + # ORGANIZATION_IDS: "" + + # Object storage credentials (per bucket) + OBJECT_STORAGE_STA_ACCESS_KEY: "" + OBJECT_STORAGE_STA_SECRET_KEY: "" + OBJECT_STORAGE_CCS_ACCESS_KEY: "" + OBJECT_STORAGE_CCS_SECRET_KEY: "" + OBJECT_STORAGE_FETCHER_ACCESS_KEY: "" + OBJECT_STORAGE_FETCHER_SECRET_KEY: "" + + # Per-upstream M2M API keys (on-prem / local literal-Bearer fallback) + # FETCHER_API_KEY: "" + # REPORTER_API_KEY: "" + # STA_API_KEY: "" + + # Per-upstream M2M OAuth2 client credentials (single-tenant) — the client_id/client_secret + # pair that authenticates br-ccs to each upstream (Fetcher / Reporter / STA) via + # the plugin-auth token endpoint. Multi-tenant (SaaS) resolves per-tenant credentials + # from AWS Secrets Manager instead. Held as a Secret; wire via a secrets manager or + # existing Secret. + # FETCHER_CLIENT_ID: "" + # FETCHER_CLIENT_SECRET: "" + # REPORTER_CLIENT_ID: "" + # REPORTER_CLIENT_SECRET: "" + # STA_CLIENT_ID: "" + # STA_CLIENT_SECRET: "" + + # -- Existing secrets name + useExistingSecret: false + existingSecretName: "" + # -- Extra environment variables (map of key: value pairs) — escape hatch for + # optional knobs not modeled above (e.g. RELATIONSHIP_SOURCE_*, CCS_REPORTER_POLL_*). + extraEnvVars: {} + serviceAccount: + # -- Specifies whether a ServiceAccount should be created + create: true + # -- Annotations for the ServiceAccount + annotations: {} + # -- Name of the service account + # @default -- `br-ccs.fullname` + name: "" +# Valkey (Redis-compatible) configuration +valkey: + enabled: true + global: + security: + allowInsecureImages: false + image: + repository: bitnami/valkey + tag: "8.0.2" + external: false + architecture: standalone + auth: + enabled: true + password: "" + primary: + kind: Deployment + resourcesPreset: "medium" + persistence: + enabled: false + extraFlags: + - "--maxmemory 640mb" + - "--maxmemory-policy allkeys-lru" + - "--hz 100" + - "--maxclients 10000" +# PostgreSQL configuration +postgresql: + enabled: true + global: + security: + allowInsecureImages: false + image: + repository: bitnami/postgresql + tag: "17.4.0" + external: false + architecture: standalone + auth: + enabled: true + enablePostgresUser: true + postgresPassword: "" + username: "br-ccs" + password: "" + database: "br-ccs" + primary: + persistence: + size: 8Gi + resourcesPreset: large + extraEnvVars: + - name: POSTGRESQL_MAX_CONNECTIONS + value: "500" + - name: POSTGRESQL_TCP_KEEPALIVES_IDLE + value: "30" + - name: POSTGRESQL_TCP_KEEPALIVES_INTERVAL + value: "10" + - name: POSTGRESQL_TCP_KEEPALIVES_COUNT + value: "5" +# RabbitMQ configuration (optional - disabled by default) +rabbitmq: + enabled: false + image: + tag: "3.13.6" + persistence: + size: 8Gi + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + podSecurityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + fsGroupChangePolicy: "OnRootMismatch" + seccompProfile: {type: RuntimeDefault} + containerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: {drop: ["ALL"]} + authentication: + user: + value: "br-ccs" + password: + value: "" + erlangCookie: + # Operators MUST supply a stable cookie (e.g. openssl rand -base64 32) when rabbitmq.enabled=true. + value: "" + extraSecrets: + - name: "br-ccs-rabbitmq-load-definition" + mountPath: /etc/rabbitmq/definitions + customConfig: | + management.load_definitions = /etc/rabbitmq/definitions/load_definition.json + +# trigger: retry release after README fix