From bd3f3b93d0b5eeba70d2e2e67ee50eee7584aa4e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Sat, 8 Aug 2026 02:02:38 +0300 Subject: [PATCH] fix(tenant): render the chart without the platform's injected values `helm lint packages/apps/tenant` fails, and two of its three errors are the chart's own. `templates/keycloakgroups.yaml` reads `index .Values._cluster "oidc-enabled"`. `_cluster` is injected by the platform, so it is absent whenever the chart is rendered on its own, and `index` on a nil map is a hard error: `index of untyped nil`. The chart cannot be rendered or linted outside a cluster at all. The idiom the neighbouring charts use for the same lookup does not rescue this on its own, which is worth stating because it looks like it does. `(index .Values._cluster "x") | default "y"` still errors: `index` evaluates first and fails on the nil map, so `default` never sees a result to substitute; it only ever covered a key missing from a map that exists. The spelling alone does not decide whether a chart breaks, though. `packages/system/cozystack-basics` uses it and lints clean, because line 1 of its values.yaml is `_cluster: {}`, which makes the map non-nil before `index` runs. So a chart is broken only when it neither declares the empty map nor guards at the use site. Guarding at the use site is preferred here because it makes the template correct whatever values.yaml contains, which is what lets the chart render standalone; and `_cluster` is platform-internal, while this chart's values.yaml, schema and README are generated together from api/apps/v1alpha1/tenant/types.go as user-facing output. The guard has to sit on the map rather than on the value it returns, hence `index (.Values._cluster | default dict) "oidc-enabled"`. That is behaviour-preserving rather than merely quieter. With `_cluster` present, `| default dict` returns it untouched and the lookup is what it was. With it absent the lookup is nil, the `eq` below is false, and the block stays unrendered, which is what an unconfigured tenant should produce. Rendered both ways before and after, with `--api-versions v1.edp.epam.com/v1` so the capability-gated block actually executes, the output is identical byte for byte: four KeycloakRealmGroups when `oidc-enabled` is `"true"`, none when it is `"false"`. Rendering with no values at all goes from a hard error to a clean render with no groups. `Chart.yaml` also does not declare `cozy-lib`, which is present as a symlink under `charts/`, so lint reports missing chart metadata. Declare it the way `packages/apps/qdrant` does, that being the one chart in the tree that declares it and does not carry this error. Rendering is unchanged by the declaration, checked the same way. `tests/keycloakgroups_oidc_test.yaml` pins all three branches of the gate: four realm groups when `oidc-enabled` is `"true"`, none when it is `"false"`, and none when the platform injected no `_cluster` at all. That third case is the one the guard exists for, and it needs no mock, because `_cluster` is absent from values.yaml and so "not injected" is simply the chart's own default state. Against the unguarded template the suite fails with `index of untyped nil`; the chart's other seven suites pass identically either way, which is what left the change uncovered. The third error, `invalid icon URL '/logos/tenant.svg'`, is deliberately left. Lint wants a URL with a scheme, and all 31 charts in the tree that carry an icon use a relative path, none an absolute one. Changing this one would make it the only chart with a different convention, and choosing the URL a relative path should become is a decision about where those assets are served from, not about lint. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/tenant/Chart.yaml | 4 ++ .../apps/tenant/templates/keycloakgroups.yaml | 9 +++- .../tests/keycloakgroups_oidc_test.yaml | 53 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 packages/apps/tenant/tests/keycloakgroups_oidc_test.yaml diff --git a/packages/apps/tenant/Chart.yaml b/packages/apps/tenant/Chart.yaml index 476359185..1fa3d3253 100644 --- a/packages/apps/tenant/Chart.yaml +++ b/packages/apps/tenant/Chart.yaml @@ -4,3 +4,7 @@ description: Separated tenant namespace icon: /logos/tenant.svg type: application version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process +dependencies: + - name: cozy-lib + version: "*" + repository: "file://charts/cozy-lib" diff --git a/packages/apps/tenant/templates/keycloakgroups.yaml b/packages/apps/tenant/templates/keycloakgroups.yaml index 9e25e60d1..f135a64a9 100644 --- a/packages/apps/tenant/templates/keycloakgroups.yaml +++ b/packages/apps/tenant/templates/keycloakgroups.yaml @@ -1,4 +1,11 @@ -{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} +{{- /* _cluster is injected by the platform, so it is absent whenever the chart + is rendered on its own, as `helm lint` and `helm template` do. `index` + errors on a nil map before any `| default` on its result can apply, so + the guard has to sit on the map rather than on the looked-up value. With + _cluster present this yields exactly what indexing it directly yielded; + with it absent the lookup is nil and the block below stays unrendered, + which is what an unconfigured tenant should produce. */ -}} +{{- $oidcEnabled := index (.Values._cluster | default dict) "oidc-enabled" }} {{- if eq $oidcEnabled "true" }} {{- if .Capabilities.APIVersions.Has "v1.edp.epam.com/v1" }} apiVersion: v1.edp.epam.com/v1 diff --git a/packages/apps/tenant/tests/keycloakgroups_oidc_test.yaml b/packages/apps/tenant/tests/keycloakgroups_oidc_test.yaml new file mode 100644 index 000000000..ae07b17b9 --- /dev/null +++ b/packages/apps/tenant/tests/keycloakgroups_oidc_test.yaml @@ -0,0 +1,53 @@ +suite: tenant keycloak realm groups — the oidc gate survives an uninjected _cluster +templates: + - templates/keycloakgroups.yaml + +# `_cluster` is injected by the platform, so it is simply absent whenever the +# chart is rendered on its own: `helm lint`, `helm template`, and this suite. +# The template reads it with `index`, and `index` on a nil map is a hard error +# rather than an empty result, so before the guard the chart could not be +# rendered outside a cluster at all. +# +# The guard has to sit on the map, not on the value: the spelling used +# elsewhere in the tree, `(index .Values._cluster "x") | default "y"`, does not +# help, because `index` is evaluated first and fails before `default` ever sees +# a result to substitute. +# +# The third case below is the one that pins the fix. It carries no `set:` at +# all, because `_cluster` is absent from values.yaml, so "the platform injected +# nothing" needs no mocking — it is just the chart's own default state. + +release: + name: tenant-alice + namespace: tenant-alice + +capabilities: + apiVersions: + - v1.edp.epam.com/v1 + +tests: + - it: renders the four realm groups when oidc is enabled + set: + _cluster: + oidc-enabled: "true" + asserts: + - hasDocuments: + count: 4 + + - it: renders nothing when oidc is explicitly disabled + # The gate is an equality against the string "true", so anything else is + # off. Pinned alongside the enabled case so a guard that accidentally + # rendered unconditionally would be caught. + set: + _cluster: + oidc-enabled: "false" + asserts: + - hasDocuments: + count: 0 + + - it: renders nothing when the platform injected no _cluster at all + # Fails with `index of untyped nil` without the guard. This is the case the + # fix exists for, and the reason the chart can now be rendered standalone. + asserts: + - hasDocuments: + count: 0