Commit Graph
149 Commits
Author SHA1 Message Date
lukaszraczyloandGitHub 1c419ab1c2 docs: rebuild the landing page and README with comparison, install and reference sections (#90)
The earlier rewrite dropped content the page needs. The landing page
has a hero with an install command, a terminal mock, the real
screenshot, feature rows with snippets from the repository, a dated
comparison with kubectl, k9s, kubefwd and kftray with source links,
install tabs, config and key references, troubleshooting and an FAQ.

The README restores the comparison, advanced features, retry strategy,
kftray migration, signal handling, signature verification and build
from source. Cells that a project does not document show as Not
documented.

It fixes four claims the code contradicts: the maxBodySize default, the
deployment resource type, the file watcher delay and the verbose table
status markers.
2026-10-02 03:25:29 +01:00
lukaszraczyloandGitHub 260a909e3d docs: rewrite the product page and README, update the feature list (#88)
* docs(site): rewrite the product page in plain language

The page lists what kportal does with the flags, keys and config keys
that exist in the code. It drops the CDN assets, gradients and icon
grids, and keeps the light and dark themes.

It now covers tui.columns, typed namespace entry, completion, the log
format flag and the --context flag. The claim about a 100ms file watcher
is removed because the code has no such debounce.

* docs: rewrite the README and update the feature list

The README now has a feature table, install steps, a flag table, a
config reference and troubleshooting. It documents tui.columns, typed
namespace entry, shell completion and the --context flag.

It drops the comparison table with other tools, which the repository
cannot back up, and corrects the supported resource types and the Go
version.
2026-10-02 02:54:50 +01:00
lukaszraczyloandGitHub 554f1e5ae0 Update go.mod and go.sum (#89) v0.11.2 2026-10-02 02:53:05 +01:00
4d263319ab 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]>
v0.11.1
2026-10-02 02:33:29 +01:00
lukaszraczyloandGitHub 4748bc4be5 feat(ui): type the namespace when the cluster does not list them (#84)
Some users cannot list namespaces on a cluster. The add wizard showed
an error and offered no way forward.

The namespace step now switches to a text prompt when the list call
fails or returns nothing. The prompt shows the reason and checks that
the name is a valid DNS label. Esc from a later step returns to the
prompt.

The j and k keys now type into text fields. They moved the list cursor
before, so a name such as kube-system could not be typed.

Closes #66
v0.10.1
2026-10-02 02:28:58 +01:00
f3d3f259a1 Allow configuring forwards-table columns, order, and width (#82)
* Allow configuring forwards-table columns, order, and width

Adds an optional tui.columns section to .kportal.yaml so the visible
columns, their order, and their widths can be customized instead of
always rendering the hardcoded 8-column layout. Omitting the section
keeps the existing default columns/order/widths.

Closes #81

* fix(ui): keep the plain table layout and check column widths

The plain verbose table must look the same when no tui section is
set. It had narrower columns and shorter port headers than before. The
default layout is now identical to the old one, and a golden test
guards it.

A status column that is not last now pads by visible width. Table
rules follow the column widths. The UI applies new columns only after a
reload succeeds, including the SIGHUP path of the verbose table.

The validator rejects a column width above 200. The README describes
the tui.columns setting.

Refs #81

* fix(httplog): replace the deprecated ReverseProxy Director with Rewrite

Staticcheck reports SA1019 because Go 1.26 deprecates Director. The
proxy now sets the target in a Rewrite function. Rewrite drops the
X-Forwarded-For header that Director sent, so the function calls
SetXForwarded to keep that header.

A new test checks the target port, the path and the header.

* fix(release): drop the deprecated cask url verified key

Homebrew warns about the verified parameter of the cask url stanza.
The setting is not needed. GoReleaser check now passes without a
deprecation warning.

The postflight warning remains. GoReleaser generates it from the
post install hook, and the upstream fix is not released yet.

Refs #79

---------

Co-authored-by: Lukasz Raczylo <[email protected]>
v0.9.33
2026-10-02 02:24:11 +01:00
lukaszraczyloandGitHub 232c0f1162 Update go.mod and go.sum (#78) v0.9.32 2026-09-02 04:17:26 +01:00
lukaszraczyloandGitHub 6f19ecea6b Update go.mod and go.sum (#77) v0.9.31 2026-08-27 05:54:28 +01:00
lukaszraczyloandGitHub 93b5d6496e Update go.mod and go.sum (#76) v0.9.30 2026-08-26 04:25:59 +01:00
lukaszraczyloandGitHub b3c63bd1fa Update go.mod and go.sum (#75) v0.9.29 2026-08-22 04:20:17 +01:00
lukaszraczyloandGitHub 374163f463 Update go.mod and go.sum (#74) v0.9.28 2026-08-21 04:25:07 +01:00
lukaszraczyloandGitHub 6c695584a4 Update go.mod and go.sum (#73) v0.9.27 2026-08-20 04:22:09 +01:00
lukaszraczyloandGitHub c1dbad858b Update go.mod and go.sum (#72) v0.9.26 2026-08-18 04:21:24 +01:00
lukaszraczyloandGitHub 3c1726cdec Update go.mod and go.sum (#71) v0.9.25 2026-08-14 04:48:03 +01:00
lukaszraczyloandGitHub 49cc69927a Update go.mod and go.sum (#70) v0.9.24 2026-08-13 04:49:32 +01:00
lukaszraczyloandGitHub 4571552f61 Update go.mod and go.sum (#69) v0.9.23 2026-08-12 04:48:15 +01:00
lukaszraczyloandGitHub 6b80c9ecc5 Update go.mod and go.sum (#68) v0.9.22 2026-08-11 04:36:05 +01:00
lukaszraczyloandGitHub bcfa2eb035 Update go.mod and go.sum (#67) v0.9.21 2026-08-03 07:25:42 +01:00
lukaszraczyloandGitHub ba475be9de Update go.mod and go.sum (#65) v0.9.20 2026-07-28 06:48:08 +01:00
lukaszraczyloandGitHub ad35ed05c8 Update go.mod and go.sum (#64) v0.9.19 2026-07-27 07:33:11 +01:00
lukaszraczyloandGitHub 698ccd510a Update go.mod and go.sum (#63) v0.9.18 2026-07-24 06:52:14 +01:00
lukaszraczyloandGitHub abe90e330d Update go.mod and go.sum (#62) v0.9.17 2026-07-23 06:56:53 +01:00
lukaszraczyloandGitHub add1cd2eb7 Update go.mod and go.sum (#61) v0.9.16 2026-07-22 06:53:15 +01:00
lukaszraczyloandGitHub 407ae1842c Update go.mod and go.sum (#60) v0.9.15 2026-07-21 06:54:04 +01:00
lukaszraczyloandGitHub 89258e70bb Update go.mod and go.sum (#59) v0.9.14 2026-07-20 07:20:26 +01:00
lukaszraczyloandGitHub dc24fe5d88 Update go.mod and go.sum (#58) v0.9.13 2026-07-19 06:56:12 +01:00
lukaszraczyloandGitHub 99485450a6 Update go.mod and go.sum (#57) v0.9.12 2026-07-17 06:45:47 +01:00
lukaszraczyloandGitHub 233671730a Update go.mod and go.sum (#56) v0.9.11 2026-07-09 05:24:01 +01:00
lukaszraczyloandGitHub 0d1f8560c3 Update go.mod and go.sum (#55) v0.9.10 2026-07-08 05:07:35 +01:00
lukaszraczyloandGitHub 5f62ca3ed1 Update go.mod and go.sum (#54) v0.9.9 2026-07-07 05:25:53 +01:00
lukaszraczyloandGitHub c2528f7eec Update go.mod and go.sum (#53) v0.9.8 2026-06-30 05:31:08 +01:00
lukaszraczyloandGitHub a37574371f Update go.mod and go.sum (#52) v0.9.7 2026-06-29 05:37:13 +01:00
lukaszraczyloandGitHub 0bab59d97d Update go.mod and go.sum (#51) v0.9.6 2026-06-27 05:26:45 +01:00
lukaszraczyloandGitHub 7acac8e885 Update go.mod and go.sum (#50) v0.9.5 2026-06-26 05:34:24 +01:00
lukaszraczyloandGitHub 87ce85b07b Update go.mod and go.sum (#49) 2026-06-25 05:31:14 +01:00
lukaszraczylo d5125a0d62 Optimisation & fixes 2026-06-21 12:59:55 +01:00
lukaszraczylo ab65c2e17b docs: add Telemetry section linking to oss-telemetry opt-out docs
Discloses the single anonymous adoption ping sent on startup and points
users to the upstream README section for full opt-out instructions
instead of duplicating the table here.
2026-05-21 04:06:27 +01:00
lukaszraczylo cda8b4be47 feat: anonymous usage telemetry via oss-telemetry
Send a single fire-and-forget ping at startup to help track adoption
and version spread. No persistent identifiers are collected.

main() is wrapped via runMain() so deferred Wait drains in-flight pings
before os.Exit.

Opt out via any of:
  DO_NOT_TRACK=1
  OSS_TELEMETRY_DISABLED=1
  KPORTAL_DISABLE_TELEMETRY=1
v0.9.1
2026-05-21 02:59:46 +01:00
lukaszraczylo 3f11219dc1 test: drain healthcheck goroutine + lock read in HealthCallback test
CI on Linux flagged a race in TestStartWorker_HealthCallback_StatusChange:
the test read ui.updates while the healthchecker's per-port goroutine
(spawned by Register at checker.go:164) was still running and could
fire UpdateStatus through notifyStatusChange.

The earlier mutex on MockStatusUpdater protected the writes; the read
side was unprotected, and the goroutine had not finished by the time
the test started ranging over the slice on slower runners.

Fix:
  - call healthChecker.Unregister(fwd.ID()) to drain the per-port
    goroutine before reading
  - hold ui.mu around the slice read for belt-and-suspenders happens-
    before, regardless of goroutine timing

Verified locally with go test -race -count=20 on the targeted test
and -count=3 on the full forward package.
v0.8.2
2026-05-06 18:24:38 +01:00
lukaszraczylo 62483f9475 docs: sync website + README + wizard guide + changelog with new features
docs/index.html (kportal.raczylo.com):
  - Features grid gains Bulk Generate, Sensitive Header Redaction,
    Verified Installer cards; Headless card subtext clarified to
    'logs to stderr'.
  - Keybindings: 'a' -> 'n' for Add (matches real binding).
  - Quick Install card notes SHA-256 + optional cosign verification
    and DRY_RUN / SKIP_COSIGN env vars.
  - New Bulk Generate usage tile with --dry-run hint; Headless tile
    redirects stderr.
  - HTTP Traffic Logging card mentions automatic header redaction
    and the wizard 'h' toggle.

README.md:
  - install.sh note expanded with DRY_RUN/SKIP_COSIGN table.
  - HTTP Traffic Logging section gains 'Toggling per-forward
    logging', 'Header redaction', and 'Advanced configuration'
    examples.
  - Headless mode clarified to log to stderr (2>kportal.log).
  - Troubleshooting note on accepted context-name characters.

WIZARD_USAGE.md:
  - Add binding corrected from 'a' to 'n'; 'e' (edit) row added.
  - 'h' httpLog toggle and Tab focus switch documented.
  - New 'Edit Forward Wizard' section noting same-port allowed and
    advanced httpLog preserved.
  - Esc-cancels-delete behaviour clarified.

CHANGELOG.md: [Unreleased] populated with Added / Changed / Fixed
entries for this session's user-facing work, dated 2026-05-06.

.kportal.yaml example: inline 'httpLog: true' comment on one
forward as a usage hint.
2026-05-06 15:03:35 +01:00
lukaszraczylo 0c11838326 refactor(cmd): extract main() into testable run() + per-mode helpers
main() body is now os.Exit(run(ctx, args, stdin, stdout, stderr))
where ctx comes from signal.NotifyContext and IO is io.Reader/Writer
instead of os.Std*. run() parses flags via a fresh flag.NewFlagSet
(no more package-level flag.* globals) and dispatches to:

  runShowVersion(stdout)
  runCheckUpdate(stdout, stderr)
  runConvert(input, output, stdout, stderr)
  runHeadless(ctx, opts)
  runVerboseTable(ctx, opts)
  runInteractive(ctx, opts)

Helpers extracted: parseFlags, resolveConfigPath, initLoggers,
configureStdlibLog, loadOrCreateConfig, buildRuntimeDeps,
makeHTTPLogSubscriber, shutdownManager. Signal-loop and fsnotify
watcher exit cleanly on ctx.Done() (previously chan-based with
unconditional os.Exit).

Behaviour preserved by smoke checks: -version, -update, generate
without --context, generate with bogus context, -check on
missing/valid config, -headless start+SIGINT shutdown.

Coverage: cmd/kportal 17.4% -> 71.3%. New run_test.go exercises
run() and every run* mode. main() and runInteractive remain at 0%
(TTY-required); not feasible without a tea.Program factory
abstraction, which would add complexity for minimal coverage gain.
2026-05-06 15:03:16 +01:00
lukaszraczylo b7b297c576 test: make MockStatusUpdater thread-safe for concurrent callbacks
The mock recorded calls into unprotected slices while being invoked
concurrently from the test goroutine and from the health-checker
callback registered by Manager.startWorker (and the watchdog hung
callback). go test -race -count=20 reliably tripped the race detector
on TestStartWorker_HealthCallback_StatusChange.

Wrap the slice writes in a sync.Mutex. Read-side code in tests reads
only after manager.Stop() drains the background goroutines (Stop's
wg.Wait establishes a happens-before edge), so direct reads remain
race-free.
2026-05-06 15:02:59 +01:00
lukaszraczylo 90ddca6709 test: cover ui handlers/views/commands/table
internal/ui: 27.9% -> 79.8%. Adds three test files (~3300 lines):
  - wizard_handlers_extended_test.go: keyboard branches not previously
    covered (ctrl+c, pgup/pgdn, search filter typing, port-checked
    failure path, alias text edit, focus toggling, error states),
    plus removeForwardsCmd / removeForwardByIDCmd against real Mutator
    + tempdir config (dedup + at-least-one-namespace validation).
  - wizard_views_test.go: every renderXxx path with a populated
    AddWizardState — context/namespace selectors, resource selection,
    port entry, http log list/detail, success, scroll indicators.
  - table_test.go: AddForward / UpdateStatus / SetError / Clear /
    FilterStrings flows.

Test-only fixups noted by agent:
  - getFilteredEntries silently skips StatusCode==0 entries (only
    completed responses are visible). Tests now seed StatusCode=200.
  - handleForwardSaved leaves step=StepConfirmation on save error
    rather than advancing to StepSuccess; assertion corrected.
  - removeForwardsCmd needs >=2 forwards in a context for dedup tests
    so removing one leaves the context non-empty (validator rejects
    empty contexts).

go test -race -count=1 ./... clean across all 14 packages.
2026-05-06 14:08:58 +01:00
lukaszraczylo ed80015e23 test: cover forward manager/worker/watchdog/portchecker
internal/forward: 45.9% -> 70.8%. Adds 14 critical-gap tests:
  - Manager.startWorker registration + duplicate error + healthcheck
    callback paths (status changes via MarkStarting/MarkReconnecting)
    + watchdog hung-callback (backdated heartbeat + checkWorkers).
  - stopWorker delegate + stopWorkerInternal removeFromUI=false.
  - DisableForward / EnableForward all error paths.
  - Reload diff logic: remove-stale, keep-unchanged, port conflict,
    empty new-config, currentConfig update.
  - SetMDNSPublisher trivial setter.
  - Watchdog.RegisterWorkerWithResponder alive/dead/transition cycle
    + pollHeartbeats direct + monitorLoop heartbeat ticker branch.
  - ForwardWorker.sleepWithBackoff (normal/cancelled/verbose) +
    IsAlive doneChan branch + Start terminates on cancel.
  - Manager.Start port-conflict path.
  - getProcessUsingPortUnix empty-bound and active-bound paths.

Remaining gaps (untestable without refactoring):
  - Windows-only branches (build tag prevents execution on macOS/Linux)
  - worker.run / establishForward — need a fake k8s.PortForwarder, but
    the Manager field is *k8s.PortForwarder (concrete), so a mock
    cannot be injected without source-level refactor.
2026-05-06 14:08:56 +01:00
lukaszraczylo fbb13aa32f test: cover converter I/O + httplog round-trip
internal/converter: 57.1% -> 98.4%. Added 18 cases covering
ConvertKFTrayToKPortal (happy path, missing input, malformed JSON,
unwritable output, multi-entry, file mode 0600), GetConversionSummary
(happy path, missing/malformed input, empty array, dedup counters),
and convertToKPortal edge cases (empty input, zero ports, empty
WorkloadType, sort order, alias preservation, tcp/udp/empty protocol
variants).

internal/httplog: 55.7% -> 95.7%. Added 17 tests using httptest for
loggingTransport.RoundTrip (GET/POST, filter-path bypass, header
redaction at the integration layer, backend-down, nil bodies, request
counter monotonicity) and direct unit tests for readBodyLimited
(empty, small, exact-at-limit, truncated, large pool-branch, zero
maxSize).
2026-05-06 14:08:54 +01:00
lukaszraczylo 1b2516ce82 test: cover cmd/kportal helpers + version checker HTTP paths
cmd/kportal: 0% -> 17.4% (testable surface only — main() is a
274-statement monolith with OS signals, bubbletea TUI, kubeconfig
loading, signal-loop goroutines, and config watcher all in one
function. Hitting 70% requires extracting it into run(args,w) int,
which is a non-trivial refactor — out of scope for a coverage pass.)
Tests cover: promptCreateConfig (yes/no/EOF/empty inputs via os.Pipe
stdin redirection), contains, resolveGenerateConfigPath (all 4 system
dirs + abs/rel branches), runGenerate (missing flag, -h, unknown
flag, invalid context, malformed YAML, no-TTY error path).

internal/version: 45.7% -> 97.8%. Added httptest.NewServer + custom
rewriteTransport to redirect GitHub API calls. Covered NewChecker,
fetchLatestRelease (200/403/404/429/500, malformed JSON, empty
tag_name), CheckForUpdate (newer/same/current-newer/error/cancelled),
parseVersion edge cases (empty, single digit, alpha).
2026-05-06 14:08:51 +01:00
lukaszraczylo f4adeedb8f feat: 'kportal generate' subcommand to bulk-add forwards from a cluster
New subcommand that connects to a chosen kube context and walks the
user through three picker steps before writing forwards to the config:

  1. Namespace multi-select (no system-namespace exclusion)
  2. Service multi-select grouped by namespace, with already-configured
     rows greyed out and locked off
  3. Starting-port input with a live preview of consecutive local-port
     assignments, skipping any port already taken by the existing config
     or another row in the batch

Multi-port services emit one forward per port. Non-TCP ports are
skipped with a one-line warning at the end (kportal forward layer is
TCP-only). --dry-run prints the planned forwards without writing.

Subcommand dispatch lives in cmd/kportal/main.go: when os.Args[1] ==
'generate', runGenerate(os.Args[2:]) is invoked before the main
flag.Parse() so the flag.NewFlagSet 'generate' parses its own
'--context', '--config', '--dry-run' flags. Invalid contexts list
the available ones for quick correction.

Tests in internal/ui/generate_test.go cover:
  - namespace toggle / toggle-all / filter
  - service multi-select with locked already-configured rows and
    non-TCP filtering
  - port-collision-aware consecutive assignment using a real Mutator
    against a tempdir config
  - reject + recover for starting-port < 1024
  - dry-run does not invoke the mutator
  - end-to-end Update() walk through the three steps
  - parse-starting-port boundary table
  - port-step view rendering
  - ServiceCandidate.Key() determinism

README updated with a 'Generate Forwards from a Cluster' section
describing the flow and the three flags.
2026-05-06 13:09:12 +01:00
lukaszraczylo e02edb68ef update dependencies 2026-05-06 12:49:28 +01:00
lukaszraczylo dbc7830546 fix(ui): edit-mode wizard allows keeping the same local port
Previously, editing a forward and keeping its local port unchanged
failed the wizard's port-availability check: the in-config scan
found the forward's own entry and reported '✗ Port N already
assigned to <self.ID>'. Users had to pick a different port,
edit, then change back.

checkPortCmd now accepts an excludeID. The wizard passes
wizard.originalID when isEditing so the forward being edited is
ignored during the in-config conflict scan. The OS-level port
check is unchanged (still catches actual port collisions).

New regression test: TestCheckPortCmd_ExcludeID_AllowsKeepingOwnPort.
2026-05-06 12:45:35 +01:00
lukaszraczylo bfe541565b feat(ui): toggle httpLog per-forward in add/edit wizard
Adds an HTTP-log enable toggle to the wizard's confirmation step so
users can flip httpLog on a forward without editing YAML by hand.

Behaviour:
- 'h' on the confirmation step toggles HTTPLog when not focused on
  the alias text input. When focus is on alias, 'h' is treated as
  text so users can still type aliases like 'host' or 'http-proxy'.
- The confirmation summary shows '[x] enabled' or '[ ] disabled'.
- New forwards: toggle on -> &HTTPLogSpec{Enabled: true}; off -> nil.
- Edit mode: pre-populates the toggle from the existing forward and
  preserves any advanced HTTPLog fields the user had configured in
  YAML (logFile, includeHeaders, maxBodySize, filterPath) by copying
  the original spec on save. Toggling off discards the advanced
  fields (consistent with 'absent in YAML = disabled').

State changes:
- ForwardStatus gains *config.HTTPLogSpec so the wizard can see the
  full original spec on edit.
- AddWizardState gains httpLog bool + httpLogOriginal *HTTPLogSpec.

Three new tests:
- TestHandleAddWizardKeys_HToggleHTTPLog
- TestHandleAddWizardKeys_HOnAliasFocusIsTextInput
- TestEditPrefill_PreservesHTTPLog
2026-05-06 12:41:36 +01:00