Files
cozystack/packages/extra/monitoring/values.yaml
IvanHunters c31a9f8ec3 fix(monitoring): promote oidc.users[].role to required + regenerate schema
The `OIDCUser` typedef in `packages/extra/monitoring/values.yaml`
declared only `@field {string} email`; `cozyvalues-gen` regenerated
`values.schema.json`, `README.md`, and the mirrored `openAPISchema`
on the `Monitoring` ApplicationDefinition WITHOUT a `role` field.
Consequences:

- The `@enum OIDCRole` declaration was orphan — referenced from
  nowhere, so the schema's items object had `required: ["email"]`
  and one property.
- A dashboard-driven CR would offer a form without any role picker;
  a schema-conformant `{email}` submission would render into the
  users-Job with `role: null`, `jq --raw-output` yields the string
  `"null"`, the `POST /api/orgs/1/users` body carries
  `"role":"null"`, Grafana returns HTTP 400, and the post-install
  hook exhausts `backoffLimit: 6` — HelmRelease never becomes Ready.
- Unit + e2e tests all hardcoded `role: Admin` / `role: Viewer`, so
  the contract mismatch never surfaced in CI.

Fix:

- Add `@field {OIDCRole} role - ...` to the `OIDCUser` typedef in
  `packages/extra/monitoring/values.yaml` so cozyvalues-gen materialises
  the field with enum + required.
- Regenerate `values.schema.json`, `README.md`, and mirrored
  `openAPISchema` via `make generate`.
- Add `monitoring.oidc.assertUsersRoleShape` template-level guard
  (same shape as `assertUsersEmailShape`) so a stale-schema CR or a
  direct-helm invocation fails at render time with a legible error
  instead of at hook execution with a Grafana 400.
- Consolidate all `spec.oidc.users` guards into a single
  `monitoring.oidc.assertUsersPrologue` include and call it once from
  `oidc-users-job.yaml`, ordering the incompat check
  (`assertCustomSecretRefUsersEmpty`) before the per-entry role /
  email regexes so operators see the more actionable message first.
  Drop the duplicate include from `grafana.yaml` (Job template renders
  in every mode anyway).
- Two new helm-unittest cases lock the regression:
  `oidc.users entry missing role fails the render` and
  `oidc.users entry with unknown role fails the render`.
- Point the previous `secretRef + users` incompat unit test at the
  users-Job template since that's where the prologue now fires.
- `docs/oidc-grafana.md`: correct the failed-hook post-mortem window
  claim — with `ttlSecondsAfterFinished: 3600` the Kubernetes TTL
  controller reaps the Job + Pod 1h after the terminal condition,
  not "until the next helm-upgrade".

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-15 15:31:45 +03:00

227 lines
9.3 KiB
YAML

## @section Common parameters
##
## @param {string} host - The hostname used to access Grafana externally (defaults to 'grafana' subdomain for the tenant host).
host: ""
##
## @section Metrics storage configuration
##
## @typedef {struct} Request - Minimum guaranteed resources.
## @field {quantity} [cpu] - CPU request.
## @field {quantity} [memory] - Memory request.
## @typedef {struct} Limit - Maximum allowed resources.
## @field {quantity} [cpu] - CPU limit.
## @field {quantity} [memory] - Memory limit.
## @typedef {struct} VMComponent - VictoriaMetrics component configuration.
## @field {Request} [minAllowed] - Minimum guaranteed resources.
## @field {Limit} [maxAllowed] - Maximum allowed resources.
## @typedef {struct} Resources - Combined resource configuration.
## @field {Request} [requests] - Resource requests.
## @field {Limit} [limits] - Resource limits.
## @typedef {struct} MetricsStorage - Configuration of metrics storage instance.
## @field {string} name - Name of the storage instance.
## @field {string} retentionPeriod - Retention period for metrics.
## @field {string} deduplicationInterval - Deduplication interval for metrics.
## @field {string} storage="10Gi" - Persistent volume size.
## @field {string} [storageClassName] - StorageClass used for the data.
## @field {VMComponent} [vminsert] - Configuration for vminsert.
## @field {VMComponent} [vmselect] - Configuration for vmselect.
## @field {VMComponent} [vmstorage] - Configuration for vmstorage.
## @param {[]MetricsStorage} metricsStorages - Configuration of metrics storage instances.
metricsStorages:
## Example:
## metricsStorages:
## - name: shortterm
## retentionPeriod: "3d"
## deduplicationInterval: "15s"
## storage: 10Gi
## storageClassName: ""
## vminsert:
## minAllowed:
## cpu: 200m
## memory: 512Mi
## maxAllowed:
## cpu: 1500m
## memory: 3Gi
## vmselect:
## minAllowed:
## cpu: 300m
## memory: 1Gi
## maxAllowed:
## cpu: 3500m
## memory: 6Gi
## vmstorage:
## minAllowed:
## cpu: 500m
## memory: 2Gi
## maxAllowed:
## cpu: 4000m
## memory: 8Gi
- name: shortterm
retentionPeriod: "3d"
deduplicationInterval: "15s"
storage: 10Gi
storageClassName: ""
- name: longterm
retentionPeriod: "14d"
deduplicationInterval: "5m"
storage: 10Gi
storageClassName: ""
##
## @section Logs storage configuration
##
## @typedef {struct} LogsStorage - Configuration of logs storage instance.
## @field {string} name - Name of the storage instance.
## @field {string} retentionPeriod="1" - Retention period for logs.
## @field {string} storage="10Gi" - Persistent volume size.
## @field {string} storageClassName="replicated" - StorageClass used to store the data.
## @param {[]LogsStorage} logsStorages - Configuration of logs storage instances.
logsStorages:
- name: generic
retentionPeriod: "1"
storage: 10Gi
storageClassName: replicated
##
## @section Alerta configuration
##
## @typedef {struct} TelegramAlerts - Telegram alert configuration.
## @field {string} token - Telegram bot token.
## @field {string} chatID - Telegram chat ID(s), separated by commas.
## @field {[]string} [disabledSeverity] - List of severities without alerts (e.g. ["informational","warning"]).
## @typedef {struct} SlackAlerts - Slack alert configuration.
## @field {string} url - Configuration uri for Slack alerts.
## @field {[]string} [disabledSeverity] - List of severities without alerts (e.g. ["informational","warning"]).
## @typedef {struct} Alerts - Alert routing configuration.
## @field {TelegramAlerts} [telegram] - Configuration for Telegram alerts.
## @field {SlackAlerts} [slack] - Configuration for Slack alerts.
## @typedef {struct} Alerta - Configuration for the Alerta service.
## @field {string} [storage] - Persistent volume size for the database.
## @field {string} [storageClassName] - StorageClass used for the database.
## @field {Resources} [resources] - Resource configuration.
## @field {Alerts} [alerts] - Alert routing configuration.
## @param {Alerta} alerta - Configuration for the Alerta service.
alerta:
storage: 10Gi
storageClassName: ""
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
alerts:
telegram:
token: ""
chatID: ""
disabledSeverity: []
slack:
url: ""
disabledSeverity: []
##
## @section Grafana configuration
##
## @typedef {struct} GrafanaDB - Grafana database configuration.
## @field {string} [size] - Persistent volume size for the database.
## @typedef {struct} Grafana - Grafana service configuration.
## @field {GrafanaDB} [db] - Database configuration.
## @field {Resources} [resources] - Resource configuration.
## @param {Grafana} grafana - Configuration for Grafana.
grafana:
db:
size: 10Gi
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
##
## @section Vmagent configuration
##
## @typedef {struct} VmagentRemoteWrite - Remote write configuration.
## @typedef {stringSlice} VmagentRemoteWriteURLs - List of remoteWrite endpoint URLs.
## @typedef {struct} VmagentExternalLabels - External labels for metrics.
## @field {string} [<key>] - Label value for the given key.
## @typedef {struct} Vmagent - VictoriaMetrics Agent configuration.
## @field {VmagentExternalLabels} [externalLabels] - External labels applied to all metrics.
## @field {VmagentRemoteWrite} [remoteWrite] - Remote write configuration.
## @param {Vmagent} vmagent - Configuration for VictoriaMetrics Agent.
vmagent:
externalLabels:
cluster: cozystack
remoteWrite:
urls:
- http://vminsert-shortterm:8480/insert/0/prometheus
- http://vminsert-longterm:8480/insert/0/prometheus
## inlineScrapeConfig: |
## - job_name: "custom"
## static_configs:
## - targets: ["my-service:9090"]
##
## @section OIDC authentication
##
## @enum {string} OIDCMode - Identity mode for the Grafana instance.
## @value None
## @value System
## @value CustomConfig
## @typedef {struct} OIDCSecretRef - Reference to a Secret carrying an `[auth.generic_oauth]` ini fragment.
## @field {string} [name] - Name of an existing Secret in the release namespace whose `auth.ini` key holds a ready-made `[auth.generic_oauth]` block. Mutually exclusive with `customConfig.config`.
## @typedef {struct} OIDCCustomConfigInline - Inline map of grafana.ini `[auth.generic_oauth]` keys.
## @field {string} [<key>] - Value for the generic_oauth key (e.g. `client_id`, `client_secret`, `auth_url`, `token_url`, `api_url`, `scopes`, `role_attribute_path`).
## @typedef {struct} OIDCCustomConfig - Tenant-supplied AuthN payload; consumed only when `mode: CustomConfig`. Exactly one of `config` or `secretRef.name` must be set.
## @field {OIDCCustomConfigInline} [config] - Inline map of `[auth.generic_oauth]` keys. Written verbatim into the Grafana CR `spec.config`. Mutually exclusive with `secretRef.name`.
## @field {OIDCSecretRef} [secretRef] - Reference to a Secret holding an `auth.ini` fragment; mounted under `/etc/grafana/oidc` and loaded via `GF_PATHS_CUSTOM_INI`.
## @enum {string} OIDCRole - Grafana org role bound to a listed user.
## @value Admin
## @value Editor
## @value Viewer
## @typedef {struct} OIDCUser - Grafana user reconciled into the release's org.
## @field {string} email - Email address matched against the `email` claim from the issuer. The chart pre-provisions a Grafana account with this email so the users-Job can set the org role before the operator's first login (`oauth_allow_insecure_email_lookup = true` glues the two together). Must match a conservative pattern: local part is `[A-Za-z0-9._%+-]+`, domain is `[A-Za-z0-9.-]+\.[A-Za-z]{2,}`. Whitespace, quoted literals, and unusual URL-reserved characters are rejected on schema level so the reconcile-Job's shell handling never has to defend against malformed input.
## @field {OIDCRole} role - Grafana org role to bind: `Admin`, `Editor`, or `Viewer`. Applied to the release's Grafana org (`Main Org.`); server-level `GrafanaAdmin` promotion is out of scope for Phase 1. Required — the users-Job POSTs `{loginOrEmail, role}` to `/api/orgs/1/users` and PATCHes `{role}` to converge across re-runs; omitting the field would render `role: "null"` into the body and fail with HTTP 400.
## @typedef {struct} OIDC - OIDC selector for the Grafana instance (Phase 1 — per-instance audience binding; no per-tenant Keycloak realm).
## @field {OIDCMode} mode - Identity mode. `None`: no OIDC, only the `grafana-admin-password` Secret works. `System`: trust the platform `cozy` realm via a per-instance confidential client and audience binding; zero-config default. `CustomConfig`: trust a tenant-supplied issuer directly (BYO); `cozy` is not in the path.
## @field {OIDCCustomConfig} [customConfig] - Tenant-supplied `[auth.generic_oauth]` payload; consumed only when `mode: CustomConfig`.
## @field {[]OIDCUser} [users] - Users granted access to this Grafana instance's org. Each entry is reconciled into Grafana org membership + role by a chart-owned post-install/post-upgrade Job. Works for both `System` and `CustomConfig` modes; ignored when `mode: None`. Users not listed here get nothing (no default org role from group mapping).
## @param {OIDC} oidc - OIDC authentication for the Grafana instance. See docs/oidc-grafana.md for the operator guide.
oidc:
mode: "None"
customConfig:
config: {}
secretRef:
name: ""
users: []