Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions pkg/terminal/fallback_executor.go
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 4 additions & 2 deletions pkg/terminal/terminalSesion.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"encoding/json"
errors2 "errors"
"fmt"
"github.com/devtron-labs/common-lib/async"
"io"
"log"
"net/http"
Expand All @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down
37 changes: 37 additions & 0 deletions pkg/terminal/websocket_executor.go
Original file line number Diff line number Diff line change
@@ -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)
}