Skip to content

fix(releases): use helm secret namespace for cross-namespace health - #758

Open
Tyagiquamar wants to merge 4 commits into
komodorio:mainfrom
Tyagiquamar:fix/issue-683-release-storage-namespace
Open

Tyagiquamar wants to merge 4 commits into
komodorio:mainfrom
Tyagiquamar:fix/issue-683-release-storage-namespace

Conversation

@Tyagiquamar

@Tyagiquamar Tyagiquamar commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Separate release storage namespace from target manifest namespace, discover release storage namespaces in all-namespaces mode, and align frontend routing with storage namespaces.

Fixes #683

Root cause

Helm allows deploying manifests into a target namespace while storing the release secret or configmap in a distinct storage namespace. Previously:

  1. When -n was omitted (Namespaces: [""]), List() passed "", causing NewRelease to fall back to orig.Namespace instead of discovering the storage namespace.
  2. In ReleaseElement, using a single Namespace field overloaded target display and Helm storage routing: health lookups for manifests without an explicit namespace fell back to the URL parameter, querying the wrong namespace.

Technical solution

  1. Separate Storage and Target Namespaces:
    • In ReleaseElement (pkg/dashboard/handlers/helmHandlers.go), keep Namespace as the release target namespace (orig.Namespace), and add StorageNamespace for where Helm stores release data.
    • In HReleaseToJSON, populate both fields.
    • In Resources handler, fall back to rel.Orig.Namespace for manifest objects missing an explicit metadata.namespace.
  2. All-Namespaces Discovery:
    • In Releases.List() (pkg/dashboard/objects/releases.go), when running in all-namespaces mode (Namespaces: [""]), resolve visible cluster namespaces via K8s.GetNameSpaces() and iterate through each namespace, setting client.AllNamespaces = false to capture the exact storage namespace for each release. If namespace listing is unavailable, fall back to client.AllNamespaces = true.
  3. Frontend Routing Alignment:
    • Added storageNamespace to Release in frontend/src/data/types.ts.
    • Updated apiService.getResourceStatus and InstalledPackageCard onClick navigation to route via storageNamespace || namespace.
  4. Automated Tests:
    • Updated TestHReleaseToJSON in pkg/dashboard/handlers/helmHandlers_test.go to assert Namespace and StorageNamespace are preserved separately.
    • Added TestListReleases_StorageNamespace in pkg/dashboard/objects/releases_test.go verifying that releases with differing target and storage namespaces preserve both values.

Test plan

  • Validated pkg/dashboard/handlers and pkg/dashboard/objects tests in Docker (golang:1.26): all passed.
  • Verified formatting using gofmt.

Track the namespace where each release secret is stored separately from
release.Namespace so API routes and helm actions work when those differ.

Fixes komodorio#683

@undera undera left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking up #683. The direction is right — the release's storage namespace and its target namespace differ — but in its current form this doesn't fix the reported case.

1. Default mode is unchanged. Without -n, Namespaces is [""] (main.go:62). List() then passes "" as the storage namespace, NewRelease falls back to orig.Namespace, and /api/helm/releases still reports projectcontour for educates-contour. The fix only works when namespaces are passed explicitly. The storage namespace can't be recovered from the release.Release object either: Helm's secrets driver drops the secret's namespace on decode (pkg/storage/driver/secrets.go, List only copies Labels). So for the all-namespaces case it has to come from somewhere else, e.g. listing the release secrets/configmaps directly, or iterating over the visible namespaces.

2. ReleaseElement.Namespace gets overloaded. That field is the release's target namespace: the UI displays it, and the resources handler falls back to the :ns URL param for manifest objects without metadata.namespace (helmHandlers.go:158). Replacing it with the storage namespace makes health lookups for those objects hit the wrong namespace. The two values need to be separate fields: the storage namespace for routing and Helm actions, the target namespace for display and resource fallback. The frontend needs a matching change to build URLs from the storage namespace.

3. Minor:

  • client.AllNamespaces = false is effectively a no-op for ns == "", since the driver is initialised with an empty namespace and lists everything anyway.
  • The tests cover the fallback helper, not the list path that actually breaks. A test for List() in the all-namespaces mode, with storage namespace ≠ release namespace, would pin the bug down.
  • Please fill in the PR description and link #683.

The error messages now including the namespace, and History/Rollback/Uninstall using the storage namespace once it's known, are both correct and worth keeping.

@Tyagiquamar

Copy link
Copy Markdown
Contributor Author

@undera Thanks for the clear and detailed review. Addressed all items in commit dd5db84:

  1. All-namespaces storage discovery: When -n is omitted (Namespaces: [""]), Releases.List() now resolves visible namespaces via a.K8s.GetNameSpaces() and iterates over each namespace (client.AllNamespaces = false), so each release receives its exact storage namespace. If cluster namespace listing is not permitted, it falls back cleanly to client.AllNamespaces = true.
  2. Separated storage namespace from target namespace:
    • ReleaseElement.Namespace stays the manifest target namespace for display, and ReleaseElement.StorageNamespace holds the storage namespace where release data lives.
    • In Resources, manifest objects lacking an explicit metadata.namespace fall back to rel.Orig.Namespace instead of :ns.
    • Updated frontend types and API routing (getResourceStatus, card navigation) to route via storageNamespace || namespace.
  3. Tests and description:

@undera undera left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. The two fields are now separated correctly, and routing by storageNamespace in the frontend fixes the URL problem from #683. A few things are still open:

1. build is failing. TestE2E panics with a nil pointer: GetReleases → Releases.List → K8s.GetNameSpaces → cfgProxyObject.ToRESTConfig (kubectl.go:46). The new namespace discovery runs in the default [""] mode, and the test's fake kube config can't serve it. This can be reproduced locally with go test ./pkg/dashboard/ -run TestE2E.

2. A failed namespace listing is swallowed silently. If GetNameSpaces() errors (for example, RBAC without cluster-wide list namespaces), List() falls back to AllNamespaces = true without logging anything. In that fallback, #683 quietly comes back. Please log the error so the degraded mode is visible.

3. Cost per refresh. In the default mode, every /api/helm/releases call now makes one namespaces call plus one Helm list call per namespace, and the UI polls this endpoint. On clusters with hundreds of namespaces this turns one API round trip into hundreds. One alternative is to keep the single AllNamespaces = true list and get the storage namespace from one cluster-wide list of the release objects' metadata (label owner=helm, using the configured driver: secrets or configmaps). That maps name+version to the namespace in a single call.

4. The new test doesn't exercise the fixed path. TestListReleases_StorageNamespace sets Namespaces: []string{"educates"}, the explicit-namespace mode that already worked after the first commit, and fakeHelmNSConfigGetter ignores the namespace anyway. The case that was broken is Namespaces: []string{""}. A test for that case would also have caught (1).

Minor: in Resources, rel can't be nil once ByName returned no error, so the rel != nil && rel.Orig != nil guard isn't needed. Falling back to rel.Orig.Namespace is the right fix.

@Tyagiquamar

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review and guidance @undera! All feedback has been addressed in commit 1be7780:

  1. TestE2E nil pointer: Added nil guards to cfgProxyObject.ToRESTConfig(), ToDiscoveryClient(), and ToRESTMapper() so an unconfigured REST client getter returns an error rather than dereferencing a nil pointer. TestE2E now runs and passes cleanly.
  2. Visible logging on degraded mode: When cluster-wide release storage metadata lookup fails, log.Warnf now explicitly logs the warning and cause before falling back to rel.Namespace.
  3. Single cluster-wide query (refresh cost): In default [""] all-namespaces mode, List() now runs a single action.NewList with AllNamespaces = true, accompanied by a single cluster-wide query for release storage metadata matching label owner=helm (on Secrets or ConfigMaps depending on driver), mapping (name, version) -> storageNamespace in one call rather than issuing N round trips.
  4. Test coverage: Added TestListReleases_DefaultAllNamespaces in releases_test.go covering Namespaces: []string{""}.
  5. Cleaned up guard: Removed the redundant rel != nil && rel.Orig != nil check in Resources, falling back directly to rel.Orig.Namespace.

Verified with go test -v ./pkg/dashboard/ -run TestE2E and go test -v ./pkg/dashboard/objects/....

@Tyagiquamar

Copy link
Copy Markdown
Contributor Author

Extracted TestHReleaseToJSON_StorageNamespace into a separate test function in commit aab18ef to resolve the CI gocyclo threshold failure (gocyclo -over 19). Both gocyclo -over 19 main.go pkg and go test ./pkg/dashboard/... pass cleanly.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chart health status not properly detected

2 participants