1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

cleanup attach api calls

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
Brian Goff 2016-01-05 16:23:24 -05:00
parent 3434860d54
commit a77b7dd227
6 changed files with 91 additions and 77 deletions

View file

@ -59,8 +59,7 @@ type monitorBackend interface {
// attachBackend includes function to implement to provide container attaching functionality. // attachBackend includes function to implement to provide container attaching functionality.
type attachBackend interface { type attachBackend interface {
ContainerAttachWithLogs(name string, c *backend.ContainerAttachWithLogsConfig) error ContainerAttach(name string, c *backend.ContainerAttachConfig) error
ContainerWsAttachWithLogs(name string, c *backend.ContainerWsAttachWithLogsConfig) error
} }
// Backend is all the methods that need to be implemented to provide container specific functionality. // Backend is all the methods that need to be implemented to provide container specific functionality.

View file

@ -3,6 +3,7 @@ package container
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -14,6 +15,7 @@ import (
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types/backend" "github.com/docker/docker/api/types/backend"
derr "github.com/docker/docker/errors" derr "github.com/docker/docker/errors"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/term"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
@ -425,18 +427,45 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo
} }
} }
attachWithLogsConfig := &backend.ContainerAttachWithLogsConfig{ hijacker, ok := w.(http.Hijacker)
Hijacker: w.(http.Hijacker), if !ok {
Upgrade: upgrade, return derr.ErrorCodeNoHijackConnection.WithArgs(containerName)
}
setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
conn, _, err := hijacker.Hijack()
if err != nil {
return nil, nil, nil, err
}
// set raw mode
conn.Write([]byte{})
if upgrade {
fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
} else {
fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
}
closer := func() error {
httputils.CloseStreams(conn)
return nil
}
return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil
}
attachConfig := &backend.ContainerAttachConfig{
GetStreams: setupStreams,
UseStdin: httputils.BoolValue(r, "stdin"), UseStdin: httputils.BoolValue(r, "stdin"),
UseStdout: httputils.BoolValue(r, "stdout"), UseStdout: httputils.BoolValue(r, "stdout"),
UseStderr: httputils.BoolValue(r, "stderr"), UseStderr: httputils.BoolValue(r, "stderr"),
Logs: httputils.BoolValue(r, "logs"), Logs: httputils.BoolValue(r, "logs"),
Stream: httputils.BoolValue(r, "stream"), Stream: httputils.BoolValue(r, "stream"),
DetachKeys: keys, DetachKeys: keys,
MuxStreams: true,
} }
return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig) return s.backend.ContainerAttach(containerName, attachConfig)
} }
func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@ -455,24 +484,44 @@ func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.Respons
} }
} }
h := websocket.Handler(func(ws *websocket.Conn) { done := make(chan struct{})
defer ws.Close() started := make(chan struct{})
wsAttachWithLogsConfig := &backend.ContainerWsAttachWithLogsConfig{ setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
InStream: ws, wsChan := make(chan *websocket.Conn)
OutStream: ws, h := func(conn *websocket.Conn) {
ErrStream: ws, wsChan <- conn
<-done
}
srv := websocket.Server{Handler: h, Handshake: nil}
go func() {
close(started)
srv.ServeHTTP(w, r)
}()
conn := <-wsChan
return conn, conn, conn, nil
}
attachConfig := &backend.ContainerAttachConfig{
GetStreams: setupStreams,
Logs: httputils.BoolValue(r, "logs"), Logs: httputils.BoolValue(r, "logs"),
Stream: httputils.BoolValue(r, "stream"), Stream: httputils.BoolValue(r, "stream"),
DetachKeys: keys, DetachKeys: keys,
UseStdin: true,
UseStdout: true,
UseStderr: true,
MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr
} }
if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil { err = s.backend.ContainerAttach(containerName, attachConfig)
logrus.Errorf("Error attaching websocket: %s", utils.GetErrorMessage(err)) close(done)
} select {
}) case <-started:
ws := websocket.Server{Handler: h, Handshake: nil} logrus.Errorf("Error attaching websocket: %s", err)
ws.ServeHTTP(w, r)
return nil return nil
default:
}
return err
} }

View file

@ -5,32 +5,25 @@ package backend
import ( import (
"io" "io"
"net/http"
"github.com/docker/engine-api/types" "github.com/docker/engine-api/types"
) )
// ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs. // ContainerAttachConfig holds the streams to use when connecting to a container to view logs.
type ContainerAttachWithLogsConfig struct { type ContainerAttachConfig struct {
Hijacker http.Hijacker GetStreams func() (io.ReadCloser, io.Writer, io.Writer, error)
Upgrade bool
UseStdin bool UseStdin bool
UseStdout bool UseStdout bool
UseStderr bool UseStderr bool
Logs bool Logs bool
Stream bool Stream bool
DetachKeys []byte DetachKeys []byte
}
// ContainerWsAttachWithLogsConfig attach with websockets, since all // Used to signify that streams are multiplexed and therefore need a StdWriter to encode stdout/sderr messages accordingly.
// stream data is delegated to the websocket to handle there. // TODO @cpuguy83: This shouldn't be needed. It was only added so that http and websocket endpoints can use the same function, and the websocket function was not using a stdwriter prior to this change...
type ContainerWsAttachWithLogsConfig struct { // HOWEVER, the websocket endpoint is using a single stream and SHOULD be encoded with stdout/stderr as is done for HTTP since it is still just a single stream.
InStream io.ReadCloser // Reader to attach to stdin of container // Since such a change is an API change unrelated to the current changeset we'll keep it as is here and change separately.
OutStream io.Writer // Writer to attach to stdout of container MuxStreams bool
ErrStream io.Writer // Writer to attach to stderr of container
Logs bool // If true return log output
Stream bool // If true return stream output
DetachKeys []byte
} }
// ContainerLogsConfig holds configs for logging operations. Exists // ContainerLogsConfig holds configs for logging operations. Exists

View file

@ -106,7 +106,7 @@ type Backend interface {
// Pull tells Docker to pull image referenced by `name`. // Pull tells Docker to pull image referenced by `name`.
PullOnBuild(name string, authConfigs map[string]types.AuthConfig, output io.Writer) (Image, error) PullOnBuild(name string, authConfigs map[string]types.AuthConfig, output io.Writer) (Image, error)
// ContainerAttach attaches to container. // ContainerAttach attaches to container.
ContainerAttachOnBuild(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error
// ContainerCreate creates a new Docker container and returns potential warnings // ContainerCreate creates a new Docker container and returns potential warnings
ContainerCreate(types.ContainerCreateConfig) (types.ContainerCreateResponse, error) ContainerCreate(types.ContainerCreateConfig) (types.ContainerCreateResponse, error)
// ContainerRm removes a container specified by `id`. // ContainerRm removes a container specified by `id`.

View file

@ -541,7 +541,7 @@ func (b *Builder) create() (string, error) {
func (b *Builder) run(cID string) (err error) { func (b *Builder) run(cID string) (err error) {
errCh := make(chan error) errCh := make(chan error)
go func() { go func() {
errCh <- b.docker.ContainerAttachOnBuild(cID, nil, b.Stdout, b.Stderr, true) errCh <- b.docker.ContainerAttachRaw(cID, nil, b.Stdout, b.Stderr, true)
}() }()
finished := make(chan struct{}) finished := make(chan struct{})

View file

@ -13,11 +13,8 @@ import (
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
) )
// ContainerAttachWithLogs attaches to logs according to the config passed in. See ContainerAttachWithLogsConfig. // ContainerAttach attaches to logs according to the config passed in. See ContainerAttachConfig.
func (daemon *Daemon) ContainerAttachWithLogs(prefixOrName string, c *backend.ContainerAttachWithLogsConfig) error { func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerAttachConfig) error {
if c.Hijacker == nil {
return derr.ErrorCodeNoHijackConnection.WithArgs(prefixOrName)
}
container, err := daemon.GetContainer(prefixOrName) container, err := daemon.GetContainer(prefixOrName)
if err != nil { if err != nil {
return derr.ErrorCodeNoSuchContainer.WithArgs(prefixOrName) return derr.ErrorCodeNoSuchContainer.WithArgs(prefixOrName)
@ -26,29 +23,15 @@ func (daemon *Daemon) ContainerAttachWithLogs(prefixOrName string, c *backend.Co
return derr.ErrorCodePausedContainer.WithArgs(prefixOrName) return derr.ErrorCodePausedContainer.WithArgs(prefixOrName)
} }
conn, _, err := c.Hijacker.Hijack() inStream, outStream, errStream, err := c.GetStreams()
if err != nil { if err != nil {
return err return err
} }
defer conn.Close() defer inStream.Close()
// Flush the options to make sure the client sets the raw mode
conn.Write([]byte{})
inStream := conn.(io.ReadCloser)
outStream := conn.(io.Writer)
if c.Upgrade { if !container.Config.Tty && c.MuxStreams {
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") errStream = stdcopy.NewStdWriter(errStream, stdcopy.Stderr)
} else {
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
}
var errStream io.Writer
if !container.Config.Tty {
errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
} else {
errStream = outStream
} }
var stdin io.ReadCloser var stdin io.ReadCloser
@ -64,32 +47,22 @@ func (daemon *Daemon) ContainerAttachWithLogs(prefixOrName string, c *backend.Co
stderr = errStream stderr = errStream
} }
if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream, c.DetachKeys); err != nil { if err := daemon.containerAttach(container, stdin, stdout, stderr, c.Logs, c.Stream, c.DetachKeys); err != nil {
fmt.Fprintf(outStream, "Error attaching: %s\n", err) fmt.Fprintf(outStream, "Error attaching: %s\n", err)
} }
return nil return nil
} }
// ContainerWsAttachWithLogs websocket connection // ContainerAttachRaw attaches the provided streams to the container's stdio
func (daemon *Daemon) ContainerWsAttachWithLogs(prefixOrName string, c *backend.ContainerWsAttachWithLogsConfig) error { func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error {
container, err := daemon.GetContainer(prefixOrName) container, err := daemon.GetContainer(prefixOrName)
if err != nil { if err != nil {
return err return err
} }
return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream, c.DetachKeys) return daemon.containerAttach(container, stdin, stdout, stderr, false, stream, nil)
} }
// ContainerAttachOnBuild attaches streams to the container cID. If stream is true, it streams the output. func (daemon *Daemon) containerAttach(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
func (daemon *Daemon) ContainerAttachOnBuild(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error {
return daemon.ContainerWsAttachWithLogs(cID, &backend.ContainerWsAttachWithLogsConfig{
InStream: stdin,
OutStream: stdout,
ErrStream: stderr,
Stream: stream,
})
}
func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
if logs { if logs {
logDriver, err := daemon.getLogger(container) logDriver, err := daemon.getLogger(container)
if err != nil { if err != nil {