Skip to content

Commit

Permalink
Merge pull request #1191 from FabianKramm/fabian-dev
Browse files Browse the repository at this point in the history
fix(ui): resize terminal correctly in the backend
  • Loading branch information
FabianKramm committed Oct 13, 2020
2 parents 8dc3ad2 + 8db5d94 commit defb68d
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 57 deletions.
10 changes: 7 additions & 3 deletions pkg/devspace/kubectl/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ func (client *client) ExecStreamWithTransport(options *ExecStreamWithTransportOp
tty, t = terminal.SetupTTY(options.Stdin, options.Stdout)
if options.ForceTTY || tty {
tty = true
if t.Raw {
if t.Raw && options.TerminalSizeQueue == nil {
// this call spawns a goroutine to monitor/update the terminal size
sizeQueue = t.MonitorSize(t.GetSize())
} else if options.TerminalSizeQueue != nil {
sizeQueue = options.TerminalSizeQueue
t.Raw = true
}

streamOptions = remotecommand.StreamOptions{
Expand Down Expand Up @@ -112,8 +115,9 @@ type ExecStreamOptions struct {
Container string
Command []string

ForceTTY bool
TTY bool
ForceTTY bool
TTY bool
TerminalSizeQueue remotecommand.TerminalSizeQueue

Stdin io.Reader
Stdout io.Writer
Expand Down
110 changes: 103 additions & 7 deletions pkg/devspace/server/enter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package server

import (
"github.com/devspace-cloud/devspace/pkg/devspace/kubectl"
"github.com/pkg/errors"
"k8s.io/client-go/tools/remotecommand"
"net/http"
"strconv"
"time"

v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -37,6 +40,22 @@ func (h *handler) enter(w http.ResponseWriter, r *http.Request) {
return
}

var terminalResizeQueue TerminalResizeQueue
resizeId, ok := r.URL.Query()["resize_id"]
if ok && len(resizeId) == 1 {
h.terminalResizeQueuesMutex.Lock()
terminalResizeQueue = newTerminalSizeQueue()
h.terminalResizeQueues[resizeId[0]] = terminalResizeQueue
h.terminalResizeQueuesMutex.Unlock()

defer func() {
h.terminalResizeQueuesMutex.Lock()
defer h.terminalResizeQueuesMutex.Unlock()

delete(h.terminalResizeQueues, resizeId[0])
}()
}

// Create kubectl client
client, err := h.getClientFromCache(kubeContext, kubeNamespace)
if err != nil {
Expand All @@ -63,13 +82,14 @@ func (h *handler) enter(w http.ResponseWriter, r *http.Request) {
Namespace: namespace[0],
},
},
Container: container[0],
Command: []string{"sh", "-c", "command -v bash >/dev/null 2>&1 && exec bash || exec sh"},
ForceTTY: true,
TTY: true,
Stdin: stream,
Stdout: stream,
Stderr: stream,
Container: container[0],
Command: []string{"sh", "-c", "command -v bash >/dev/null 2>&1 && exec bash || exec sh"},
ForceTTY: true,
TTY: true,
TerminalSizeQueue: terminalResizeQueue,
Stdin: stream,
Stdout: stream,
Stderr: stream,
})
if err != nil {
h.log.Errorf("Error in %s: %v", r.URL.String(), err)
Expand All @@ -80,3 +100,79 @@ func (h *handler) enter(w http.ResponseWriter, r *http.Request) {
ws.SetWriteDeadline(time.Now().Add(time.Second * 5))
ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
}

func (h *handler) resize(w http.ResponseWriter, r *http.Request) {
resizeId, ok := r.URL.Query()["resize_id"]
if !ok || len(resizeId) != 1 {
http.Error(w, "resize_id is missing", http.StatusBadRequest)
return
}
widthStr, ok := r.URL.Query()["width"]
if !ok || len(widthStr) != 1 {
http.Error(w, "width is missing", http.StatusBadRequest)
return
}
heightStr, ok := r.URL.Query()["height"]
if !ok || len(heightStr) != 1 {
http.Error(w, "height is missing", http.StatusBadRequest)
return
}
width, err := strconv.Atoi(widthStr[0])
if err != nil {
http.Error(w, errors.Wrap(err, "parse width").Error(), http.StatusBadRequest)
return
}
height, err := strconv.Atoi(heightStr[0])
if err != nil {
http.Error(w, errors.Wrap(err, "parse height").Error(), http.StatusBadRequest)
return
}

h.terminalResizeQueuesMutex.Lock()
defer h.terminalResizeQueuesMutex.Unlock()

resizeQueue, ok := h.terminalResizeQueues[resizeId[0]]
if !ok {
http.Error(w, "resize_id does not exist", http.StatusBadRequest)
return
}

resizeQueue.Resize(remotecommand.TerminalSize{
Width: uint16(width),
Height: uint16(height),
})
}

type TerminalResizeQueue interface {
remotecommand.TerminalSizeQueue

Resize(size remotecommand.TerminalSize)
}

func newTerminalSizeQueue() TerminalResizeQueue {
return &terminalSizeQueue{
resizeChan: make(chan remotecommand.TerminalSize, 1),
}
}

type terminalSizeQueue struct {
resizeChan chan remotecommand.TerminalSize
}

func (t *terminalSizeQueue) Resize(size remotecommand.TerminalSize) {
select {
// try to send the size to resizeChan, but don't block
case t.resizeChan <- size:
// send successful
default:
// unable to send / no-op
}
}

func (t *terminalSizeQueue) Next() *remotecommand.TerminalSize {
size, ok := <-t.resizeChan
if !ok {
return nil
}
return &size
}
62 changes: 24 additions & 38 deletions pkg/devspace/server/server.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
package server

import (
"bytes"
"encoding/json"
"github.com/devspace-cloud/devspace/pkg/util/randutil"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"

"github.com/devspace-cloud/devspace/pkg/devspace/config/generated"
"github.com/devspace-cloud/devspace/pkg/devspace/config/loader"
"github.com/devspace-cloud/devspace/pkg/devspace/config/versions/latest"
Expand All @@ -27,6 +16,12 @@ import (
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
)

// Server is listens on a given port for the ui functionality
Expand Down Expand Up @@ -116,10 +111,11 @@ type handler struct {
clientCache map[string]kubectl.Client
clientCacheMutex sync.Mutex

terminalResizeQueues map[string]TerminalResizeQueue
terminalResizeQueuesMutex sync.Mutex

ports map[string]*forward
portsMutex sync.Mutex

token string
}

type forward struct {
Expand Down Expand Up @@ -152,18 +148,19 @@ func newHandler(configLoader loader.ConfigLoader, config *latest.Config, generat
}

handler := &handler{
mux: http.NewServeMux(),
path: path,
defaultContext: defaultContext,
defaultNamespace: defaultNamespace,
kubeContexts: kubeContexts,
workingDirectory: cwd,
configLoader: configLoader,
config: config,
log: log,
generatedConfig: generatedConfig,
ports: make(map[string]*forward),
clientCache: make(map[string]kubectl.Client),
mux: http.NewServeMux(),
path: path,
defaultContext: defaultContext,
defaultNamespace: defaultNamespace,
kubeContexts: kubeContexts,
workingDirectory: cwd,
configLoader: configLoader,
config: config,
log: log,
generatedConfig: generatedConfig,
ports: make(map[string]*forward),
clientCache: make(map[string]kubectl.Client),
terminalResizeQueues: make(map[string]TerminalResizeQueue),
}

analytics, err := analytics.GetAnalytics()
Expand All @@ -179,20 +176,8 @@ func newHandler(configLoader loader.ConfigLoader, config *latest.Config, generat
}
}

out, err := ioutil.ReadFile(filepath.Join(path, "index.html"))
if err != nil {
return nil, err
}

token, err := randutil.GenerateRandomString(64)
if err != nil {
return nil, err
}
handler.token = token

newOut := strings.Replace(string(out), "###TOKEN###", token, -1)
handler.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader([]byte(newOut)))
http.ServeFile(w, r, "index.html")
})
handler.mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(filepath.Join(path, "static")))))
handler.mux.HandleFunc("/api/version", handler.version)
Expand All @@ -201,6 +186,7 @@ func newHandler(configLoader loader.ConfigLoader, config *latest.Config, generat
handler.mux.HandleFunc("/api/config", handler.returnConfig)
handler.mux.HandleFunc("/api/forward", handler.forward)
handler.mux.HandleFunc("/api/enter", handler.enter)
handler.mux.HandleFunc("/api/resize", handler.resize)
handler.mux.HandleFunc("/api/logs", handler.logs)
handler.mux.HandleFunc("/api/logs-multiple", handler.logsMultiple)
return handler, nil
Expand Down
3 changes: 0 additions & 3 deletions ui/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'ddataLayer', 'GTM-KM6KSWG');
</script>
<script>
window.token = "###TOKEN###";
</script>
</head>
<body>
<noscript>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import styles from './InteractiveTerminal.module.scss';
import MaximizeButton from 'components/basic/IconButton/MaximizeButton/MaximizeButton';
import IconButton from 'components/basic/IconButton/IconButton';
import IconTrash from 'images/trash.svg';
import {uuidv4} from "../../../lib/utils";
import authFetch from "../../../lib/fetch";

export interface InteractiveTerminalProps {
className?: string;
Expand All @@ -17,6 +19,7 @@ export interface InteractiveTerminalProps {
closeOnConnectionLost?: boolean;
closeDelay?: number;

remoteResize?: boolean;
onClose?: () => void;
}

Expand All @@ -32,6 +35,7 @@ class InteractiveTerminal extends React.PureComponent<InteractiveTerminalProps,
fullscreen: false,
};

private resizeId: string = uuidv4();
private socket: WebSocket;
private term: Terminal;

Expand Down Expand Up @@ -77,6 +81,13 @@ class InteractiveTerminal extends React.PureComponent<InteractiveTerminalProps,
this.term.resize(Math.floor(dims.cols), Math.floor(dims.rows));
}

// send dims to the server
if (this.props.remoteResize) {
authFetch(`/api/resize?resize_id=${this.resizeId}&width=${dims.cols}&height=${dims.rows}`).catch(err => {
console.error(err);
})
}

this.needUpdate = false;
}
};
Expand All @@ -100,7 +111,7 @@ class InteractiveTerminal extends React.PureComponent<InteractiveTerminalProps,
});

// Open the websocket
this.socket = new WebSocket(this.props.url + "&token="+(window as any).token);
this.socket = new WebSocket(this.props.url + (this.props.remoteResize ? "&resize_id="+this.resizeId : ""));
const attachAddon = new AttachAddon(this.socket, {
bidirectional: this.props.interactive,
onClose: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class TerminalCache extends React.PureComponent<Props, State> {
{terminal.pod} -c {terminal.container}
</AdvancedCodeLine>
}
remoteResize={terminal.interactive}
closeOnConnectionLost={terminal.interactive}
closeDelay={5000}
onClose={() =>
Expand Down
6 changes: 1 addition & 5 deletions ui/src/lib/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import {ApiHostname} from "./rest";

export default function authFetch(url: string): Promise<Response> {
return fetch(`http://${ApiHostname()}${url}`, {
headers: new Headers({
"Authorization": (window as any).token,
})
})
return fetch(`http://${ApiHostname()}${url}`)
}

0 comments on commit defb68d

Please # to comment.