From c2f0962cfc91182ae20adbd0eb849bae8868e45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Sj=C3=B6str=C3=B6m?= Date: Fri, 22 Nov 2024 19:00:39 +0100 Subject: [PATCH] feat: support portforwarding over websocket (#2894) * feat: support portforwarding over websocket * replicating kubectl implementation * update readme --- README.md | 7 +++++++ internal/dao/port_forwarder.go | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/README.md b/README.md index 297f45f1..04e53995 100644 --- a/README.md +++ b/README.md @@ -563,6 +563,13 @@ In order to surface hotkeys globally please follow these steps: --- +## Port Forwarding over websockets + +K9s follows `kubectl` feature flag environment variables to enable/disable port-forwarding over websockets. (default enabled in >1.30) +To disable Websocket support, set `KUBECTL_PORT_FORWARD_WEBSOCKETS=false` + +--- + ## FastForwards As of v0.25.0, you can leverage the `FastForwards` feature to tell K9s how to default port-forwards. In situations where you are dealing with multiple containers or containers exposing multiple ports, it can be cumbersome to specify the desired port-forward from the dialog as in most cases, you already know which container/port tuple you desire. For these use cases, you can now annotate your manifests with the following annotations: diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index e0bef73f..54aed487 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -18,10 +18,12 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" "k8s.io/client-go/tools/portforward" "k8s.io/client-go/transport/spdy" + cmdutil "k8s.io/kubectl/pkg/cmd/util" ) const defaultTimeout = 30 * time.Second @@ -181,6 +183,18 @@ func (p *PortForwarder) forwardPorts(method string, url *url.URL, addr, portMap } dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport, Timeout: defaultTimeout}, method, url) + if !cmdutil.PortForwardWebsockets.IsDisabled() { + tunnelingDialer, err := portforward.NewSPDYOverWebsocketDialer(url, cfg) + if err != nil { + return nil, err + } + + // First attempt tunneling (websocket) dialer, then fallback to spdy dialer. + dialer = portforward.NewFallbackDialer(tunnelingDialer, dialer, func(err error) bool { + return httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err) + }) + } + return portforward.NewOnAddresses(dialer, []string{addr}, []string{portMap}, p.stopChan, p.readyChan, p.Out, p.ErrOut) }