feat: add --context to forward only selected contexts (#85)

* feat: add --context to forward only selected contexts

kportal forwards every context in the config file. When the file describes
several clusters that host the same services, that means holding connections
open to clusters nobody is using, and - because all contexts are active at once
- every localPort has to be globally unique. The same service therefore lands on
a different local port per cluster, so switching clusters means reconfiguring
the app being developed.

--context limits a run to the contexts you name. It is repeatable and accepts
comma-separated values, and composes with -headless:

    kportal --context development-team-a
    kportal --context team-a,team-b -headless
    kportal --context team-a --context team-b

An unknown name is an error listing the contexts the file defines, so a typo
fails loudly instead of silently forwarding nothing.

Local-port and mDNS-hostname conflicts are now scoped to forwards that could run
at the same time: same context, or both contexts in the active selection. With
no selection every context is active, so existing behaviour is unchanged.
Contexts that are never selected together may reuse the same localPort, which is
what lets a service keep one fixed local port in every cluster.

Validation still covers the whole file, so a problem in a context outside the
selection is still reported and --check does not get weaker. Loading, validating
and filtering are shared by every path that re-reads the config (startup, file
watcher, SIGHUP in both headless and verbose modes), so no path can forget the
selection.

Also fixes a latent identity bug this makes reachable: Forward.ID() is only
"alias:localPort" when an alias is set, which was unique across contexts only
because duplicate local ports were globally rejected. With ports now shareable,
deleting or editing a forward from the TUI could have matched same-alias
forwards in other contexts and silently removed them. Delete and edit are now
scoped to the context the row came from.

* fix(config): keep the running config when no selected context remains

A reload dropped selected contexts that no longer exist in the file.
When all of them vanished, the reload returned an empty config and the
manager stopped every forward after a single rename.

LoadForRuntime now returns an error in that case. The watcher keeps the
previous config, as it does for any other invalid reload.

Refs #80

* fix(ui): apply new table columns after a successful SIGHUP reload

The context selection moved the SIGHUP reload into reloadFromSignal,
which had no way to update the verbose table columns. The function now
takes a callback that runs only after the manager reloads without error.
The verbose table passes SetColumns.

Refs #80

* style: gofmt run_test.go

---------

Co-authored-by: André Cruz <[email protected]>
This commit is contained in:
2026-10-02 02:33:29 +01:00
committed by GitHub
co-authored by André Cruz
parent 4748bc4be5
commit 4d263319ab
24 changed files with 1761 additions and 143 deletions
+69
View File
@@ -302,6 +302,65 @@ kportal --check
kportal -c /path/to/config.yaml
```
### Select Contexts
By default every context in the config file is forwarded. `--context` limits the
run to the contexts you name, which is useful when the file describes several
clusters but you only work against one at a time:
```bash
kportal --context development-team-a
```
The flag is repeatable and accepts comma-separated values, so these are
equivalent:
```bash
kportal --context team-a --context team-b
kportal --context team-a,team-b
```
It composes with the other modes, and works well as a per-cluster shell alias:
```bash
alias kp-a='kportal --context development-team-a'
kportal --context development-team-a -headless
```
An unknown context name is an error that lists the contexts the file defines, so
a typo fails loudly instead of silently forwarding nothing.
#### Reusing local ports across contexts
Local-port conflicts are only reported between forwards that could run at the
same time. Contexts that are never selected together may therefore reuse the
same `localPort`, so the same service keeps the same local port in every
cluster and the app you are developing never needs reconfiguring:
```yaml
contexts:
- name: development-team-a
namespaces:
- name: rate-service
forwards:
- &rate-service
resource: pod/rpc-server
protocol: tcp
port: 50051
localPort: 3004
alias: rate-service
- name: development-team-b
namespaces:
- name: rate-service
forwards:
- <<: *rate-service # same local port, no conflict
```
This requires passing `--context`. Without it every context is active, so the
duplicate ports above would genuinely collide and are still reported as an
error - as is naming both contexts at once (`--context team-a,team-b`).
### Generate Forwards from a Cluster
The `generate` subcommand discovers services in a Kubernetes context and lets you
@@ -449,6 +508,11 @@ kill -HUP $(pgrep kportal)
kportal validates port availability at startup and during hot-reload, showing which process is using conflicting ports.
Conflicts are only reported between forwards that could be running together.
With `--context`, forwards in contexts outside the selection are ignored, which
is what lets each context reuse the same `localPort` values - see
[Reusing local ports across contexts](#reusing-local-ports-across-contexts).
### Retry Strategy
Exponential backoff: 1s → 2s → 4s → 8s → 10s (max). Retries continue indefinitely until connection succeeds.
@@ -489,6 +553,11 @@ Context names containing `@`, `.`, `:`, or `/` (e.g. `admin@home`,
`[email protected]`, GKE dotted names, EKS ARNs) are accepted by the
config validator.
If `--context` reports `unknown context "..."`, the name does not match any
`contexts[].name` in the config file. The error lists the names that are
defined - note these are the contexts in your `.kportal.yaml`, which need not be
every context in your kubeconfig.
## 🔧 Development
### Prerequisites