From d8a684e387d63d8832eac8eb8463f53c1c803660 Mon Sep 17 00:00:00 2001 From: Cristiano Veiga Date: Sun, 14 Jun 2026 18:25:22 -0400 Subject: [PATCH 1/5] feat: add GMP PodMonitoring support Add PodMonitoring CRD template (monitoring.googleapis.com/v1) for Google Managed Prometheus scraping. Disabled by default via monitoring.podMonitoring.enabled=false. Co-Authored-By: Claude Sonnet 4.6 --- charts/templates/podmonitoring.yaml | 24 ++++++++++++++++++++++++ charts/values.yaml | 10 ++++++++++ 2 files changed, 34 insertions(+) create mode 100644 charts/templates/podmonitoring.yaml diff --git a/charts/templates/podmonitoring.yaml b/charts/templates/podmonitoring.yaml new file mode 100644 index 0000000..e2635f0 --- /dev/null +++ b/charts/templates/podmonitoring.yaml @@ -0,0 +1,24 @@ +{{- if .Values.monitoring.podMonitoring.enabled }} +apiVersion: monitoring.googleapis.com/v1 +kind: PodMonitoring +metadata: + name: {{ include "hyperfleet-api.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "hyperfleet-api.labels" . | nindent 4 }} + {{- with .Values.monitoring.podMonitoring.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- include "hyperfleet-api.selectorLabels" . | nindent 6 }} + endpoints: + - port: metrics + interval: {{ .Values.monitoring.podMonitoring.interval | default "30s" }} + path: /metrics + {{- with .Values.monitoring.podMonitoring.metricRelabeling }} + metricRelabeling: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/values.yaml b/charts/values.yaml index 9f0cc48..21a1df5 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -333,6 +333,16 @@ database: # -- Monitoring and alerting configuration monitoring: + # -- PodMonitoring for Google Managed Prometheus (GMP) scraping + podMonitoring: + # -- Create a PodMonitoring resource + enabled: false + # -- Scrape interval + interval: 30s + # -- Additional labels for the PodMonitoring resource + additionalLabels: {} + # -- Metric relabel configs to apply to samples before ingestion + metricRelabeling: [] # -- PrometheusRule for alerting prometheusRule: # -- Create PrometheusRule resources From 09899cff4b6c1adbb3ba3f5d069e8e9bc44f9b8e Mon Sep 17 00:00:00 2001 From: Cristiano Veiga Date: Mon, 15 Jun 2026 08:58:42 -0400 Subject: [PATCH 2/5] docs: update helm-docs and metrics guide for GMP PodMonitoring Co-Authored-By: Claude Sonnet 4.6 --- charts/README.md | 211 ++++++++++++++++++++++++----------------------- docs/metrics.md | 13 +++ 2 files changed, 121 insertions(+), 103 deletions(-) diff --git a/charts/README.md b/charts/README.md index fb6516b..acaa984 100644 --- a/charts/README.md +++ b/charts/README.md @@ -32,133 +32,114 @@ helm install hyperfleet-api oci://REGISTRY/hyperfleet-api \ | Key | Type | Default | Description | |-----|------|---------|-------------| -| replicaCount | int | `1` | Number of API server replicas | -| image.registry | string | `"CHANGE_ME"` | Container image registry (no default — must be set) | -| image.repository | string | `"CHANGE_ME"` | Container image repository (no default — must be set) | -| image.pullPolicy | string | `"Always"` | Image pull policy | -| image.tag | string | `""` | Image tag (no default — must be set via `--set image.tag=`) | -| imagePullSecrets | list | `[]` | Secrets for pulling images from private registries | -| nameOverride | string | `""` | Override the chart name used in resource names | -| fullnameOverride | string | `""` | Override the full release name used in resource names | -| ports | object | `{"api":8000,"health":8080,"metrics":9090}` | Container ports exposed by the API server. These must match the corresponding application config values (`config.server.port`, `config.health.port`, `config.metrics.port`). | -| ports.api | int | `8000` | API server port | -| ports.health | int | `8080` | Health check endpoint port | -| ports.metrics | int | `9090` | Prometheus metrics endpoint port | +| affinity | object | `{}` | Affinity rules for pod scheduling | +| autoscaling | object | `{"enabled":false,"maxReplicas":10,"minReplicas":1,"targetCPUUtilizationPercentage":80,"targetMemoryUtilizationPercentage":80}` | Horizontal Pod Autoscaler configuration | +| autoscaling.enabled | bool | `false` | Enable the HPA | +| autoscaling.maxReplicas | int | `10` | Maximum number of replicas | +| autoscaling.minReplicas | int | `1` | Minimum number of replicas | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | +| autoscaling.targetMemoryUtilizationPercentage | int | `80` | Target memory utilization percentage | | config | object | `{"adapters":{"required":{"cluster":[],"nodepool":[]}},"database":{"debug":false,"dialect":"postgres","host":"","name":"hyperfleet","pool":{"conn_max_idle_time":"1m","conn_max_lifetime":"5m","conn_retry_attempts":10,"conn_retry_interval":"3s","max_connections":50,"max_idle_connections":10,"request_timeout":"30s"},"port":5432,"ssl":{"mode":"disable","root_cert_file":""}},"existingConfigMap":"","health":{"db_ping_timeout":"2s","host":"0.0.0.0","port":8080,"shutdown_timeout":"20s","tls":{"enabled":false}},"logging":{"format":"json","level":"info","masking":{"enabled":true,"fields":["password","secret","token","api_key","access_token","refresh_token","client_secret"],"headers":["Authorization","X-API-Key","Cookie","X-Auth-Token","X-Forwarded-Authorization","X-HyperFleet-Identity"]},"otel":{"enabled":false},"output":"stdout"},"metrics":{"deletion_stuck_threshold":"30m","host":"0.0.0.0","label_metrics_inclusion_duration":"168h","port":9090,"tls":{"enabled":false}},"server":{"host":"0.0.0.0","hostname":"","identity_header":"","jwk":{"cert_file":"","cert_url":""},"jwt":{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""},"port":8000,"timeouts":{"read":"5s","write":"30s"},"tls":{"cert_file":"","enabled":false,"key_file":""}}}` | Application configuration. All settings in this section generate the ConfigMap consumed by the API server. Set `config.existingConfigMap` to use a pre-existing ConfigMap instead. | -| config.existingConfigMap | string | `""` | Use an existing ConfigMap instead of generating one. When set, all other `config.*` values are ignored. | -| config.server | object | `{"host":"0.0.0.0","hostname":"","identity_header":"","jwk":{"cert_file":"","cert_url":""},"jwt":{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""},"port":8000,"timeouts":{"read":"5s","write":"30s"},"tls":{"cert_file":"","enabled":false,"key_file":""}}` | HTTP server settings | -| config.server.hostname | string | `""` | Public hostname advertised by the API (leave empty for auto-detect) | -| config.server.host | string | `"0.0.0.0"` | Listen address | -| config.server.port | int | `8000` | Listen port (must match `ports.api`) | -| config.server.timeouts | object | `{"read":"5s","write":"30s"}` | Request timeout settings | -| config.server.timeouts.read | string | `"5s"` | HTTP read timeout | -| config.server.timeouts.write | string | `"30s"` | HTTP write timeout | -| config.server.tls | object | `{"cert_file":"","enabled":false,"key_file":""}` | TLS configuration for the API server | -| config.server.tls.enabled | bool | `false` | Enable TLS on the API listener | -| config.server.tls.cert_file | string | `""` | Path to TLS certificate file | -| config.server.tls.key_file | string | `""` | Path to TLS key file | -| config.server.jwt | object | `{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""}` | JWT authentication settings | -| config.server.jwt.enabled | bool | `false` | Enable JWT authentication | -| config.server.jwt.issuer_url | string | `""` | OIDC issuer URL for token validation | -| config.server.jwt.audience | string | `""` | Expected JWT audience claim | -| config.server.jwt.identity_claim | string | `"email"` | JWT claim used as the caller identity | -| config.server.identity_header | string | `""` | HTTP header used to pass caller identity (bypasses JWT when set) | -| config.server.jwk | object | `{"cert_file":"","cert_url":""}` | JWK settings for token verification | -| config.server.jwk.cert_file | string | `""` | Path to a local JWK certificate file | -| config.server.jwk.cert_url | string | `""` | URL to fetch JWK certificates from | +| config.adapters | object | `{"required":{"cluster":[],"nodepool":[]}}` | Adapters required for resources to reach "Ready" state. Production deployments should list all expected adapters. | +| config.adapters.required | object | `{"cluster":[],"nodepool":[]}` | Adapters required for cluster resources | +| config.adapters.required.cluster | list | `[]` | Required cluster adapters (e.g. `["validation", "dns", "pullsecret", "hypershift"]`) | +| config.adapters.required.nodepool | list | `[]` | Required nodepool adapters (e.g. `["validation", "hypershift"]`) | | config.database | object | `{"debug":false,"dialect":"postgres","host":"","name":"hyperfleet","pool":{"conn_max_idle_time":"1m","conn_max_lifetime":"5m","conn_retry_attempts":10,"conn_retry_interval":"3s","max_connections":50,"max_idle_connections":10,"request_timeout":"30s"},"port":5432,"ssl":{"mode":"disable","root_cert_file":""}}` | Database connection settings. Credentials must be provided via a Secret — see `database.external.secretName` or use the built-in PostgreSQL (`database.postgresql.enabled`). | +| config.database.debug | bool | `false` | Enable SQL debug logging | | config.database.dialect | string | `"postgres"` | SQL dialect | | config.database.host | string | `""` | Database host (auto-set when using the built-in PostgreSQL) | -| config.database.port | int | `5432` | Database port | | config.database.name | string | `"hyperfleet"` | Database name | -| config.database.debug | bool | `false` | Enable SQL debug logging | -| config.database.ssl | object | `{"mode":"disable","root_cert_file":""}` | SSL / TLS settings for the database connection | -| config.database.ssl.mode | string | `"disable"` | SSL mode (`disable`, `require`, `verify-ca`, `verify-full`) | -| config.database.ssl.root_cert_file | string | `""` | Path to the CA root certificate | | config.database.pool | object | `{"conn_max_idle_time":"1m","conn_max_lifetime":"5m","conn_retry_attempts":10,"conn_retry_interval":"3s","max_connections":50,"max_idle_connections":10,"request_timeout":"30s"}` | Connection pool tuning | -| config.database.pool.max_connections | int | `50` | Maximum number of open connections | -| config.database.pool.max_idle_connections | int | `10` | Maximum number of idle connections | -| config.database.pool.conn_max_lifetime | string | `"5m"` | Maximum lifetime of a connection | | config.database.pool.conn_max_idle_time | string | `"1m"` | Maximum idle time before a connection is closed | -| config.database.pool.request_timeout | string | `"30s"` | Timeout for acquiring a connection from the pool | +| config.database.pool.conn_max_lifetime | string | `"5m"` | Maximum lifetime of a connection | | config.database.pool.conn_retry_attempts | int | `10` | Number of connection retry attempts on startup | | config.database.pool.conn_retry_interval | string | `"3s"` | Interval between connection retry attempts | +| config.database.pool.max_connections | int | `50` | Maximum number of open connections | +| config.database.pool.max_idle_connections | int | `10` | Maximum number of idle connections | +| config.database.pool.request_timeout | string | `"30s"` | Timeout for acquiring a connection from the pool | +| config.database.port | int | `5432` | Database port | +| config.database.ssl | object | `{"mode":"disable","root_cert_file":""}` | SSL / TLS settings for the database connection | +| config.database.ssl.mode | string | `"disable"` | SSL mode (`disable`, `require`, `verify-ca`, `verify-full`) | +| config.database.ssl.root_cert_file | string | `""` | Path to the CA root certificate | +| config.existingConfigMap | string | `""` | Use an existing ConfigMap instead of generating one. When set, all other `config.*` values are ignored. | +| config.health | object | `{"db_ping_timeout":"2s","host":"0.0.0.0","port":8080,"shutdown_timeout":"20s","tls":{"enabled":false}}` | Health check endpoint settings | +| config.health.db_ping_timeout | string | `"2s"` | Timeout for the database liveness ping | +| config.health.host | string | `"0.0.0.0"` | Listen address (must be `0.0.0.0` for probe access) | +| config.health.port | int | `8080` | Listen port (must match `ports.health`) | +| config.health.shutdown_timeout | string | `"20s"` | Graceful shutdown timeout | +| config.health.tls | object | `{"enabled":false}` | TLS configuration for the health endpoint | +| config.health.tls.enabled | bool | `false` | Enable TLS on the health endpoint | | config.logging | object | `{"format":"json","level":"info","masking":{"enabled":true,"fields":["password","secret","token","api_key","access_token","refresh_token","client_secret"],"headers":["Authorization","X-API-Key","Cookie","X-Auth-Token","X-Forwarded-Authorization","X-HyperFleet-Identity"]},"otel":{"enabled":false},"output":"stdout"}` | Logging configuration | -| config.logging.level | string | `"info"` | Log level (`debug`, `info`, `warn`, `error`) | | config.logging.format | string | `"json"` | Log format (`json` or `text`) | -| config.logging.output | string | `"stdout"` | Log output destination | -| config.logging.otel | object | `{"enabled":false}` | OpenTelemetry tracing integration. See the [tracing standard](https://github.com/openshift-hyperfleet/architecture/blob/main/hyperfleet/standards/tracing.md#configuration). | -| config.logging.otel.enabled | bool | `false` | Enable OpenTelemetry log correlation | +| config.logging.level | string | `"info"` | Log level (`debug`, `info`, `warn`, `error`) | | config.logging.masking | object | `{"enabled":true,"fields":["password","secret","token","api_key","access_token","refresh_token","client_secret"],"headers":["Authorization","X-API-Key","Cookie","X-Auth-Token","X-Forwarded-Authorization","X-HyperFleet-Identity"]}` | Sensitive-data masking for logs | | config.logging.masking.enabled | bool | `true` | Enable log masking | -| config.logging.masking.headers | list | `["Authorization","X-API-Key","Cookie","X-Auth-Token","X-Forwarded-Authorization","X-HyperFleet-Identity"]` | HTTP headers whose values are redacted in logs | | config.logging.masking.fields | list | `["password","secret","token","api_key","access_token","refresh_token","client_secret"]` | Field names whose values are redacted in logs | +| config.logging.masking.headers | list | `["Authorization","X-API-Key","Cookie","X-Auth-Token","X-Forwarded-Authorization","X-HyperFleet-Identity"]` | HTTP headers whose values are redacted in logs | +| config.logging.otel | object | `{"enabled":false}` | OpenTelemetry tracing integration. See the [tracing standard](https://github.com/openshift-hyperfleet/architecture/blob/main/hyperfleet/standards/tracing.md#configuration). | +| config.logging.otel.enabled | bool | `false` | Enable OpenTelemetry log correlation | +| config.logging.output | string | `"stdout"` | Log output destination | | config.metrics | object | `{"deletion_stuck_threshold":"30m","host":"0.0.0.0","label_metrics_inclusion_duration":"168h","port":9090,"tls":{"enabled":false}}` | Prometheus metrics endpoint settings | +| config.metrics.deletion_stuck_threshold | string | `"30m"` | Threshold after which a deletion is considered stuck | | config.metrics.host | string | `"0.0.0.0"` | Listen address (must be `0.0.0.0` for in-cluster access) | +| config.metrics.label_metrics_inclusion_duration | string | `"168h"` | Duration window for label-based metric inclusion | | config.metrics.port | int | `9090` | Listen port (must match `ports.metrics`) | | config.metrics.tls | object | `{"enabled":false}` | TLS configuration for the metrics endpoint | | config.metrics.tls.enabled | bool | `false` | Enable TLS on the metrics endpoint | -| config.metrics.label_metrics_inclusion_duration | string | `"168h"` | Duration window for label-based metric inclusion | -| config.metrics.deletion_stuck_threshold | string | `"30m"` | Threshold after which a deletion is considered stuck | -| config.health | object | `{"db_ping_timeout":"2s","host":"0.0.0.0","port":8080,"shutdown_timeout":"20s","tls":{"enabled":false}}` | Health check endpoint settings | -| config.health.host | string | `"0.0.0.0"` | Listen address (must be `0.0.0.0` for probe access) | -| config.health.port | int | `8080` | Listen port (must match `ports.health`) | -| config.health.tls | object | `{"enabled":false}` | TLS configuration for the health endpoint | -| config.health.tls.enabled | bool | `false` | Enable TLS on the health endpoint | -| config.health.shutdown_timeout | string | `"20s"` | Graceful shutdown timeout | -| config.health.db_ping_timeout | string | `"2s"` | Timeout for the database liveness ping | -| config.adapters | object | `{"required":{"cluster":[],"nodepool":[]}}` | Adapters required for resources to reach "Ready" state. Production deployments should list all expected adapters. | -| config.adapters.required | object | `{"cluster":[],"nodepool":[]}` | Adapters required for cluster resources | -| config.adapters.required.cluster | list | `[]` | Required cluster adapters (e.g. `["validation", "dns", "pullsecret", "hypershift"]`) | -| config.adapters.required.nodepool | list | `[]` | Required nodepool adapters (e.g. `["validation", "hypershift"]`) | -| serviceAccount | object | `{"annotations":{},"create":true,"name":""}` | ServiceAccount configuration | -| serviceAccount.create | bool | `true` | Create a ServiceAccount for the API server | -| serviceAccount.annotations | object | `{}` | Annotations added to the ServiceAccount (e.g. for Workload Identity) | -| serviceAccount.name | string | `""` | Override the ServiceAccount name (defaults to the release fullname) | -| podAnnotations | object | `{}` | Additional annotations applied to all pods | -| podLabels | object | `{}` | Additional labels applied to all pods | -| podSecurityContext | object | `{"fsGroup":65532,"runAsNonRoot":true,"runAsUser":65532}` | Pod-level security context | -| podSecurityContext.fsGroup | int | `65532` | Filesystem group for volume mounts | -| podSecurityContext.runAsNonRoot | bool | `true` | Run all containers as non-root | -| podSecurityContext.runAsUser | int | `65532` | UID for all containers | -| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"seccompProfile":{"type":"RuntimeDefault"}}` | Container-level security context | -| securityContext.allowPrivilegeEscalation | bool | `false` | Disallow privilege escalation | -| securityContext.readOnlyRootFilesystem | bool | `true` | Mount root filesystem as read-only | -| service | object | `{"type":"ClusterIP"}` | Kubernetes Service configuration | -| service.type | string | `"ClusterIP"` | Service type (`ClusterIP`, `LoadBalancer`, `NodePort`) | -| resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | CPU and memory resource requests and limits | -| nodeSelector | object | `{}` | Node selector constraints for pod scheduling | -| tolerations | list | `[]` | Tolerations for pod scheduling | -| affinity | object | `{}` | Affinity rules for pod scheduling | -| autoscaling | object | `{"enabled":false,"maxReplicas":10,"minReplicas":1,"targetCPUUtilizationPercentage":80,"targetMemoryUtilizationPercentage":80}` | Horizontal Pod Autoscaler configuration | -| autoscaling.enabled | bool | `false` | Enable the HPA | -| autoscaling.minReplicas | int | `1` | Minimum number of replicas | -| autoscaling.maxReplicas | int | `10` | Maximum number of replicas | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | -| autoscaling.targetMemoryUtilizationPercentage | int | `80` | Target memory utilization percentage | -| podDisruptionBudget | object | `{"enabled":false,"minAvailable":1}` | PodDisruptionBudget configuration | -| podDisruptionBudget.enabled | bool | `false` | Enable the PDB | -| podDisruptionBudget.minAvailable | int | `1` | Minimum number of available pods during disruption | +| config.server | object | `{"host":"0.0.0.0","hostname":"","identity_header":"","jwk":{"cert_file":"","cert_url":""},"jwt":{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""},"port":8000,"timeouts":{"read":"5s","write":"30s"},"tls":{"cert_file":"","enabled":false,"key_file":""}}` | HTTP server settings | +| config.server.host | string | `"0.0.0.0"` | Listen address | +| config.server.hostname | string | `""` | Public hostname advertised by the API (leave empty for auto-detect) | +| config.server.identity_header | string | `""` | HTTP header used to pass caller identity (bypasses JWT when set) | +| config.server.jwk | object | `{"cert_file":"","cert_url":""}` | JWK settings for token verification | +| config.server.jwk.cert_file | string | `""` | Path to a local JWK certificate file | +| config.server.jwk.cert_url | string | `""` | URL to fetch JWK certificates from | +| config.server.jwt | object | `{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""}` | JWT authentication settings | +| config.server.jwt.audience | string | `""` | Expected JWT audience claim | +| config.server.jwt.enabled | bool | `false` | Enable JWT authentication | +| config.server.jwt.identity_claim | string | `"email"` | JWT claim used as the caller identity | +| config.server.jwt.issuer_url | string | `""` | OIDC issuer URL for token validation | +| config.server.port | int | `8000` | Listen port (must match `ports.api`) | +| config.server.timeouts | object | `{"read":"5s","write":"30s"}` | Request timeout settings | +| config.server.timeouts.read | string | `"5s"` | HTTP read timeout | +| config.server.timeouts.write | string | `"30s"` | HTTP write timeout | +| config.server.tls | object | `{"cert_file":"","enabled":false,"key_file":""}` | TLS configuration for the API server | +| config.server.tls.cert_file | string | `""` | Path to TLS certificate file | +| config.server.tls.enabled | bool | `false` | Enable TLS on the API listener | +| config.server.tls.key_file | string | `""` | Path to TLS key file | | database | object | `{"external":{"enabled":false,"secretName":""},"postgresql":{"database":"hyperfleet","enabled":true,"image":"docker.io/library/postgres:14.2","password":"hyperfleet-dev-password","persistence":{"enabled":false,"size":"1Gi","storageClass":""},"port":5432,"resources":{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}},"user":"hyperfleet"}}` | Database infrastructure settings. For **production**, set `database.external.enabled=true` and supply a secret with connection details. For **development**, the built-in PostgreSQL pod is enabled by default. | | database.external | object | `{"enabled":false,"secretName":""}` | External database configuration (production) | | database.external.enabled | bool | `false` | Use an external database instead of the built-in PostgreSQL | | database.external.secretName | string | `""` | Name of an existing Secret with keys: `db.host`, `db.port`, `db.name`, `db.user`, `db.password` | | database.postgresql | object | `{"database":"hyperfleet","enabled":true,"image":"docker.io/library/postgres:14.2","password":"hyperfleet-dev-password","persistence":{"enabled":false,"size":"1Gi","storageClass":""},"port":5432,"resources":{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}},"user":"hyperfleet"}` | Built-in PostgreSQL for development and testing | +| database.postgresql.database | string | `"hyperfleet"` | Database name | | database.postgresql.enabled | bool | `true` | Deploy a single-pod PostgreSQL instance | | database.postgresql.image | string | `"docker.io/library/postgres:14.2"` | PostgreSQL container image | -| database.postgresql.database | string | `"hyperfleet"` | Database name | -| database.postgresql.user | string | `"hyperfleet"` | Database user | | database.postgresql.password | string | `"hyperfleet-dev-password"` | Database password (**development only** — use a Secret in production) | -| database.postgresql.port | int | `5432` | PostgreSQL listen port | -| database.postgresql.resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | Resource requests and limits for the PostgreSQL pod | | database.postgresql.persistence | object | `{"enabled":false,"size":"1Gi","storageClass":""}` | Persistent volume configuration for PostgreSQL data | | database.postgresql.persistence.enabled | bool | `false` | Enable persistent storage (uses emptyDir when disabled) | | database.postgresql.persistence.size | string | `"1Gi"` | Volume size | | database.postgresql.persistence.storageClass | string | `""` | StorageClass name (empty for cluster default) | -| monitoring | object | `{"prometheusRule":{"additionalLabels":{},"enabled":false,"namespace":"","rules":{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}}}` | Monitoring and alerting configuration | +| database.postgresql.port | int | `5432` | PostgreSQL listen port | +| database.postgresql.resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | Resource requests and limits for the PostgreSQL pod | +| database.postgresql.user | string | `"hyperfleet"` | Database user | +| extraEnv | list | `[]` | Additional environment variables injected into the API container. Use sparingly — prefer `config.*` values above. | +| extraVolumeMounts | list | `[]` | Extra volume mounts added to the API container | +| extraVolumes | list | `[]` | Extra volumes added to the pod | +| fullnameOverride | string | `""` | Override the full release name used in resource names | +| image.pullPolicy | string | `"Always"` | Image pull policy | +| image.registry | string | `"CHANGE_ME"` | Container image registry (no default — must be set) | +| image.repository | string | `"CHANGE_ME"` | Container image repository (no default — must be set) | +| image.tag | string | `""` | Image tag (no default — must be set via `--set image.tag=`) | +| imagePullSecrets | list | `[]` | Secrets for pulling images from private registries | +| monitoring | object | `{"podMonitoring":{"additionalLabels":{},"enabled":false,"interval":"30s","metricRelabeling":[]},"prometheusRule":{"additionalLabels":{},"enabled":false,"namespace":"","rules":{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}}}` | Monitoring and alerting configuration | +| monitoring.podMonitoring | object | `{"additionalLabels":{},"enabled":false,"interval":"30s","metricRelabeling":[]}` | PodMonitoring for Google Managed Prometheus (GMP) scraping | +| monitoring.podMonitoring.additionalLabels | object | `{}` | Additional labels for the PodMonitoring resource | +| monitoring.podMonitoring.enabled | bool | `false` | Create a PodMonitoring resource | +| monitoring.podMonitoring.interval | string | `"30s"` | Scrape interval | +| monitoring.podMonitoring.metricRelabeling | list | `[]` | Metric relabel configs to apply to samples before ingestion | | monitoring.prometheusRule | object | `{"additionalLabels":{},"enabled":false,"namespace":"","rules":{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}}` | PrometheusRule for alerting | -| monitoring.prometheusRule.enabled | bool | `false` | Create PrometheusRule resources | | monitoring.prometheusRule.additionalLabels | object | `{}` | Additional labels for PrometheusRule discovery | +| monitoring.prometheusRule.enabled | bool | `false` | Create PrometheusRule resources | | monitoring.prometheusRule.namespace | string | `""` | Namespace to create the PrometheusRule in (defaults to release namespace) | | monitoring.prometheusRule.rules | object | `{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}` | Alert rule configuration | | monitoring.prometheusRule.rules.deletionStuck | object | `{"for":"5m","runbookUrl":""}` | Alert when a deletion is stuck | @@ -167,29 +148,53 @@ helm install hyperfleet-api oci://REGISTRY/hyperfleet-api \ | monitoring.prometheusRule.rules.deletionTimeout | object | `{"for":"30m","runbookUrl":""}` | Alert when a deletion times out | | monitoring.prometheusRule.rules.deletionTimeout.for | string | `"30m"` | Duration before the alert fires | | monitoring.prometheusRule.rules.deletionTimeout.runbookUrl | string | `""` | Runbook URL included in the alert | +| nameOverride | string | `""` | Override the chart name used in resource names | +| nativeSidecars | list | `[]` | Native sidecar containers (Kubernetes 1.28+). Native sidecars are init containers with `restartPolicy: Always` — they start before other init containers and keep running throughout the pod lifecycle. Use this for database proxies that must be available during `db-migrate`. Each entry is a full Kubernetes container spec. | +| nodeSelector | object | `{}` | Node selector constraints for pod scheduling | +| podAnnotations | object | `{}` | Additional annotations applied to all pods | +| podDisruptionBudget | object | `{"enabled":false,"minAvailable":1}` | PodDisruptionBudget configuration | +| podDisruptionBudget.enabled | bool | `false` | Enable the PDB | +| podDisruptionBudget.minAvailable | int | `1` | Minimum number of available pods during disruption | +| podLabels | object | `{}` | Additional labels applied to all pods | +| podSecurityContext | object | `{"fsGroup":65532,"runAsNonRoot":true,"runAsUser":65532}` | Pod-level security context | +| podSecurityContext.fsGroup | int | `65532` | Filesystem group for volume mounts | +| podSecurityContext.runAsNonRoot | bool | `true` | Run all containers as non-root | +| podSecurityContext.runAsUser | int | `65532` | UID for all containers | +| ports | object | `{"api":8000,"health":8080,"metrics":9090}` | Container ports exposed by the API server. These must match the corresponding application config values (`config.server.port`, `config.health.port`, `config.metrics.port`). | +| ports.api | int | `8000` | API server port | +| ports.health | int | `8080` | Health check endpoint port | +| ports.metrics | int | `9090` | Prometheus metrics endpoint port | +| replicaCount | int | `1` | Number of API server replicas | +| resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | CPU and memory resource requests and limits | +| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"seccompProfile":{"type":"RuntimeDefault"}}` | Container-level security context | +| securityContext.allowPrivilegeEscalation | bool | `false` | Disallow privilege escalation | +| securityContext.readOnlyRootFilesystem | bool | `true` | Mount root filesystem as read-only | +| service | object | `{"type":"ClusterIP"}` | Kubernetes Service configuration | +| service.type | string | `"ClusterIP"` | Service type (`ClusterIP`, `LoadBalancer`, `NodePort`) | +| serviceAccount | object | `{"annotations":{},"create":true,"name":""}` | ServiceAccount configuration | +| serviceAccount.annotations | object | `{}` | Annotations added to the ServiceAccount (e.g. for Workload Identity) | +| serviceAccount.create | bool | `true` | Create a ServiceAccount for the API server | +| serviceAccount.name | string | `""` | Override the ServiceAccount name (defaults to the release fullname) | | serviceMonitor | object | `{"enabled":false,"interval":"30s","labels":{},"namespace":"","scrapeTimeout":"10s"}` | ServiceMonitor for Prometheus Operator scrape configuration | | serviceMonitor.enabled | bool | `false` | Create a ServiceMonitor resource | | serviceMonitor.interval | string | `"30s"` | Scrape interval | -| serviceMonitor.scrapeTimeout | string | `"10s"` | Scrape timeout | | serviceMonitor.labels | object | `{}` | Additional labels for ServiceMonitor discovery | | serviceMonitor.namespace | string | `""` | Namespace to create the ServiceMonitor in (defaults to release namespace) | +| serviceMonitor.scrapeTimeout | string | `"10s"` | Scrape timeout | +| sidecars | list | `[]` | Regular sidecar containers. These start after init containers complete. Use `nativeSidecars` above for containers that must be available during init (e.g. database proxies). Each entry is a full Kubernetes container spec. | +| tolerations | list | `[]` | Tolerations for pod scheduling | | tracing | object | `{"enabled":false,"otlpEndpoint":"","otlpProtocol":"grpc","propagators":"tracecontext,baggage","sampler":"parentbased_traceidratio","samplerArg":"1.0","serviceName":"hyperfleet-api"}` | Distributed tracing configuration (OpenTelemetry) | | tracing.enabled | bool | `false` | Enable trace export | -| tracing.serviceName | string | `"hyperfleet-api"` | Service name reported in traces | | tracing.otlpEndpoint | string | `""` | OTLP exporter endpoint (traces go to stdout when empty) | | tracing.otlpProtocol | string | `"grpc"` | OTLP protocol (`grpc` or `http/protobuf`) | +| tracing.propagators | string | `"tracecontext,baggage"` | Context propagation formats | | tracing.sampler | string | `"parentbased_traceidratio"` | Sampler type | | tracing.samplerArg | string | `"1.0"` | Sampling rate (`1.0` for dev, `0.01` for production) | -| tracing.propagators | string | `"tracecontext,baggage"` | Context propagation formats | -| nativeSidecars | list | `[]` | Native sidecar containers (Kubernetes 1.28+). Native sidecars are init containers with `restartPolicy: Always` — they start before other init containers and keep running throughout the pod lifecycle. Use this for database proxies that must be available during `db-migrate`. Each entry is a full Kubernetes container spec. | -| sidecars | list | `[]` | Regular sidecar containers. These start after init containers complete. Use `nativeSidecars` above for containers that must be available during init (e.g. database proxies). Each entry is a full Kubernetes container spec. | +| tracing.serviceName | string | `"hyperfleet-api"` | Service name reported in traces | | validationSchema | object | `{"content":"","enabled":false,"existingConfigMap":""}` | Validation schema configuration. Supply a custom OpenAPI schema for cluster/nodepool spec validation. When enabled, the schema is mounted into the container and every create/update request is validated against it. The API will fail to start if the schema is invalid. | +| validationSchema.content | string | `""` | Inline OpenAPI 3.0 schema content. Must define `ClusterSpec` and `NodePoolSpec` under `components.schemas`. | | validationSchema.enabled | bool | `false` | Enable spec validation | | validationSchema.existingConfigMap | string | `""` | Use an existing ConfigMap (must contain an `openapi.yaml` key). When set, `validationSchema.content` is ignored. | -| validationSchema.content | string | `""` | Inline OpenAPI 3.0 schema content. Must define `ClusterSpec` and `NodePoolSpec` under `components.schemas`. | -| extraEnv | list | `[]` | Additional environment variables injected into the API container. Use sparingly — prefer `config.*` values above. | -| extraVolumeMounts | list | `[]` | Extra volume mounts added to the API container | -| extraVolumes | list | `[]` | Extra volumes added to the pod | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs](https://github.com/norwoodj/helm-docs) diff --git a/docs/metrics.md b/docs/metrics.md index fbb3596..c83b160 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -376,6 +376,19 @@ serviceMonitor: See [Deployment Guide](deployment.md#prometheus-operator-integration) for details. +## Google Managed Prometheus (GMP) Integration + +If running on GKE with Google Managed Prometheus, enable the PodMonitoring resource in Helm values: + +```yaml +monitoring: + podMonitoring: + enabled: true + interval: 30s +``` + +This creates a `monitoring.googleapis.com/v1/PodMonitoring` resource that configures the GMP collector agent to scrape the `/metrics` endpoint directly from pods. The `serviceMonitor` and `podMonitoring` options are independent and can be enabled simultaneously. + ## Grafana Dashboard Example dashboard JSON for HyperFleet API monitoring is available in the architecture repository. Key panels to include: From 3aec0bf2282d3e4224411c03b66d9d9b2e7e3fcb Mon Sep 17 00:00:00 2001 From: Cristiano Veiga Date: Mon, 15 Jun 2026 10:09:20 -0400 Subject: [PATCH 3/5] feat: add configurable TLS support to GMP PodMonitoring When config.metrics.tls.enabled=true, set scheme: https and apply monitoring.podMonitoring.tlsConfig.insecureSkipVerify (default: false). Co-Authored-By: Claude Sonnet 4.6 --- charts/README.md | 6 ++++-- charts/templates/podmonitoring.yaml | 5 +++++ charts/values.yaml | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/charts/README.md b/charts/README.md index acaa984..7709c29 100644 --- a/charts/README.md +++ b/charts/README.md @@ -131,12 +131,14 @@ helm install hyperfleet-api oci://REGISTRY/hyperfleet-api \ | image.repository | string | `"CHANGE_ME"` | Container image repository (no default — must be set) | | image.tag | string | `""` | Image tag (no default — must be set via `--set image.tag=`) | | imagePullSecrets | list | `[]` | Secrets for pulling images from private registries | -| monitoring | object | `{"podMonitoring":{"additionalLabels":{},"enabled":false,"interval":"30s","metricRelabeling":[]},"prometheusRule":{"additionalLabels":{},"enabled":false,"namespace":"","rules":{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}}}` | Monitoring and alerting configuration | -| monitoring.podMonitoring | object | `{"additionalLabels":{},"enabled":false,"interval":"30s","metricRelabeling":[]}` | PodMonitoring for Google Managed Prometheus (GMP) scraping | +| monitoring | object | `{"podMonitoring":{"additionalLabels":{},"enabled":false,"interval":"30s","metricRelabeling":[],"tlsConfig":{"insecureSkipVerify":false}},"prometheusRule":{"additionalLabels":{},"enabled":false,"namespace":"","rules":{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}}}` | Monitoring and alerting configuration | +| monitoring.podMonitoring | object | `{"additionalLabels":{},"enabled":false,"interval":"30s","metricRelabeling":[],"tlsConfig":{"insecureSkipVerify":false}}` | PodMonitoring for Google Managed Prometheus (GMP) scraping | | monitoring.podMonitoring.additionalLabels | object | `{}` | Additional labels for the PodMonitoring resource | | monitoring.podMonitoring.enabled | bool | `false` | Create a PodMonitoring resource | | monitoring.podMonitoring.interval | string | `"30s"` | Scrape interval | | monitoring.podMonitoring.metricRelabeling | list | `[]` | Metric relabel configs to apply to samples before ingestion | +| monitoring.podMonitoring.tlsConfig | object | `{"insecureSkipVerify":false}` | TLS configuration when config.metrics.tls.enabled=true | +| monitoring.podMonitoring.tlsConfig.insecureSkipVerify | bool | `false` | Disable target certificate validation (e.g. for self-signed certs) | | monitoring.prometheusRule | object | `{"additionalLabels":{},"enabled":false,"namespace":"","rules":{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}}` | PrometheusRule for alerting | | monitoring.prometheusRule.additionalLabels | object | `{}` | Additional labels for PrometheusRule discovery | | monitoring.prometheusRule.enabled | bool | `false` | Create PrometheusRule resources | diff --git a/charts/templates/podmonitoring.yaml b/charts/templates/podmonitoring.yaml index e2635f0..edd9a71 100644 --- a/charts/templates/podmonitoring.yaml +++ b/charts/templates/podmonitoring.yaml @@ -17,6 +17,11 @@ spec: - port: metrics interval: {{ .Values.monitoring.podMonitoring.interval | default "30s" }} path: /metrics + {{- if .Values.config.metrics.tls.enabled }} + scheme: https + tls: + insecureSkipVerify: {{ .Values.monitoring.podMonitoring.tlsConfig.insecureSkipVerify }} + {{- end }} {{- with .Values.monitoring.podMonitoring.metricRelabeling }} metricRelabeling: {{- toYaml . | nindent 8 }} diff --git a/charts/values.yaml b/charts/values.yaml index 21a1df5..f75f2c5 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -343,6 +343,10 @@ monitoring: additionalLabels: {} # -- Metric relabel configs to apply to samples before ingestion metricRelabeling: [] + # -- TLS configuration when config.metrics.tls.enabled=true + tlsConfig: + # -- Disable target certificate validation (e.g. for self-signed certs) + insecureSkipVerify: false # -- PrometheusRule for alerting prometheusRule: # -- Create PrometheusRule resources From 63ae62f35cfd4cd8a7e2b90a90ef2123ddd31f60 Mon Sep 17 00:00:00 2001 From: Cristiano Veiga Date: Mon, 15 Jun 2026 11:11:44 -0400 Subject: [PATCH 4/5] docs: regenerate helm-docs for GMP PodMonitoring Co-Authored-By: Claude --- charts/README.md | 206 +++++++++++++++++++++++------------------------ 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/charts/README.md b/charts/README.md index 7709c29..88c0269 100644 --- a/charts/README.md +++ b/charts/README.md @@ -32,116 +32,140 @@ helm install hyperfleet-api oci://REGISTRY/hyperfleet-api \ | Key | Type | Default | Description | |-----|------|---------|-------------| -| affinity | object | `{}` | Affinity rules for pod scheduling | -| autoscaling | object | `{"enabled":false,"maxReplicas":10,"minReplicas":1,"targetCPUUtilizationPercentage":80,"targetMemoryUtilizationPercentage":80}` | Horizontal Pod Autoscaler configuration | -| autoscaling.enabled | bool | `false` | Enable the HPA | -| autoscaling.maxReplicas | int | `10` | Maximum number of replicas | -| autoscaling.minReplicas | int | `1` | Minimum number of replicas | -| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | -| autoscaling.targetMemoryUtilizationPercentage | int | `80` | Target memory utilization percentage | +| replicaCount | int | `1` | Number of API server replicas | +| image.registry | string | `"CHANGE_ME"` | Container image registry (no default — must be set) | +| image.repository | string | `"CHANGE_ME"` | Container image repository (no default — must be set) | +| image.pullPolicy | string | `"Always"` | Image pull policy | +| image.tag | string | `""` | Image tag (no default — must be set via `--set image.tag=`) | +| imagePullSecrets | list | `[]` | Secrets for pulling images from private registries | +| nameOverride | string | `""` | Override the chart name used in resource names | +| fullnameOverride | string | `""` | Override the full release name used in resource names | +| ports | object | `{"api":8000,"health":8080,"metrics":9090}` | Container ports exposed by the API server. These must match the corresponding application config values (`config.server.port`, `config.health.port`, `config.metrics.port`). | +| ports.api | int | `8000` | API server port | +| ports.health | int | `8080` | Health check endpoint port | +| ports.metrics | int | `9090` | Prometheus metrics endpoint port | | config | object | `{"adapters":{"required":{"cluster":[],"nodepool":[]}},"database":{"debug":false,"dialect":"postgres","host":"","name":"hyperfleet","pool":{"conn_max_idle_time":"1m","conn_max_lifetime":"5m","conn_retry_attempts":10,"conn_retry_interval":"3s","max_connections":50,"max_idle_connections":10,"request_timeout":"30s"},"port":5432,"ssl":{"mode":"disable","root_cert_file":""}},"existingConfigMap":"","health":{"db_ping_timeout":"2s","host":"0.0.0.0","port":8080,"shutdown_timeout":"20s","tls":{"enabled":false}},"logging":{"format":"json","level":"info","masking":{"enabled":true,"fields":["password","secret","token","api_key","access_token","refresh_token","client_secret"],"headers":["Authorization","X-API-Key","Cookie","X-Auth-Token","X-Forwarded-Authorization","X-HyperFleet-Identity"]},"otel":{"enabled":false},"output":"stdout"},"metrics":{"deletion_stuck_threshold":"30m","host":"0.0.0.0","label_metrics_inclusion_duration":"168h","port":9090,"tls":{"enabled":false}},"server":{"host":"0.0.0.0","hostname":"","identity_header":"","jwk":{"cert_file":"","cert_url":""},"jwt":{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""},"port":8000,"timeouts":{"read":"5s","write":"30s"},"tls":{"cert_file":"","enabled":false,"key_file":""}}}` | Application configuration. All settings in this section generate the ConfigMap consumed by the API server. Set `config.existingConfigMap` to use a pre-existing ConfigMap instead. | -| config.adapters | object | `{"required":{"cluster":[],"nodepool":[]}}` | Adapters required for resources to reach "Ready" state. Production deployments should list all expected adapters. | -| config.adapters.required | object | `{"cluster":[],"nodepool":[]}` | Adapters required for cluster resources | -| config.adapters.required.cluster | list | `[]` | Required cluster adapters (e.g. `["validation", "dns", "pullsecret", "hypershift"]`) | -| config.adapters.required.nodepool | list | `[]` | Required nodepool adapters (e.g. `["validation", "hypershift"]`) | +| config.existingConfigMap | string | `""` | Use an existing ConfigMap instead of generating one. When set, all other `config.*` values are ignored. | +| config.server | object | `{"host":"0.0.0.0","hostname":"","identity_header":"","jwk":{"cert_file":"","cert_url":""},"jwt":{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""},"port":8000,"timeouts":{"read":"5s","write":"30s"},"tls":{"cert_file":"","enabled":false,"key_file":""}}` | HTTP server settings | +| config.server.hostname | string | `""` | Public hostname advertised by the API (leave empty for auto-detect) | +| config.server.host | string | `"0.0.0.0"` | Listen address | +| config.server.port | int | `8000` | Listen port (must match `ports.api`) | +| config.server.timeouts | object | `{"read":"5s","write":"30s"}` | Request timeout settings | +| config.server.timeouts.read | string | `"5s"` | HTTP read timeout | +| config.server.timeouts.write | string | `"30s"` | HTTP write timeout | +| config.server.tls | object | `{"cert_file":"","enabled":false,"key_file":""}` | TLS configuration for the API server | +| config.server.tls.enabled | bool | `false` | Enable TLS on the API listener | +| config.server.tls.cert_file | string | `""` | Path to TLS certificate file | +| config.server.tls.key_file | string | `""` | Path to TLS key file | +| config.server.jwt | object | `{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""}` | JWT authentication settings | +| config.server.jwt.enabled | bool | `false` | Enable JWT authentication | +| config.server.jwt.issuer_url | string | `""` | OIDC issuer URL for token validation | +| config.server.jwt.audience | string | `""` | Expected JWT audience claim | +| config.server.jwt.identity_claim | string | `"email"` | JWT claim used as the caller identity | +| config.server.identity_header | string | `""` | HTTP header used to pass caller identity (bypasses JWT when set) | +| config.server.jwk | object | `{"cert_file":"","cert_url":""}` | JWK settings for token verification | +| config.server.jwk.cert_file | string | `""` | Path to a local JWK certificate file | +| config.server.jwk.cert_url | string | `""` | URL to fetch JWK certificates from | | config.database | object | `{"debug":false,"dialect":"postgres","host":"","name":"hyperfleet","pool":{"conn_max_idle_time":"1m","conn_max_lifetime":"5m","conn_retry_attempts":10,"conn_retry_interval":"3s","max_connections":50,"max_idle_connections":10,"request_timeout":"30s"},"port":5432,"ssl":{"mode":"disable","root_cert_file":""}}` | Database connection settings. Credentials must be provided via a Secret — see `database.external.secretName` or use the built-in PostgreSQL (`database.postgresql.enabled`). | -| config.database.debug | bool | `false` | Enable SQL debug logging | | config.database.dialect | string | `"postgres"` | SQL dialect | | config.database.host | string | `""` | Database host (auto-set when using the built-in PostgreSQL) | +| config.database.port | int | `5432` | Database port | | config.database.name | string | `"hyperfleet"` | Database name | +| config.database.debug | bool | `false` | Enable SQL debug logging | +| config.database.ssl | object | `{"mode":"disable","root_cert_file":""}` | SSL / TLS settings for the database connection | +| config.database.ssl.mode | string | `"disable"` | SSL mode (`disable`, `require`, `verify-ca`, `verify-full`) | +| config.database.ssl.root_cert_file | string | `""` | Path to the CA root certificate | | config.database.pool | object | `{"conn_max_idle_time":"1m","conn_max_lifetime":"5m","conn_retry_attempts":10,"conn_retry_interval":"3s","max_connections":50,"max_idle_connections":10,"request_timeout":"30s"}` | Connection pool tuning | -| config.database.pool.conn_max_idle_time | string | `"1m"` | Maximum idle time before a connection is closed | -| config.database.pool.conn_max_lifetime | string | `"5m"` | Maximum lifetime of a connection | -| config.database.pool.conn_retry_attempts | int | `10` | Number of connection retry attempts on startup | -| config.database.pool.conn_retry_interval | string | `"3s"` | Interval between connection retry attempts | | config.database.pool.max_connections | int | `50` | Maximum number of open connections | | config.database.pool.max_idle_connections | int | `10` | Maximum number of idle connections | +| config.database.pool.conn_max_lifetime | string | `"5m"` | Maximum lifetime of a connection | +| config.database.pool.conn_max_idle_time | string | `"1m"` | Maximum idle time before a connection is closed | | config.database.pool.request_timeout | string | `"30s"` | Timeout for acquiring a connection from the pool | -| config.database.port | int | `5432` | Database port | -| config.database.ssl | object | `{"mode":"disable","root_cert_file":""}` | SSL / TLS settings for the database connection | -| config.database.ssl.mode | string | `"disable"` | SSL mode (`disable`, `require`, `verify-ca`, `verify-full`) | -| config.database.ssl.root_cert_file | string | `""` | Path to the CA root certificate | -| config.existingConfigMap | string | `""` | Use an existing ConfigMap instead of generating one. When set, all other `config.*` values are ignored. | -| config.health | object | `{"db_ping_timeout":"2s","host":"0.0.0.0","port":8080,"shutdown_timeout":"20s","tls":{"enabled":false}}` | Health check endpoint settings | -| config.health.db_ping_timeout | string | `"2s"` | Timeout for the database liveness ping | -| config.health.host | string | `"0.0.0.0"` | Listen address (must be `0.0.0.0` for probe access) | -| config.health.port | int | `8080` | Listen port (must match `ports.health`) | -| config.health.shutdown_timeout | string | `"20s"` | Graceful shutdown timeout | -| config.health.tls | object | `{"enabled":false}` | TLS configuration for the health endpoint | -| config.health.tls.enabled | bool | `false` | Enable TLS on the health endpoint | +| config.database.pool.conn_retry_attempts | int | `10` | Number of connection retry attempts on startup | +| config.database.pool.conn_retry_interval | string | `"3s"` | Interval between connection retry attempts | | config.logging | object | `{"format":"json","level":"info","masking":{"enabled":true,"fields":["password","secret","token","api_key","access_token","refresh_token","client_secret"],"headers":["Authorization","X-API-Key","Cookie","X-Auth-Token","X-Forwarded-Authorization","X-HyperFleet-Identity"]},"otel":{"enabled":false},"output":"stdout"}` | Logging configuration | -| config.logging.format | string | `"json"` | Log format (`json` or `text`) | | config.logging.level | string | `"info"` | Log level (`debug`, `info`, `warn`, `error`) | +| config.logging.format | string | `"json"` | Log format (`json` or `text`) | +| config.logging.output | string | `"stdout"` | Log output destination | +| config.logging.otel | object | `{"enabled":false}` | OpenTelemetry tracing integration. See the [tracing standard](https://github.com/openshift-hyperfleet/architecture/blob/main/hyperfleet/standards/tracing.md#configuration). | +| config.logging.otel.enabled | bool | `false` | Enable OpenTelemetry log correlation | | config.logging.masking | object | `{"enabled":true,"fields":["password","secret","token","api_key","access_token","refresh_token","client_secret"],"headers":["Authorization","X-API-Key","Cookie","X-Auth-Token","X-Forwarded-Authorization","X-HyperFleet-Identity"]}` | Sensitive-data masking for logs | | config.logging.masking.enabled | bool | `true` | Enable log masking | -| config.logging.masking.fields | list | `["password","secret","token","api_key","access_token","refresh_token","client_secret"]` | Field names whose values are redacted in logs | | config.logging.masking.headers | list | `["Authorization","X-API-Key","Cookie","X-Auth-Token","X-Forwarded-Authorization","X-HyperFleet-Identity"]` | HTTP headers whose values are redacted in logs | -| config.logging.otel | object | `{"enabled":false}` | OpenTelemetry tracing integration. See the [tracing standard](https://github.com/openshift-hyperfleet/architecture/blob/main/hyperfleet/standards/tracing.md#configuration). | -| config.logging.otel.enabled | bool | `false` | Enable OpenTelemetry log correlation | -| config.logging.output | string | `"stdout"` | Log output destination | +| config.logging.masking.fields | list | `["password","secret","token","api_key","access_token","refresh_token","client_secret"]` | Field names whose values are redacted in logs | | config.metrics | object | `{"deletion_stuck_threshold":"30m","host":"0.0.0.0","label_metrics_inclusion_duration":"168h","port":9090,"tls":{"enabled":false}}` | Prometheus metrics endpoint settings | -| config.metrics.deletion_stuck_threshold | string | `"30m"` | Threshold after which a deletion is considered stuck | | config.metrics.host | string | `"0.0.0.0"` | Listen address (must be `0.0.0.0` for in-cluster access) | -| config.metrics.label_metrics_inclusion_duration | string | `"168h"` | Duration window for label-based metric inclusion | | config.metrics.port | int | `9090` | Listen port (must match `ports.metrics`) | | config.metrics.tls | object | `{"enabled":false}` | TLS configuration for the metrics endpoint | | config.metrics.tls.enabled | bool | `false` | Enable TLS on the metrics endpoint | -| config.server | object | `{"host":"0.0.0.0","hostname":"","identity_header":"","jwk":{"cert_file":"","cert_url":""},"jwt":{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""},"port":8000,"timeouts":{"read":"5s","write":"30s"},"tls":{"cert_file":"","enabled":false,"key_file":""}}` | HTTP server settings | -| config.server.host | string | `"0.0.0.0"` | Listen address | -| config.server.hostname | string | `""` | Public hostname advertised by the API (leave empty for auto-detect) | -| config.server.identity_header | string | `""` | HTTP header used to pass caller identity (bypasses JWT when set) | -| config.server.jwk | object | `{"cert_file":"","cert_url":""}` | JWK settings for token verification | -| config.server.jwk.cert_file | string | `""` | Path to a local JWK certificate file | -| config.server.jwk.cert_url | string | `""` | URL to fetch JWK certificates from | -| config.server.jwt | object | `{"audience":"","enabled":false,"identity_claim":"email","issuer_url":""}` | JWT authentication settings | -| config.server.jwt.audience | string | `""` | Expected JWT audience claim | -| config.server.jwt.enabled | bool | `false` | Enable JWT authentication | -| config.server.jwt.identity_claim | string | `"email"` | JWT claim used as the caller identity | -| config.server.jwt.issuer_url | string | `""` | OIDC issuer URL for token validation | -| config.server.port | int | `8000` | Listen port (must match `ports.api`) | -| config.server.timeouts | object | `{"read":"5s","write":"30s"}` | Request timeout settings | -| config.server.timeouts.read | string | `"5s"` | HTTP read timeout | -| config.server.timeouts.write | string | `"30s"` | HTTP write timeout | -| config.server.tls | object | `{"cert_file":"","enabled":false,"key_file":""}` | TLS configuration for the API server | -| config.server.tls.cert_file | string | `""` | Path to TLS certificate file | -| config.server.tls.enabled | bool | `false` | Enable TLS on the API listener | -| config.server.tls.key_file | string | `""` | Path to TLS key file | +| config.metrics.label_metrics_inclusion_duration | string | `"168h"` | Duration window for label-based metric inclusion | +| config.metrics.deletion_stuck_threshold | string | `"30m"` | Threshold after which a deletion is considered stuck | +| config.health | object | `{"db_ping_timeout":"2s","host":"0.0.0.0","port":8080,"shutdown_timeout":"20s","tls":{"enabled":false}}` | Health check endpoint settings | +| config.health.host | string | `"0.0.0.0"` | Listen address (must be `0.0.0.0` for probe access) | +| config.health.port | int | `8080` | Listen port (must match `ports.health`) | +| config.health.tls | object | `{"enabled":false}` | TLS configuration for the health endpoint | +| config.health.tls.enabled | bool | `false` | Enable TLS on the health endpoint | +| config.health.shutdown_timeout | string | `"20s"` | Graceful shutdown timeout | +| config.health.db_ping_timeout | string | `"2s"` | Timeout for the database liveness ping | +| config.adapters | object | `{"required":{"cluster":[],"nodepool":[]}}` | Adapters required for resources to reach "Ready" state. Production deployments should list all expected adapters. | +| config.adapters.required | object | `{"cluster":[],"nodepool":[]}` | Adapters required for cluster resources | +| config.adapters.required.cluster | list | `[]` | Required cluster adapters (e.g. `["validation", "dns", "pullsecret", "hypershift"]`) | +| config.adapters.required.nodepool | list | `[]` | Required nodepool adapters (e.g. `["validation", "hypershift"]`) | +| serviceAccount | object | `{"annotations":{},"create":true,"name":""}` | ServiceAccount configuration | +| serviceAccount.create | bool | `true` | Create a ServiceAccount for the API server | +| serviceAccount.annotations | object | `{}` | Annotations added to the ServiceAccount (e.g. for Workload Identity) | +| serviceAccount.name | string | `""` | Override the ServiceAccount name (defaults to the release fullname) | +| podAnnotations | object | `{}` | Additional annotations applied to all pods | +| podLabels | object | `{}` | Additional labels applied to all pods | +| podSecurityContext | object | `{"fsGroup":65532,"runAsNonRoot":true,"runAsUser":65532}` | Pod-level security context | +| podSecurityContext.fsGroup | int | `65532` | Filesystem group for volume mounts | +| podSecurityContext.runAsNonRoot | bool | `true` | Run all containers as non-root | +| podSecurityContext.runAsUser | int | `65532` | UID for all containers | +| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"seccompProfile":{"type":"RuntimeDefault"}}` | Container-level security context | +| securityContext.allowPrivilegeEscalation | bool | `false` | Disallow privilege escalation | +| securityContext.readOnlyRootFilesystem | bool | `true` | Mount root filesystem as read-only | +| service | object | `{"type":"ClusterIP"}` | Kubernetes Service configuration | +| service.type | string | `"ClusterIP"` | Service type (`ClusterIP`, `LoadBalancer`, `NodePort`) | +| resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | CPU and memory resource requests and limits | +| nodeSelector | object | `{}` | Node selector constraints for pod scheduling | +| tolerations | list | `[]` | Tolerations for pod scheduling | +| affinity | object | `{}` | Affinity rules for pod scheduling | +| autoscaling | object | `{"enabled":false,"maxReplicas":10,"minReplicas":1,"targetCPUUtilizationPercentage":80,"targetMemoryUtilizationPercentage":80}` | Horizontal Pod Autoscaler configuration | +| autoscaling.enabled | bool | `false` | Enable the HPA | +| autoscaling.minReplicas | int | `1` | Minimum number of replicas | +| autoscaling.maxReplicas | int | `10` | Maximum number of replicas | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | +| autoscaling.targetMemoryUtilizationPercentage | int | `80` | Target memory utilization percentage | +| podDisruptionBudget | object | `{"enabled":false,"minAvailable":1}` | PodDisruptionBudget configuration | +| podDisruptionBudget.enabled | bool | `false` | Enable the PDB | +| podDisruptionBudget.minAvailable | int | `1` | Minimum number of available pods during disruption | | database | object | `{"external":{"enabled":false,"secretName":""},"postgresql":{"database":"hyperfleet","enabled":true,"image":"docker.io/library/postgres:14.2","password":"hyperfleet-dev-password","persistence":{"enabled":false,"size":"1Gi","storageClass":""},"port":5432,"resources":{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}},"user":"hyperfleet"}}` | Database infrastructure settings. For **production**, set `database.external.enabled=true` and supply a secret with connection details. For **development**, the built-in PostgreSQL pod is enabled by default. | | database.external | object | `{"enabled":false,"secretName":""}` | External database configuration (production) | | database.external.enabled | bool | `false` | Use an external database instead of the built-in PostgreSQL | | database.external.secretName | string | `""` | Name of an existing Secret with keys: `db.host`, `db.port`, `db.name`, `db.user`, `db.password` | | database.postgresql | object | `{"database":"hyperfleet","enabled":true,"image":"docker.io/library/postgres:14.2","password":"hyperfleet-dev-password","persistence":{"enabled":false,"size":"1Gi","storageClass":""},"port":5432,"resources":{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}},"user":"hyperfleet"}` | Built-in PostgreSQL for development and testing | -| database.postgresql.database | string | `"hyperfleet"` | Database name | | database.postgresql.enabled | bool | `true` | Deploy a single-pod PostgreSQL instance | | database.postgresql.image | string | `"docker.io/library/postgres:14.2"` | PostgreSQL container image | +| database.postgresql.database | string | `"hyperfleet"` | Database name | +| database.postgresql.user | string | `"hyperfleet"` | Database user | | database.postgresql.password | string | `"hyperfleet-dev-password"` | Database password (**development only** — use a Secret in production) | +| database.postgresql.port | int | `5432` | PostgreSQL listen port | +| database.postgresql.resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | Resource requests and limits for the PostgreSQL pod | | database.postgresql.persistence | object | `{"enabled":false,"size":"1Gi","storageClass":""}` | Persistent volume configuration for PostgreSQL data | | database.postgresql.persistence.enabled | bool | `false` | Enable persistent storage (uses emptyDir when disabled) | | database.postgresql.persistence.size | string | `"1Gi"` | Volume size | | database.postgresql.persistence.storageClass | string | `""` | StorageClass name (empty for cluster default) | -| database.postgresql.port | int | `5432` | PostgreSQL listen port | -| database.postgresql.resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | Resource requests and limits for the PostgreSQL pod | -| database.postgresql.user | string | `"hyperfleet"` | Database user | -| extraEnv | list | `[]` | Additional environment variables injected into the API container. Use sparingly — prefer `config.*` values above. | -| extraVolumeMounts | list | `[]` | Extra volume mounts added to the API container | -| extraVolumes | list | `[]` | Extra volumes added to the pod | -| fullnameOverride | string | `""` | Override the full release name used in resource names | -| image.pullPolicy | string | `"Always"` | Image pull policy | -| image.registry | string | `"CHANGE_ME"` | Container image registry (no default — must be set) | -| image.repository | string | `"CHANGE_ME"` | Container image repository (no default — must be set) | -| image.tag | string | `""` | Image tag (no default — must be set via `--set image.tag=`) | -| imagePullSecrets | list | `[]` | Secrets for pulling images from private registries | | monitoring | object | `{"podMonitoring":{"additionalLabels":{},"enabled":false,"interval":"30s","metricRelabeling":[],"tlsConfig":{"insecureSkipVerify":false}},"prometheusRule":{"additionalLabels":{},"enabled":false,"namespace":"","rules":{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}}}` | Monitoring and alerting configuration | | monitoring.podMonitoring | object | `{"additionalLabels":{},"enabled":false,"interval":"30s","metricRelabeling":[],"tlsConfig":{"insecureSkipVerify":false}}` | PodMonitoring for Google Managed Prometheus (GMP) scraping | -| monitoring.podMonitoring.additionalLabels | object | `{}` | Additional labels for the PodMonitoring resource | | monitoring.podMonitoring.enabled | bool | `false` | Create a PodMonitoring resource | | monitoring.podMonitoring.interval | string | `"30s"` | Scrape interval | +| monitoring.podMonitoring.additionalLabels | object | `{}` | Additional labels for the PodMonitoring resource | | monitoring.podMonitoring.metricRelabeling | list | `[]` | Metric relabel configs to apply to samples before ingestion | | monitoring.podMonitoring.tlsConfig | object | `{"insecureSkipVerify":false}` | TLS configuration when config.metrics.tls.enabled=true | | monitoring.podMonitoring.tlsConfig.insecureSkipVerify | bool | `false` | Disable target certificate validation (e.g. for self-signed certs) | | monitoring.prometheusRule | object | `{"additionalLabels":{},"enabled":false,"namespace":"","rules":{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}}` | PrometheusRule for alerting | -| monitoring.prometheusRule.additionalLabels | object | `{}` | Additional labels for PrometheusRule discovery | | monitoring.prometheusRule.enabled | bool | `false` | Create PrometheusRule resources | +| monitoring.prometheusRule.additionalLabels | object | `{}` | Additional labels for PrometheusRule discovery | | monitoring.prometheusRule.namespace | string | `""` | Namespace to create the PrometheusRule in (defaults to release namespace) | | monitoring.prometheusRule.rules | object | `{"deletionStuck":{"for":"5m","runbookUrl":""},"deletionTimeout":{"for":"30m","runbookUrl":""}}` | Alert rule configuration | | monitoring.prometheusRule.rules.deletionStuck | object | `{"for":"5m","runbookUrl":""}` | Alert when a deletion is stuck | @@ -150,53 +174,29 @@ helm install hyperfleet-api oci://REGISTRY/hyperfleet-api \ | monitoring.prometheusRule.rules.deletionTimeout | object | `{"for":"30m","runbookUrl":""}` | Alert when a deletion times out | | monitoring.prometheusRule.rules.deletionTimeout.for | string | `"30m"` | Duration before the alert fires | | monitoring.prometheusRule.rules.deletionTimeout.runbookUrl | string | `""` | Runbook URL included in the alert | -| nameOverride | string | `""` | Override the chart name used in resource names | -| nativeSidecars | list | `[]` | Native sidecar containers (Kubernetes 1.28+). Native sidecars are init containers with `restartPolicy: Always` — they start before other init containers and keep running throughout the pod lifecycle. Use this for database proxies that must be available during `db-migrate`. Each entry is a full Kubernetes container spec. | -| nodeSelector | object | `{}` | Node selector constraints for pod scheduling | -| podAnnotations | object | `{}` | Additional annotations applied to all pods | -| podDisruptionBudget | object | `{"enabled":false,"minAvailable":1}` | PodDisruptionBudget configuration | -| podDisruptionBudget.enabled | bool | `false` | Enable the PDB | -| podDisruptionBudget.minAvailable | int | `1` | Minimum number of available pods during disruption | -| podLabels | object | `{}` | Additional labels applied to all pods | -| podSecurityContext | object | `{"fsGroup":65532,"runAsNonRoot":true,"runAsUser":65532}` | Pod-level security context | -| podSecurityContext.fsGroup | int | `65532` | Filesystem group for volume mounts | -| podSecurityContext.runAsNonRoot | bool | `true` | Run all containers as non-root | -| podSecurityContext.runAsUser | int | `65532` | UID for all containers | -| ports | object | `{"api":8000,"health":8080,"metrics":9090}` | Container ports exposed by the API server. These must match the corresponding application config values (`config.server.port`, `config.health.port`, `config.metrics.port`). | -| ports.api | int | `8000` | API server port | -| ports.health | int | `8080` | Health check endpoint port | -| ports.metrics | int | `9090` | Prometheus metrics endpoint port | -| replicaCount | int | `1` | Number of API server replicas | -| resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | CPU and memory resource requests and limits | -| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"seccompProfile":{"type":"RuntimeDefault"}}` | Container-level security context | -| securityContext.allowPrivilegeEscalation | bool | `false` | Disallow privilege escalation | -| securityContext.readOnlyRootFilesystem | bool | `true` | Mount root filesystem as read-only | -| service | object | `{"type":"ClusterIP"}` | Kubernetes Service configuration | -| service.type | string | `"ClusterIP"` | Service type (`ClusterIP`, `LoadBalancer`, `NodePort`) | -| serviceAccount | object | `{"annotations":{},"create":true,"name":""}` | ServiceAccount configuration | -| serviceAccount.annotations | object | `{}` | Annotations added to the ServiceAccount (e.g. for Workload Identity) | -| serviceAccount.create | bool | `true` | Create a ServiceAccount for the API server | -| serviceAccount.name | string | `""` | Override the ServiceAccount name (defaults to the release fullname) | | serviceMonitor | object | `{"enabled":false,"interval":"30s","labels":{},"namespace":"","scrapeTimeout":"10s"}` | ServiceMonitor for Prometheus Operator scrape configuration | | serviceMonitor.enabled | bool | `false` | Create a ServiceMonitor resource | | serviceMonitor.interval | string | `"30s"` | Scrape interval | +| serviceMonitor.scrapeTimeout | string | `"10s"` | Scrape timeout | | serviceMonitor.labels | object | `{}` | Additional labels for ServiceMonitor discovery | | serviceMonitor.namespace | string | `""` | Namespace to create the ServiceMonitor in (defaults to release namespace) | -| serviceMonitor.scrapeTimeout | string | `"10s"` | Scrape timeout | -| sidecars | list | `[]` | Regular sidecar containers. These start after init containers complete. Use `nativeSidecars` above for containers that must be available during init (e.g. database proxies). Each entry is a full Kubernetes container spec. | -| tolerations | list | `[]` | Tolerations for pod scheduling | | tracing | object | `{"enabled":false,"otlpEndpoint":"","otlpProtocol":"grpc","propagators":"tracecontext,baggage","sampler":"parentbased_traceidratio","samplerArg":"1.0","serviceName":"hyperfleet-api"}` | Distributed tracing configuration (OpenTelemetry) | | tracing.enabled | bool | `false` | Enable trace export | +| tracing.serviceName | string | `"hyperfleet-api"` | Service name reported in traces | | tracing.otlpEndpoint | string | `""` | OTLP exporter endpoint (traces go to stdout when empty) | | tracing.otlpProtocol | string | `"grpc"` | OTLP protocol (`grpc` or `http/protobuf`) | -| tracing.propagators | string | `"tracecontext,baggage"` | Context propagation formats | | tracing.sampler | string | `"parentbased_traceidratio"` | Sampler type | | tracing.samplerArg | string | `"1.0"` | Sampling rate (`1.0` for dev, `0.01` for production) | -| tracing.serviceName | string | `"hyperfleet-api"` | Service name reported in traces | +| tracing.propagators | string | `"tracecontext,baggage"` | Context propagation formats | +| nativeSidecars | list | `[]` | Native sidecar containers (Kubernetes 1.28+). Native sidecars are init containers with `restartPolicy: Always` — they start before other init containers and keep running throughout the pod lifecycle. Use this for database proxies that must be available during `db-migrate`. Each entry is a full Kubernetes container spec. | +| sidecars | list | `[]` | Regular sidecar containers. These start after init containers complete. Use `nativeSidecars` above for containers that must be available during init (e.g. database proxies). Each entry is a full Kubernetes container spec. | | validationSchema | object | `{"content":"","enabled":false,"existingConfigMap":""}` | Validation schema configuration. Supply a custom OpenAPI schema for cluster/nodepool spec validation. When enabled, the schema is mounted into the container and every create/update request is validated against it. The API will fail to start if the schema is invalid. | -| validationSchema.content | string | `""` | Inline OpenAPI 3.0 schema content. Must define `ClusterSpec` and `NodePoolSpec` under `components.schemas`. | | validationSchema.enabled | bool | `false` | Enable spec validation | | validationSchema.existingConfigMap | string | `""` | Use an existing ConfigMap (must contain an `openapi.yaml` key). When set, `validationSchema.content` is ignored. | +| validationSchema.content | string | `""` | Inline OpenAPI 3.0 schema content. Must define `ClusterSpec` and `NodePoolSpec` under `components.schemas`. | +| extraEnv | list | `[]` | Additional environment variables injected into the API container. Use sparingly — prefer `config.*` values above. | +| extraVolumeMounts | list | `[]` | Extra volume mounts added to the API container | +| extraVolumes | list | `[]` | Extra volumes added to the pod | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs](https://github.com/norwoodj/helm-docs) From 86e7f5104bb189be962eef1cd8a83bd6a54e86fe Mon Sep 17 00:00:00 2001 From: Cristiano Veiga Date: Mon, 15 Jun 2026 12:09:20 -0400 Subject: [PATCH 5/5] feat: add helm template test cases for PodMonitoring Co-Authored-By: Claude --- Makefile | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Makefile b/Makefile index 4a737cf..0eb44c1 100755 --- a/Makefile +++ b/Makefile @@ -329,6 +329,29 @@ test-helm: $(KUBECONFORM) verify-helm-docs ## Test Helm charts (lint, template, --set serviceMonitor.interval=15s | $(KUBECONFORM) $(KUBECONFORM_FLAGS) @echo "ServiceMonitor config template OK" @echo "" + @echo "Testing template with PodMonitoring enabled..." + helm template test-release charts/ \ + --set image.registry=quay.io \ + --set image.repository=openshift-hyperfleet/hyperfleet-api \ + --set image.tag=test \ + --set 'adapters.cluster=["validation"]' \ + --set 'adapters.nodepool=["validation"]' \ + --set monitoring.podMonitoring.enabled=true \ + --set monitoring.podMonitoring.interval=15s | $(KUBECONFORM) $(KUBECONFORM_FLAGS) -ignore-missing-schemas + @echo "PodMonitoring config template OK" + @echo "" + @echo "Testing template with PodMonitoring and TLS enabled..." + helm template test-release charts/ \ + --set image.registry=quay.io \ + --set image.repository=openshift-hyperfleet/hyperfleet-api \ + --set image.tag=test \ + --set 'adapters.cluster=["validation"]' \ + --set 'adapters.nodepool=["validation"]' \ + --set monitoring.podMonitoring.enabled=true \ + --set config.metrics.tls.enabled=true \ + --set monitoring.podMonitoring.tlsConfig.insecureSkipVerify=true | $(KUBECONFORM) $(KUBECONFORM_FLAGS) -ignore-missing-schemas + @echo "PodMonitoring with TLS config template OK" + @echo "" @echo "Testing template with auth disabled..." helm template test-release charts/ \ --set image.registry=quay.io \