devantler-tech / devantler-tech/ksail

fix(clusterapi): the web UI and desktop app ignore KUBECONFIG and always read ~/.kube/config

Open
#6,906 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Go
Stars
165
Forks
12
Avg merge
5h 51m
Merged PRs (30d)
347

Description

> 🤖 Generated by the Agentic Engineer

## Evidence

`ksail open web` (and the local cluster API behind it) reads `~/.kube/config` directly and ignores `KUBECONFIG`, while the CLI honours it. Reproduced with a binary built from `main`, using a temporary kubeconfig declaring exactly two contexts, `kind-local` and `colleague-cluster`:

```
$ export KUBECONFIG=/tmp/eval/kubeconfig

# CLI — control: honours KUBECONFIG
$ ksail cluster diagnose --name ghost
✗ ... Get "https://127.0.0.1:19101/api/v1/nodes" ... # kind-local's server, from $KUBECONFIG

# Web API — same environment, same moment
$ ksail open web --no-browser --port 19181
$ curl -s http://127.0.0.1:19181/api/v1/clusters
{"items":[{"metadata":{"name":"" ...
```

The web API returned a cluster that is **not present in the file `KUBECONFIG` names**, and neither of the two contexts that *are* in that file. The CLI, in the same shell, resolved against the file. (The listed cluster's identity and endpoint are omitted deliberately — they are local environment detail, not needed to characterise the defect.)

## Root cause

`pkg/k8s/rest_config.go` documents `ResolveKubeconfigPath` as *"the single owner of kubeconfig-path resolution"*, with the order: explicit path → **first entry of `KUBECONFIG`** → `~/.kube/config`. Its comment is explicit that honouring `KUBECONFIG` is the intended behaviour for callers that pass no explicit path.

`pkg/cli/clusterapi/local_service.go` does not go through that owner. `NewService` sets:

```go
kubeconfigPath: k8s.DefaultKubeconfigPath,
```

and `DefaultKubeconfigPath` is unconditional:

```go
homeDir, _ := os.UserHomeDir()
return filepath.Join(homeDir, ".kube", "config")
```

So every surface built on this service — the web UI, the desktop app, and the operator's REST API — resolves the kubeconfig by a path that skips the resolver the rest of the codebase routes through.

## Affected audience and impact

Anyone who uses `KUBECONFIG` — per-project kubeconfigs, `direnv`, CI shells, or a deliberately narrowed file for a session:

1. **The CLI and the web UI disagree about which clusters exist**, in the same shell, at the same moment. That undermines the premise of #5654, which is that all three surfaces see one consistent cluster set.
2. **Clusters deliberately excluded from the session's kubeconfig are still surfaced and operable** in the web UI. This is not a privilege escalation — the default file is readable by the same user either way — but it does mean a narrowed `KUBECONFIG` does not narrow what the UI acts on, which is the opposite of what setting it implies.
3. **A project-local kubeconfig is silently ignored**, so the clusters the user is actually working with may be missing from the UI entirely, with no message explaining why.

`DefaultKubeconfigPath` also discards the `os.UserHomeDir()` error, so on a host where the home directory cannot be resolved the path degrades to a bare relative `.kube/config` rather than reporting the problem. Worth fixing in the same pass, though it is not what this issue is about.

## Expected behaviour

Every surface resolves the kubeconfig through `ResolveKubeconfigPath`, so an explicit path wins, `KUBECONFIG` is honoured next, and `~/.kube/config` is the fallback. The CLI and the web UI list the same clusters for the same environment.

## Proposed direction

Have `clusterapi.NewService` seed `kubeconfigPath` from `ResolveKubeconfigPath("")` rather than `DefaultKubeconfigPath`, keeping the existing test seam (`SetKubeconfigPathForTest`) intact. Two things to decide while implementing:

- **When to resolve.** Resolving once at construction is simplest and matches today's shape; resolving per call would pick up a `KUBECONFIG` change during the server's lifetime. Construction-time is probably right for a process the user restarts anyway, but it should be a deliberate choice.
- **Multi-entry `KUBECONFIG`.** The resolver takes only the *first* entry of a `:`-separated list, while `kubectl` merges all of them. That is a pre-existing limitation of the shared resolver, not something this change should silently alter — but it will become visible on a new surface, so it is worth stating in the fix.

## Acceptance criteria

- [ ] With `KUBECONFIG` set, the web API's cluster list matches what the CLI reports for the same environment, pinned by a test that fails if the service reverts to the unconditional default path.
- [ ] With `KUBECONFIG` unset, behaviour is unchanged (`~/.kube/config`).
- [ ] An explicit path still wins over both.
- [ ] `DefaultKubeconfigPath`'s discarded home-directory error is either surfaced or explicitly justified in a comment.

## Rough size

S. One seam, one call-site change, plus the regression test and a decision on the two points above.

Contributor guide

Open the contributing guide

Research direction

Start with pkg/k8s/rest_config.go to understand ResolveKubeconfigPath, then inspect pkg/cli/clusterapi/local_service.go and NewService. Add or update the cluster API regression test so KUBECONFIG, its unset fallback, and an explicit path are covered without breaking SetKubeconfigPathForTest. Done means the web API and CLI resolve the same kubeconfig, with the home-directory error handled or explicitly justified.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, kubernetes
Domain
api, backend, devops
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.