Implement App endpoint /pipe/{id}/resize_tty/{height}/{width} to resize ttys

This commit is contained in:
Alfonso Acosta
2016-10-28 16:12:27 +00:00
committed by Simon Howe
parent 654ba35be6
commit c2f2a90b61
6 changed files with 84 additions and 3 deletions

View File

@@ -2,6 +2,7 @@ package app
import (
"net/http"
"strconv"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
@@ -15,7 +16,12 @@ func RegisterPipeRoutes(router *mux.Router, pr PipeRouter) {
router.Methods("GET").
Name("api_pipe_pipeid_check").
Path("/api/pipe/{pipeID}/check").
HandlerFunc(requestContextDecorator(checkPipe(pr, UIEnd)))
HandlerFunc(requestContextDecorator(checkPipe(pr)))
router.Methods("POST").
Name("api_pipe_pipeid_resize_tty").
Path("/api/pipe/{pipeID}/resize_tty/{height}/{width}").
HandlerFunc(requestContextDecorator(resizeTTY(pr)))
router.Methods("GET").
Name("api_pipe_pipeid").
@@ -33,7 +39,7 @@ func RegisterPipeRoutes(router *mux.Router, pr PipeRouter) {
HandlerFunc(requestContextDecorator(deletePipe(pr)))
}
func checkPipe(pr PipeRouter, end End) CtxHandlerFunc {
func checkPipe(pr PipeRouter) CtxHandlerFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["pipeID"]
exists, err := pr.Exists(ctx, id)
@@ -47,6 +53,47 @@ func checkPipe(pr PipeRouter, end End) CtxHandlerFunc {
}
}
func resizeTTY(pr PipeRouter) CtxHandlerFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
var (
height, width uint64
err error
)
id := mux.Vars(r)["pipeID"]
height, err = strconv.ParseUint(mux.Vars(r)["height"], 10, 32)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
width, err = strconv.ParseUint(mux.Vars(r)["width"], 10, 32)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
pipe, _, err := pr.Get(ctx, id, ProbeEnd)
if err != nil {
log.Debugf("Error getting pipe %s: %v", id, err)
http.NotFound(w, r)
return
}
tty := pipe.GetTTY()
if tty == nil {
// The pipe doesn't have a tty, nothing to do
log.Debugf("Error getting terminal for pipe %s", id)
http.NotFound(w, r)
return
}
if err = tty.SetSize(uint(height), uint(width)); err != nil {
log.Errorf("Error setting terminal size (%d, %d) of pipe %s: %v", height, width, id, err)
respondWith(w, http.StatusInternalServerError, err)
}
}
}
func handlePipeWs(pr PipeRouter, end End) CtxHandlerFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["pipeID"]

View File

@@ -7,12 +7,20 @@ import (
"github.com/gorilla/websocket"
)
// Pipe is a bi-directional channel from someone thing in the probe
// A TTY is a terminal with an associated height and width
type TTY interface {
SetSize(height, width uint) error
}
// Pipe is a bi-directional channel from something in the probe
// to the UI.
type Pipe interface {
Ends() (io.ReadWriter, io.ReadWriter)
CopyToWebsocket(io.ReadWriter, Websocket) error
SetTTY(TTY)
GetTTY() TTY
Close() error
Closed() bool
OnClose(func())
@@ -26,6 +34,7 @@ type pipe struct {
quit chan struct{}
closed bool
onClose func()
tty TTY
}
// NewPipeFromEnds makes a new pipe specifying its ends
@@ -65,6 +74,13 @@ func (p *pipe) Ends() (io.ReadWriter, io.ReadWriter) {
return p.port, p.starboard
}
func (p *pipe) SetTTY(tty TTY) {
p.tty = tty
}
func (p *pipe) GetTTY() TTY {
return p.tty
}
func (p *pipe) Close() error {
p.mtx.Lock()
var onClose func()

View File

@@ -105,6 +105,15 @@ func (r *registry) attachContainer(containerID string, req xfer.Request) xfer.Re
}
}
type execTTY struct {
client Client
execID string
}
func (e execTTY) SetSize(height, width uint) error {
return e.client.ResizeExecTTY(e.execID, int(height), int(width))
}
func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Response {
exec, err := r.client.CreateExec(docker_client.CreateExecOptions{
AttachStdin: true,
@@ -122,6 +131,8 @@ func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Resp
if err != nil {
return xfer.ResponseError(err)
}
pipe.SetTTY(execTTY{r.client, exec.ID})
local, _ := pipe.Ends()
cw, err := r.client.StartExecNonBlocking(exec.ID, docker_client.StartExecOptions{
Tty: true,

View File

@@ -47,6 +47,8 @@ func (mockPipe) CopyToWebsocket(io.ReadWriter, xfer.Websocket) error { return ni
func (mockPipe) Close() error { return nil }
func (mockPipe) Closed() bool { return false }
func (mockPipe) OnClose(func()) {}
func (mockPipe) GetTTY() xfer.TTY { return nil }
func (mockPipe) SetTTY(xfer.TTY) {}
func TestPipes(t *testing.T) {
oldNewPipe := controls.NewPipe

View File

@@ -86,6 +86,7 @@ type Client interface {
CreateExec(docker_client.CreateExecOptions) (*docker_client.Exec, error)
StartExecNonBlocking(string, docker_client.StartExecOptions) (docker_client.CloseWaiter, error)
Stats(docker_client.StatsOptions) error
ResizeExecTTY(id string, height, width int) error
}
func newDockerClient(endpoint string) (Client, error) {

View File

@@ -167,6 +167,10 @@ func (m *mockDockerClient) Stats(_ client.StatsOptions) error {
return fmt.Errorf("stats")
}
func (m *mockDockerClient) ResizeExecTTY(id string, height, width int) error {
return fmt.Errorf("resizeExecTTY")
}
type mockCloseWaiter struct{}
func (mockCloseWaiter) Close() error { return nil }