mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
This hasn't been found in the wild but by code inspection. If we fail the docker API call, the pipe is never closed. Close it before returning.
224 lines
6.2 KiB
Go
224 lines
6.2 KiB
Go
package docker
|
|
|
|
import (
|
|
docker_client "github.com/fsouza/go-dockerclient"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
|
|
"github.com/weaveworks/scope/common/xfer"
|
|
"github.com/weaveworks/scope/probe/controls"
|
|
"github.com/weaveworks/scope/report"
|
|
)
|
|
|
|
// Control IDs used by the docker integration.
|
|
const (
|
|
StopContainer = "docker_stop_container"
|
|
StartContainer = "docker_start_container"
|
|
RestartContainer = "docker_restart_container"
|
|
PauseContainer = "docker_pause_container"
|
|
UnpauseContainer = "docker_unpause_container"
|
|
RemoveContainer = "docker_remove_container"
|
|
AttachContainer = "docker_attach_container"
|
|
ExecContainer = "docker_exec_container"
|
|
ResizeExecTTY = "docker_resize_exec_tty"
|
|
|
|
waitTime = 10
|
|
)
|
|
|
|
func (r *registry) stopContainer(containerID string, _ xfer.Request) xfer.Response {
|
|
log.Infof("Stopping container %s", containerID)
|
|
return xfer.ResponseError(r.client.StopContainer(containerID, waitTime))
|
|
}
|
|
|
|
func (r *registry) startContainer(containerID string, _ xfer.Request) xfer.Response {
|
|
log.Infof("Starting container %s", containerID)
|
|
return xfer.ResponseError(r.client.StartContainer(containerID, nil))
|
|
}
|
|
|
|
func (r *registry) restartContainer(containerID string, _ xfer.Request) xfer.Response {
|
|
log.Infof("Restarting container %s", containerID)
|
|
return xfer.ResponseError(r.client.RestartContainer(containerID, waitTime))
|
|
}
|
|
|
|
func (r *registry) pauseContainer(containerID string, _ xfer.Request) xfer.Response {
|
|
log.Infof("Pausing container %s", containerID)
|
|
return xfer.ResponseError(r.client.PauseContainer(containerID))
|
|
}
|
|
|
|
func (r *registry) unpauseContainer(containerID string, _ xfer.Request) xfer.Response {
|
|
log.Infof("Unpausing container %s", containerID)
|
|
return xfer.ResponseError(r.client.UnpauseContainer(containerID))
|
|
}
|
|
|
|
func (r *registry) removeContainer(containerID string, req xfer.Request) xfer.Response {
|
|
log.Infof("Removing container %s", containerID)
|
|
if err := r.client.RemoveContainer(docker_client.RemoveContainerOptions{
|
|
ID: containerID,
|
|
}); err != nil {
|
|
return xfer.ResponseError(err)
|
|
}
|
|
return xfer.Response{
|
|
RemovedNode: req.NodeID,
|
|
}
|
|
}
|
|
|
|
func (r *registry) attachContainer(containerID string, req xfer.Request) xfer.Response {
|
|
c, ok := r.GetContainer(containerID)
|
|
if !ok {
|
|
return xfer.ResponseErrorf("Not found: %s", containerID)
|
|
}
|
|
|
|
hasTTY := c.HasTTY()
|
|
id, pipe, err := controls.NewPipe(r.pipes, req.AppID)
|
|
if err != nil {
|
|
return xfer.ResponseError(err)
|
|
}
|
|
local, _ := pipe.Ends()
|
|
cw, err := r.client.AttachToContainerNonBlocking(docker_client.AttachToContainerOptions{
|
|
Container: containerID,
|
|
RawTerminal: hasTTY,
|
|
Stream: true,
|
|
Stdin: true,
|
|
Stdout: true,
|
|
Stderr: true,
|
|
InputStream: local,
|
|
OutputStream: local,
|
|
ErrorStream: local,
|
|
})
|
|
if err != nil {
|
|
pipe.Close()
|
|
return xfer.ResponseError(err)
|
|
}
|
|
pipe.OnClose(func() {
|
|
if err := cw.Close(); err != nil {
|
|
log.Errorf("Error closing attachment to container %s: %v", containerID, err)
|
|
return
|
|
}
|
|
})
|
|
go func() {
|
|
if err := cw.Wait(); err != nil {
|
|
log.Errorf("Error waiting on attachment to container %s: %v", containerID, err)
|
|
}
|
|
pipe.Close()
|
|
}()
|
|
return xfer.Response{
|
|
Pipe: id,
|
|
RawTTY: hasTTY,
|
|
}
|
|
}
|
|
|
|
func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Response {
|
|
exec, err := r.client.CreateExec(docker_client.CreateExecOptions{
|
|
AttachStdin: true,
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
Tty: true,
|
|
Cmd: []string{"/bin/sh", "-c", "TERM=xterm exec $( (type getent > /dev/null 2>&1 && getent passwd root | cut -d: -f7 2>/dev/null) || echo /bin/sh)"},
|
|
Container: containerID,
|
|
})
|
|
if err != nil {
|
|
return xfer.ResponseError(err)
|
|
}
|
|
|
|
id, pipe, err := controls.NewPipe(r.pipes, req.AppID)
|
|
if err != nil {
|
|
return xfer.ResponseError(err)
|
|
}
|
|
|
|
local, _ := pipe.Ends()
|
|
cw, err := r.client.StartExecNonBlocking(exec.ID, docker_client.StartExecOptions{
|
|
Tty: true,
|
|
RawTerminal: true,
|
|
InputStream: local,
|
|
OutputStream: local,
|
|
ErrorStream: local,
|
|
})
|
|
if err != nil {
|
|
pipe.Close()
|
|
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 {
|
|
log.Errorf("Error waiting on exec in container %s: %v", containerID, err)
|
|
}
|
|
pipe.Close()
|
|
}()
|
|
return xfer.Response{
|
|
Pipe: id,
|
|
RawTTY: true,
|
|
ResizeTTYControl: ResizeExecTTY,
|
|
}
|
|
}
|
|
|
|
func (r *registry) resizeExecTTY(pipeID string, height, width uint) xfer.Response {
|
|
r.Lock()
|
|
execID, ok := r.pipeIDToexecID[pipeID]
|
|
r.Unlock()
|
|
|
|
if !ok {
|
|
return xfer.ResponseErrorf("Unknown pipeID (%q)", pipeID)
|
|
}
|
|
|
|
if err := 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)
|
|
if !ok {
|
|
return xfer.ResponseErrorf("Invalid ID: %s", req.NodeID)
|
|
}
|
|
return f(containerID, req)
|
|
}
|
|
}
|
|
|
|
func (r *registry) registerControls() {
|
|
controls := map[string]xfer.ControlHandlerFunc{
|
|
StopContainer: captureContainerID(r.stopContainer),
|
|
StartContainer: captureContainerID(r.startContainer),
|
|
RestartContainer: captureContainerID(r.restartContainer),
|
|
PauseContainer: captureContainerID(r.pauseContainer),
|
|
UnpauseContainer: captureContainerID(r.unpauseContainer),
|
|
RemoveContainer: captureContainerID(r.removeContainer),
|
|
AttachContainer: captureContainerID(r.attachContainer),
|
|
ExecContainer: captureContainerID(r.execContainer),
|
|
ResizeExecTTY: xfer.ResizeTTYControlWrapper(r.resizeExecTTY),
|
|
}
|
|
r.handlerRegistry.Batch(nil, controls)
|
|
}
|
|
|
|
func (r *registry) deregisterControls() {
|
|
controls := []string{
|
|
StopContainer,
|
|
StartContainer,
|
|
RestartContainer,
|
|
PauseContainer,
|
|
UnpauseContainer,
|
|
RemoveContainer,
|
|
AttachContainer,
|
|
ExecContainer,
|
|
ResizeExecTTY,
|
|
}
|
|
r.handlerRegistry.Batch(controls, nil)
|
|
}
|