Skip to content

fix(tenant): render the chart without the platform's injected values - #3636

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/tenant-helm-lint
Aug 8, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/tenant-helm-lint

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

helm lint packages/apps/tenant fails on main with three errors. This removes two of the three; the branch name notwithstanding, lint still exits 1 afterwards on the third, which is a repository-wide convention question rather than a defect in this chart.

The chart cannot be rendered outside a cluster at all. templates/keycloakgroups.yaml reads index .Values._cluster "oidc-enabled", and _cluster is injected by the platform, so it is absent whenever the chart is rendered on its own. index on a nil map is a hard error, not an empty result, so both helm lint and a plain helm template die on line 1.

The idiom the neighbouring charts use for the same lookup does not rescue this on its own, which is worth stating because it looks exactly like a guard. (index .Values._cluster "x") | default "y" still fails: index is evaluated first and errors on the nil map, so default never receives 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. A chart is broken only when it neither declares the empty map nor guards at the use site, and a lint sweep across the catalog found 27 in that state. Guarding at the use site is the choice here because it makes the template correct whatever values.yaml contains, and because _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:

{{- $oidcEnabled := index (.Values._cluster | default dict) "oidc-enabled" }}

This is behaviour-preserving, and that is checked rather than asserted. 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. Rendering before and after gives byte-identical output:

values before after
_cluster.oidc-enabled: "true" 4 KeycloakRealmGroups byte-identical
_cluster.oidc-enabled: "false" no groups byte-identical
no values at all hard error renders, no groups

Worth recording how that comparison has to be run, because the obvious way proves nothing. The KeycloakRealmGroup block sits behind .Capabilities.APIVersions.Has "v1.edp.epam.com/v1", which is false under a plain helm template, so a first pass rendered the same output in both the enabled and disabled cases, and a diff of those two would have compared one empty result against another. The renders above pass --api-versions v1.edp.epam.com/v1 so the guarded branch actually executes. Anyone proving neutrality on a capability-gated template needs that flag or the proof is vacuous.

Chart.yaml does not declare cozy-lib, which is present as a symlink under charts/, so lint reports missing chart metadata. It is declared the way packages/apps/qdrant does it, that being the one chart in the tree which declares it and does not carry this error. Rendering is unchanged by the declaration, checked the same way as above.

Two things that could plausibly have broken and did not, checked rather than assumed: rendering from a packaged helm package artifact is byte-identical, and the charts/cozy-lib symlink is still expanded into the tgz with the dependency declared. And neither hack/update-crd.sh nor the fix-charts target in packages/apps/Makefile reads the dependencies: block, so nothing needs regenerating.

The icon error is left in place on purpose, and the relative path is load-bearing. helm lint wants a URL with a scheme, and /logos/tenant.svg has none. But hack/update-crd.sh consumes exactly this form: it rewrites a leading slash to ./, then reads that file off disk and base64-encodes it into .spec.dashboard.icon of the generated ApplicationDefinition. packages/apps/tenant/logos/tenant.svg is that file. Give the chart a URL with a scheme and the generator's [[ ! -f "$ICON_PATH" ]] check no longer finds anything to encode. So the relative form is a requirement of the pipeline rather than a stylistic convention, all 31 icon-carrying charts use it, and satisfying helm lint here means changing the generator, not the charts. That is a repository-wide decision and not this PR's.

tests/keycloakgroups_oidc_test.yaml pins the gate in all three directions: four realm groups when oidc-enabled is "true", none when it is "false", and none when the platform injected no _cluster at all. The last case is the one the guard exists for, and it needs no mocking, since _cluster is absent from values.yaml and "not injected" is just the chart's default state. Against the unguarded template that suite fails with index of untyped nil. The chart's other seven suites pass identically with and without the fix, which is precisely why the change needed its own.

Two limits of the new suite, stated rather than left to be found. It pins how many documents render, not which: renaming the four groups or breaking a realmRef would pass. And the template's second gate, .Capabilities.APIVersions.Has "v1.edp.epam.com/v1", is enabled for the whole suite, so no case covers its absence. Both are worth closing on the next touch of this file; neither weakens what the suite does pin, which is that the chart renders at all when the platform injected nothing.

A trap for anyone reproducing this by hand: the gate compares against the string "true", so --set _cluster.oidc-enabled=true passes a YAML boolean and renders nothing, while --set-string renders the four groups. The platform writes a genuine string, and it survives the round-trip through a Secret because toYaml quotes it. Note also that this chart's helpers reject a release name that does not begin with tenant-, which will silently render nothing for either form.

One note in case a future change teaches the linter to inject the platform's values, which would hide this class of error without fixing it: the guard is still worth having, because it is what lets helm template packages/apps/tenant succeed with no values file at all. A chart that renders standalone can be inspected, diffed and unit-tested without reproducing the platform around it.

Screenshots

Downstream repositories

Release note

fix(tenant): the tenant chart now renders without the values the platform injects, so `helm template` works on it standalone, and its `cozy-lib` dependency is declared in Chart.yaml; this clears two of the three `helm lint` errors it reported, the remaining one being the repository-wide relative icon URL

Summary by CodeRabbit

  • New Features

    • Added support for using the shared Cozy library in tenant deployments.
  • Bug Fixes

    • Improved tenant configuration rendering when cluster settings are unavailable.
    • OIDC-related Keycloak groups now render reliably when OIDC is enabled and required platform capabilities are present.
    • Preserved expected behavior when OIDC is disabled or configuration is omitted.
  • Tests

    • Added coverage for enabled, disabled, and missing cluster configuration scenarios.

`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 <[email protected]>
Signed-off-by: Aleksei Sviridkin <[email protected]>
@github-actions github-actions Bot added area/tenant Issues or PRs related to the tenant chart and multi-tenancy kind/bug Categorizes issue or PR as related to a bug size/M This PR changes 30-99 lines, ignoring generated files labels Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 99cf4c6b-2139-46d6-8c44-60db77bdf983

📥 Commits

Reviewing files that changed from the base of the PR and between 605030b and bd3f3b9.

📒 Files selected for processing (3)
  • packages/apps/tenant/Chart.yaml
  • packages/apps/tenant/templates/keycloakgroups.yaml
  • packages/apps/tenant/tests/keycloakgroups_oidc_test.yaml

📝 Walkthrough

Walkthrough

The tenant chart now includes the local cozy-lib dependency. Keycloak group rendering safely handles missing _cluster values. Helm tests cover enabled, disabled, and absent OIDC configuration.

Changes

Tenant OIDC rendering

Layer / File(s) Summary
Add local chart dependency
packages/apps/tenant/Chart.yaml
The tenant chart declares the local cozy-lib dependency.
Harden OIDC group rendering
packages/apps/tenant/templates/keycloakgroups.yaml, packages/apps/tenant/tests/keycloakgroups_oidc_test.yaml
The template defaults missing _cluster values to an empty map. Tests validate rendering when OIDC is enabled, disabled, or absent.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: ivanhunters

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enabling standalone tenant chart rendering without platform-injected values.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tenant-helm-lint

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit d956536 into main Aug 8, 2026
14 of 15 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/tenant-helm-lint branch August 8, 2026 07:55
Aleksei Sviridkin (lexfrei) added a commit that referenced this pull request Aug 14, 2026
…ason (#3661)

## What this PR does

Two tests in
`packages/apps/tenant/tests/vmo_webhook_dependson_test.yaml` say the
seaweedfs and gateway HelmReleases deliberately carry no operator gate,
and assert that with `notExists: spec.dependsOn` and nothing else. A
bare absence assert also passes when the template renders no document at
all, so both stayed green with those two releases switched off entirely.
With `seaweedfs: false, gateway: false` the suite still reported 5 of 5
passing; pinning `metadata.name` next to the absence turns the same run
into 2 failures.

An earlier check on these asserts injected a `dependsOn` and watched
them redden. That proves they catch an added gate, which is the other
direction. For a negative assert the direction that matters is the
document going away, and that one was never covered.

Pairing `equal: metadata.name` with the absence is not a new idiom
introduced here. `no_upgrade_force_test.yaml`, in the same directory,
already anchors all seven of its releases that way, and this brings the
last two suites in the chart into line with it.

Both suite headers also carried a reason for their `templates:` list
that has expired: that rendering the whole chart fails because
`keycloakgroups.yaml` indexes `.Values._cluster` unguarded. #3636 added
the guard, so a no-values render of the chart now succeeds. The list is
still load-bearing, for the reason `no_upgrade_force_test.yaml` already
records — it keeps an unrelated template breaking out of this suite's
result. Checked by adding a nil dereference to a template neither suite
asserts on: both suites stay green with their lists and error without
them.

### Screenshots

Not a UI change.

### Downstream repositories

Walked the trigger map against the diff. It touches two files, both
under `packages/apps/tenant/tests/`, and no chart, template, values file
or tooling. Nothing in the map keys on test files.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:

### Release note

```release-note
NONE
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/tenant Issues or PRs related to the tenant chart and multi-tenancy kind/bug Categorizes issue or PR as related to a bug size/M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant