Skip to content

Commit

Permalink
feat: implement realtime terminal resize handling
Browse files Browse the repository at this point in the history
  • Loading branch information
devbranch-vadym committed Aug 1, 2021
1 parent 6b47a6d commit 21bc1f3
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 21 deletions.
25 changes: 10 additions & 15 deletions pkg/portainer/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"strings"

"github.com/gorilla/websocket"
"github.com/kopoli/go-terminal-size"
"github.com/minio/pkg/wildcard"
)

Expand Down Expand Up @@ -181,26 +180,17 @@ func (r *API) getExecEndpointId(containerId string, params *ContainerExecParams)
return resp["Id"].(string), nil
}

func (r *API) getWsUrl(containerId string, params *ContainerExecParams) string {
func (r *API) getWsUrl(containerId string, params *ContainerExecParams) (string, chan<- TriggerResize) {
endpointId, err := r.getExecEndpointId(containerId, params)
if err != nil {
fmt.Println("Failed to run exec on container: ", err.Error())
os.Exit(1)
}

// TODO: Connect to a tsize channel.
s, err := tsize.GetSize()
if err != nil {
fmt.Println("GetSize failed: ", err.Error())
} else {
// TODO: That's not really a correct approach; Portainer is expecting resize request _after_ WS connection
// is established.
go r.resizeTerminal(endpointId, TerminalDimensions{Height: s.Height, Width: s.Width})
}
resize, _, _ := r.handleTerminalResize(endpointId)

jwt, _ := r.getJwt()

return r.formatWsApiUrl() + "/websocket/exec?token=" + jwt + "&endpointId=1&id=" + endpointId
return r.formatWsApiUrl() + "/websocket/exec?token=" + jwt + "&endpointId=1&id=" + endpointId, resize
}

func (r *API) getWSConn(wsUrl string) *websocket.Conn {
Expand All @@ -220,7 +210,12 @@ func (r *API) GetContainerConn(params *ContainerExecParams) *websocket.Conn {
fmt.Println("Searching for container " + params.ContainerName)
containerId := r.getContainerId(params)
fmt.Println("Getting access token")
wsurl := r.getWsUrl(containerId, params)
wsurl, resize := r.getWsUrl(containerId, params)
fmt.Println("Connecting to a shell ...")
return r.getWSConn(wsurl)
conn := r.getWSConn(wsurl)

// Trigger terminal resize after connection is established.
resize <- TriggerResize{}

return conn
}
45 changes: 39 additions & 6 deletions pkg/portainer/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/json"
"net/http"
"strconv"

tsize "github.com/kopoli/go-terminal-size"
)

// TerminalDimensions is a simple struct containing current user's terminal width and height.
Expand All @@ -13,17 +15,48 @@ type TerminalDimensions struct {
Height int
}

func (r *API) resizeTerminal(execEndpointId string, dimensions TerminalDimensions) (map[string]interface{}, error) {
func (r *API) resizeTerminal(execEndpointId string, size tsize.Size) error {
jsonBodyData := map[string]interface{}{
"Height": dimensions.Height,
"Width": dimensions.Width,
"Height": size.Height,
"Width": size.Width,
"id": execEndpointId,
}
body, err := json.Marshal(jsonBodyData)
if err != nil {
return nil, err
return err
}
req, _ := http.NewRequest("POST", r.formatHttpApiUrl()+"/endpoints/"+strconv.Itoa(r.Endpoint)+"/docker/exec/"+execEndpointId+"/resize?h="+strconv.Itoa(dimensions.Height)+"&w="+strconv.Itoa(dimensions.Width), bytes.NewReader(body))
req, _ := http.NewRequest("POST", r.formatHttpApiUrl()+"/endpoints/"+strconv.Itoa(r.Endpoint)+"/docker/exec/"+execEndpointId+"/resize?h="+strconv.Itoa(size.Height)+"&w="+strconv.Itoa(size.Width), bytes.NewReader(body))
_, err = r.makeObjReq(req, true)

return err
}

type TriggerResize struct{}

func (r *API) handleTerminalResize(execEndpointId string) (chan<- TriggerResize, <-chan error, error) {
sizeListener, err := tsize.NewSizeListener()
if err != nil {
// Error creating SizeListener
return nil, nil, err
}
resize := make(chan TriggerResize)
errChan := make(chan error)

go func() {
for {
select {
case <-resize:
size, err := tsize.GetSize()
if err != nil {
errChan <- err
}
r.resizeTerminal(execEndpointId, size)

case newSize := <-sizeListener.Change:
r.resizeTerminal(execEndpointId, newSize)
}
}
}()

return r.makeObjReq(req, true)
return resize, errChan, nil
}

0 comments on commit 21bc1f3

Please sign in to comment.