Skip to content

feat(stack): add stack port-forward (#178) - #180

Merged
omattsson merged 7 commits into
mainfrom
feat/178-port-forward
Oct 30, 2026
Merged

omattsson merged 7 commits into
mainfrom
feat/178-port-forward

Conversation

@omattsson

Copy link
Copy Markdown
Owner

Closes #178. Server side: omattsson/k8s-stack-manager#722.

Summary

Adds stackctl stack port-forward <stack> <service> [LOCAL:]REMOTE. The command listens on a local port. Each local connection opens one WebSocket tunnel to GET /api/v1/stack-instances/:id/port-forward.

  • Default address: 127.0.0.1. --address sets another address. A non-loopback bind gives a warning.
  • At most 16 tunnels are open at the same time. A connection over the limit is closed at once.
  • Auth goes in headers only. On a 401, the command renews the session once and dials again.
  • Fatal errors stop the command: 400, 401, 403, 404, 409 and close 1008. Errors such as 429, 502, 503 and close 4001/4002 close one connection only.
  • 3 handshake 429s in a row (over all tunnels, reset by a successful upgrade) stop the command.
  • The command refuses an http:// API URL to a non-loopback host unless --allow-insecure-stream is set.
  • After a local EOF, the tunnel still delivers the server response, then closes.
  • Ctrl-C and SIGTERM close all tunnels (wait at most 3 s).
  • The upgrade hint names k8s-stack-manager v0.15.0 or later (one constant).

cli/pkg/client/websocket.go gets the shared stream parts (dial, handshake errors, close codes, keepalive) for #179.

Tests

  • Unit tests with a fake WebSocket server: round trip, 2 parallel tunnels, half-close, error and close code messages, connection limit, repeated 429s, renewal, Ctrl-C with goleak.
  • SIGTERM test (Unix): a subprocess gets SIGTERM, exits 0 and sends close 1000.
  • Live test cli/test/live/portforward_live_test.go with an echo chart. It skips when port-forward is off on the server or when the server refuses API keys on stream routes.
  • go test ./... -race: pass. make lint: no findings. Live test against a local backend: pass (round trip, parallel tunnels, half-close).

Build

  • cli/go.mod: Go 1.27.3. All workflows read the version from cli/go.mod.
  • STATICCHECK_VERSION: 2026.2.2. Version 2026.1 does not run on Go 1.27. Run make tools to update a local staticcheck.
  • make lint runs the staticcheck that make tools installs (GOBIN or GOPATH/bin), so it works when that folder is not on PATH. It stops when the version does not match the pin.

🤖 Generated with Claude Code

omattsson and others added 6 commits October 30, 2026 10:24
Forward a local port to a service of a stack through the server
WebSocket route GET /api/v1/stack-instances/:id/port-forward.

- One WebSocket per accepted local connection, binary frames both ways,
  keepalive pings and a read deadline.
- Header-only auth; no ?token= retry on stream routes.
- Typed handshake errors with the server error codes; fatal errors stop
  the command, per-connection errors close one tunnel.
- Close-code messages for 1001, 1003, 1008, 1009, 1011, 4001, 4002, 4502.
- Local port rules: LOCAL 0 or 1025-65535, default port fallback, a
  clear error for an explicit port in use.
- --insecure needs --allow-insecure-stream.
- Refresh the vendored swagger.json; README, CLAUDE.md and agent guide.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Keep reading server data after a local EOF; send close 1000 only after
  the server closes, a local write fails or the drain wait ends.
- Close 1008: separate texts for a stopped, deleted or cleaned stack,
  a revoked session and an expired API key.
- Send pings from their own goroutine, so a cancel closes fast.
- Renew the session once for parallel 401s (renew with the refused token).
- Handle SIGTERM.
- Print a fatal tunnel error once.
- Refuse a plain http API URL to a non-loopback host unless
  --allow-insecure-stream is set.
- Cap open tunnels at 16; local write deadline in the tunnel.
- Check the bound address for the non-loopback warning.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…178)

Deploys a small echo chart, tunnels through the server and checks a round
trip, two parallel connections and a half-close. Skips when the server has
port-forward turned off or no cluster or chart is available.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…rsion hint (#178)

- Count handshake 429s in a row over all tunnels; a successful upgrade
  resets the count. At 3 the command stops: wait and try again.
- portForwardSignals lists os.Interrupt and SIGTERM. A Unix test sends a
  real SIGTERM to a subprocess with an open tunnel: exit 0, close 1000.
- The 404 without a code names k8s-stack-manager v0.15.0 or later
  (client.PortForwardServerVersion); README and agent guide match.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- cli/go.mod: go 1.27.3 (all workflows read go-version-file: cli/go.mod).
  go mod tidy changes nothing in go.sum.
- Makefile: staticcheck 2026.2.2; 2026.1 cannot read the Go 1.27 export
  data.
- govulncheck v1.1.4 and goreleaser v2.18.2 work with Go 1.27.3 and stay.
- README, CLAUDE.md and copilot instructions name 1.27.3.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
make lint looked up staticcheck on PATH and did not check its version.
With GOBIN not on PATH, lint failed after make tools. A stale binary
from an older pin ran without a warning.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

govulncheck v1.1.4 uses x/tools v0.29.0, which panics on Go 1.27 code
(unexpected expr: *ast.KeyValueExpr).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@omattsson
omattsson merged commit b412494 into main Oct 30, 2026
8 checks passed
@omattsson
omattsson deleted the feat/178-port-forward branch October 30, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: stackctl stack port-forward

1 participant