diff --git a/pkg/terminal/fallback_executor.go b/pkg/terminal/fallback_executor.go new file mode 100644 index 0000000000..12fc589e1f --- /dev/null +++ b/pkg/terminal/fallback_executor.go @@ -0,0 +1,74 @@ +package terminal + +import ( + "context" + "fmt" + "log" + "net/url" + + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/remotecommand" +) + +// FallbackExecutor tries WebSocket first and falls back to SPDY if needed +type FallbackExecutor struct { + executor remotecommand.Executor +} + +// NewFallbackExecutor creates a new executor that tries WebSocket first and falls back to SPDY +func NewFallbackExecutor(config *rest.Config, method string, url *url.URL) (remotecommand.Executor, error) { + var wsExecutor, spdyExecutor remotecommand.Executor + var wsErr, spdyErr error + + // Try to create WebSocket executor + wsExecutor, wsErr = remotecommand.NewWebSocketExecutor(config, method, url.String()) + if wsErr != nil { + log.Printf("Warning: Failed to create WebSocket executor: %v", wsErr) + } + + // Try to create SPDY executor + spdyExecutor, spdyErr = remotecommand.NewSPDYExecutor(config, method, url) + if spdyErr != nil { + log.Printf("Warning: Failed to create SPDY executor: %v", spdyErr) + } + + // Handle different scenarios + if wsErr != nil && spdyErr != nil { + // Both failed + return nil, fmt.Errorf("failed to create any executor: WebSocket error: %v, SPDY error: %v", wsErr, spdyErr) + } + + if wsErr != nil { + // Only WebSocket failed, use SPDY + return spdyExecutor, nil + } + + if spdyErr != nil { + // Only SPDY failed, use WebSocket + return wsExecutor, nil + } + + // Both succeeded, create fallback executor + fallbackExecutor, err := remotecommand.NewFallbackExecutor(wsExecutor, spdyExecutor, func(err error) bool { + // Fall back to SPDY if WebSocket fails due to connection issues + log.Printf("WebSocket failed, falling back to SPDY: %v", err) + return true + }) + if err != nil { + return nil, fmt.Errorf("failed to create fallback executor: %v", err) + } + + return &FallbackExecutor{ + executor: fallbackExecutor, + }, nil +} + +// Stream is deprecated. Please use StreamWithContext. +func (e *FallbackExecutor) Stream(options remotecommand.StreamOptions) error { + return e.executor.Stream(options) +} + +// StreamWithContext delegates to the underlying fallback executor +func (e *FallbackExecutor) StreamWithContext(ctx context.Context, options remotecommand.StreamOptions) error { + return e.executor.StreamWithContext(ctx, options) +} diff --git a/pkg/terminal/terminalSesion.go b/pkg/terminal/terminalSesion.go index 355ee126ed..c9fa758a5a 100644 --- a/pkg/terminal/terminalSesion.go +++ b/pkg/terminal/terminalSesion.go @@ -24,7 +24,6 @@ import ( "encoding/json" errors2 "errors" "fmt" - "github.com/devtron-labs/common-lib/async" "io" "log" "net/http" @@ -33,6 +32,8 @@ import ( "sync" "time" + "github.com/devtron-labs/common-lib/async" + "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils/k8s" "github.com/devtron-labs/devtron/internal/middleware" @@ -358,7 +359,8 @@ func getExecutor(k8sClient kubernetes.Interface, cfg *rest.Config, podName, name TTY: tty, }, scheme.ParameterCodec) - exec, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL()) + // Use the new fallback executor instead of SPDY directly + exec, err := NewFallbackExecutor(cfg, "POST", req.URL()) return exec, err } diff --git a/pkg/terminal/websocket_executor.go b/pkg/terminal/websocket_executor.go new file mode 100644 index 0000000000..930ef4405c --- /dev/null +++ b/pkg/terminal/websocket_executor.go @@ -0,0 +1,37 @@ +package terminal + +import ( + "context" + "net/url" + + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/remotecommand" +) + +// WebSocketExecutor wraps the Kubernetes WebSocket executor +type WebSocketExecutor struct { + executor remotecommand.Executor +} + +// NewWebSocketExecutor creates a new WebSocket-based executor for terminal sessions +func NewWebSocketExecutor(config *rest.Config, method string, url *url.URL) (remotecommand.Executor, error) { + // Use the Kubernetes WebSocket executor directly + executor, err := remotecommand.NewWebSocketExecutor(config, method, url.String()) + if err != nil { + return nil, err + } + + return &WebSocketExecutor{ + executor: executor, + }, nil +} + +// Stream is deprecated. Please use StreamWithContext. +func (e *WebSocketExecutor) Stream(options remotecommand.StreamOptions) error { + return e.executor.Stream(options) +} + +// StreamWithContext delegates to the underlying Kubernetes WebSocket executor +func (e *WebSocketExecutor) StreamWithContext(ctx context.Context, options remotecommand.StreamOptions) error { + return e.executor.StreamWithContext(ctx, options) +}