mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-28 17:51:21 +00:00
Implement resize control for docker TTYs
This commit is contained in:
committed by
Simon Howe
parent
37ba071feb
commit
2443f7a3c5
@@ -23,8 +23,9 @@ type Response struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
|
||||
// Pipe specific fields
|
||||
Pipe string `json:"pipe,omitempty"`
|
||||
RawTTY bool `json:"raw_tty,omitempty"`
|
||||
Pipe string `json:"pipe,omitempty"`
|
||||
RawTTY bool `json:"raw_tty,omitempty"`
|
||||
ResizeTTYControl string `json:"resize_tty_control,omitempty"`
|
||||
|
||||
// Remove specific fields
|
||||
RemovedNode string `json:"removedNode,omitempty"` // Set if node was removed
|
||||
|
||||
@@ -7,20 +7,12 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// 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())
|
||||
@@ -34,7 +26,6 @@ type pipe struct {
|
||||
quit chan struct{}
|
||||
closed bool
|
||||
onClose func()
|
||||
tty TTY
|
||||
}
|
||||
|
||||
// NewPipeFromEnds makes a new pipe specifying its ends
|
||||
@@ -74,13 +65,6 @@ 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()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
docker_client "github.com/fsouza/go-dockerclient"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
@@ -20,6 +22,7 @@ const (
|
||||
RemoveContainer = "docker_remove_container"
|
||||
AttachContainer = "docker_attach_container"
|
||||
ExecContainer = "docker_exec_container"
|
||||
ResizeExecTTY = "docker_resize_exec_tty"
|
||||
|
||||
waitTime = 10
|
||||
)
|
||||
@@ -105,15 +108,6 @@ 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,
|
||||
@@ -131,7 +125,6 @@ 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{
|
||||
@@ -144,11 +137,19 @@ func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Resp
|
||||
if err != nil {
|
||||
return xfer.ResponseError(err)
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
r.pipeIDToexecID[id] = exec.ID
|
||||
r.Unlock()
|
||||
|
||||
pipe.OnClose(func() {
|
||||
if err := cw.Close(); err != nil {
|
||||
log.Errorf("Error closing exec in container %s: %v", containerID, err)
|
||||
return
|
||||
}
|
||||
r.Lock()
|
||||
delete(r.pipeIDToexecID, id)
|
||||
r.Unlock()
|
||||
})
|
||||
go func() {
|
||||
if err := cw.Wait(); err != nil {
|
||||
@@ -157,11 +158,57 @@ func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Resp
|
||||
pipe.Close()
|
||||
}()
|
||||
return xfer.Response{
|
||||
Pipe: id,
|
||||
RawTTY: true,
|
||||
Pipe: id,
|
||||
RawTTY: true,
|
||||
ResizeTTYControl: ResizeExecTTY,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) resizeExecTTY(containerID string, req xfer.Request) xfer.Response {
|
||||
var (
|
||||
height, width int
|
||||
err error
|
||||
)
|
||||
|
||||
pipeID, ok := req.ControlArgs["pipeID"]
|
||||
if !ok {
|
||||
return xfer.ResponseErrorf("Missing argument: pipeID")
|
||||
}
|
||||
heightS, ok := req.ControlArgs["height"]
|
||||
if !ok {
|
||||
return xfer.ResponseErrorf("Missing argument: height")
|
||||
}
|
||||
widthS, ok := req.ControlArgs["width"]
|
||||
if !ok {
|
||||
return xfer.ResponseErrorf("Missing argument: width")
|
||||
}
|
||||
|
||||
height, err = strconv.Atoi(heightS)
|
||||
if err != nil {
|
||||
return xfer.ResponseErrorf("Bad parameter: height (%q): %v", heightS, err)
|
||||
}
|
||||
width, err = strconv.Atoi(widthS)
|
||||
if err != nil {
|
||||
return xfer.ResponseErrorf("Bad parameter: width (%q): %v", widthS, err)
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
execID, ok := r.pipeIDToexecID[pipeID]
|
||||
r.Unlock()
|
||||
|
||||
if !ok {
|
||||
return xfer.ResponseErrorf("Unknown pipeID (%q)", pipeID)
|
||||
}
|
||||
|
||||
if r.client.ResizeExecTTY(execID, int(height), int(width)); err != nil {
|
||||
return xfer.ResponseErrorf(
|
||||
"Error setting terminal size (%d, %d) of pipe %s: %v",
|
||||
height, width, pipeID, err)
|
||||
}
|
||||
|
||||
return xfer.Response{}
|
||||
}
|
||||
|
||||
func captureContainerID(f func(string, xfer.Request) xfer.Response) func(xfer.Request) xfer.Response {
|
||||
return func(req xfer.Request) xfer.Response {
|
||||
containerID, ok := report.ParseContainerNodeID(req.NodeID)
|
||||
@@ -182,6 +229,7 @@ func (r *registry) registerControls() {
|
||||
RemoveContainer: captureContainerID(r.removeContainer),
|
||||
AttachContainer: captureContainerID(r.attachContainer),
|
||||
ExecContainer: captureContainerID(r.execContainer),
|
||||
ResizeExecTTY: captureContainerID(r.resizeExecTTY),
|
||||
}
|
||||
r.handlerRegistry.Batch(nil, controls)
|
||||
}
|
||||
@@ -196,6 +244,7 @@ func (r *registry) deregisterControls() {
|
||||
RemoveContainer,
|
||||
AttachContainer,
|
||||
ExecContainer,
|
||||
ResizeExecTTY,
|
||||
}
|
||||
r.handlerRegistry.Batch(controls, nil)
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ type registry struct {
|
||||
containersByPID map[int]Container
|
||||
images map[string]docker_client.APIImages
|
||||
networks []docker_client.Network
|
||||
pipeIDToexecID map[string]string
|
||||
}
|
||||
|
||||
// Client interface for mocking.
|
||||
@@ -104,6 +105,7 @@ func NewRegistry(interval time.Duration, pipes controls.PipeClient, collectStats
|
||||
containers: radix.New(),
|
||||
containersByPID: map[int]Container{},
|
||||
images: map[string]docker_client.APIImages{},
|
||||
pipeIDToexecID: map[string]string{},
|
||||
|
||||
client: client,
|
||||
pipes: pipes,
|
||||
|
||||
Reference in New Issue
Block a user