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
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.
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).
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.
P0 #1 — HTTP traffic logger captured Authorization, Cookie, Set-Cookie,
X-Api-Key, X-Auth-Token, X-Csrf-Token, Proxy-Authorization, X-Access-Token
verbatim into log entries (file 0600 + UI subscribers). Bearer tokens
and session cookies were ending up on disk whenever httpLog.includeHeaders
was enabled.
flattenHeaders now redacts:
- the explicit list above (case-insensitive via http.CanonicalHeaderKey)
- any header name containing 'token', 'secret', 'password', 'apikey'
Header names remain visible; values become [REDACTED].
Redaction is unconditional and on-by-default — no opt-out flag. Users
who want raw headers can use tcpdump.
P0 #6 — Headless mode without -v silently routed both structured and
stdlib logs to io.Discard. A daemon under launchd/systemd had no way to
report errors. Headless now defaults log destination to os.Stderr; -v
controls only the level (debug vs info). TUI-quiet path is preserved.
Tests in internal/httplog/redact_test.go cover all explicit names,
substring patterns, and case variants.
- [x] Add golangci-lint v2 configuration with formatters section
- [x] Reorganize linters-settings under linters section
- [x] Replace if-else chains with switch statements for clarity
- [x] Wrap all ignored error returns with `_ = ` pattern
- [x] Add OSC 8 hyperlink helper function for clickable ports
- [x] Add blank line in table styling function
- [x] Remove unnecessary type assertion in test
When user starts kportal for the first time, and there is no config file,
kportal should create an empty config file with default values and empty
forwarding rules, so that user can easily edit the config file and add their
own rules.
* Fix enter misbehaving.
* Cleanup after previous tui implementation.
* Fix race condition and improve logging
* Add filtering of the namespaces by text input in the wizard UI
- Fix .gitignore to only ignore binary at root (/kportal)
- Add cmd/kportal/main.go to repository (was incorrectly ignored)
- Resolve merge conflict in static.yml workflow
- Ensure GitHub Pages workflow only triggers on docs/ changes