diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2020-01-16 21:10:53 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-16 21:10:53 +0100 |
commit | 74b89da27c5da20ddcbff5ea3689795ad2b720f5 (patch) | |
tree | 0a4516323bc9dd5bedf4ef75595892dcf7a27b63 /pkg/api | |
parent | 79fbe7252e35b086e168c55c32b6c7b8e83496bc (diff) | |
parent | ac47e80b07ddc1e56e7c4fd6b0deca9f3bdc5f54 (diff) | |
download | podman-74b89da27c5da20ddcbff5ea3689795ad2b720f5.tar.gz podman-74b89da27c5da20ddcbff5ea3689795ad2b720f5.tar.bz2 podman-74b89da27c5da20ddcbff5ea3689795ad2b720f5.zip |
Merge pull request #4837 from mheon/rework_attach
Add an API for Attach over HTTP API
Diffstat (limited to 'pkg/api')
-rw-r--r-- | pkg/api/handlers/containers_attach.go | 159 | ||||
-rw-r--r-- | pkg/api/server/register_containers.go | 170 |
2 files changed, 329 insertions, 0 deletions
diff --git a/pkg/api/handlers/containers_attach.go b/pkg/api/handlers/containers_attach.go new file mode 100644 index 000000000..eb306348b --- /dev/null +++ b/pkg/api/handlers/containers_attach.go @@ -0,0 +1,159 @@ +package handlers + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/mux" + "github.com/gorilla/schema" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/client-go/tools/remotecommand" +) + +func AttachContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + DetachKeys string `schema:"detachKeys"` + Logs bool `schema:"logs"` + Stream bool `schema:"stream"` + Stdin bool `schema:"stdin"` + Stdout bool `schema:"stdout"` + Stderr bool `schema:"stderr"` + }{} + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Error parsing parameters", http.StatusBadRequest, err) + return + } + + muxVars := mux.Vars(r) + + // Detach keys: explicitly set to "" is very different from unset + // TODO: Our format for parsing these may be different from Docker. + var detachKeys *string + if _, found := muxVars["detachKeys"]; found { + detachKeys = &query.DetachKeys + } + + streams := new(libpod.HTTPAttachStreams) + streams.Stdout = true + streams.Stderr = true + streams.Stdin = true + useStreams := false + if _, found := muxVars["stdin"]; found { + streams.Stdin = query.Stdin + useStreams = true + } + if _, found := muxVars["stdout"]; found { + streams.Stdout = query.Stdout + useStreams = true + } + if _, found := muxVars["stderr"]; found { + streams.Stderr = query.Stderr + useStreams = true + } + if !useStreams { + streams = nil + } + if useStreams && !streams.Stdout && !streams.Stderr && !streams.Stdin { + utils.Error(w, "Parameter conflict", http.StatusBadRequest, errors.Errorf("at least one of stdin, stdout, stderr must be true")) + return + } + + // TODO: Investigate supporting these. + // Logs replays container logs over the attach socket. + // Stream seems to break things up somehow? Not 100% clear. + if query.Logs { + utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the logs parameter to attach is not presently supported")) + return + } + // We only support stream=true or unset + if _, found := muxVars["stream"]; found && query.Stream { + utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the stream parameter to attach is not presently supported")) + return + } + + name := getName(r) + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + state, err := ctr.State() + if err != nil { + utils.InternalServerError(w, err) + return + } + if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) { + utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers")) + return + } + + // Hijack the connection + hijacker, ok := w.(http.Hijacker) + if !ok { + utils.InternalServerError(w, errors.Errorf("unable to hijack connection")) + return + } + + w.WriteHeader(http.StatusSwitchingProtocols) + + connection, buffer, err := hijacker.Hijack() + if err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection")) + return + } + + logrus.Debugf("Hijack for attach of container %s successful", ctr.ID()) + + // Perform HTTP attach. + // HTTPAttach will handle everything about the connection from here on + // (including closing it and writing errors to it). + if err := ctr.HTTPAttach(connection, buffer, streams, detachKeys, nil); err != nil { + // We can't really do anything about errors anymore. HTTPAttach + // should be writing them to the connection. + logrus.Errorf("Error attaching to container %s: %v", ctr.ID(), err) + } + + logrus.Debugf("Attach for container %s completed successfully", ctr.ID()) +} + +func ResizeContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + Height uint16 `schema:"h"` + Width uint16 `schema:"w"` + }{} + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + // This is not a 400, despite the fact that is should be, for + // compatibility reasons. + utils.InternalServerError(w, errors.Wrapf(err, "error parsing query options")) + return + } + + name := getName(r) + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + newSize := remotecommand.TerminalSize{ + Width: query.Width, + Height: query.Height, + } + if err := ctr.AttachResize(newSize); err != nil { + utils.InternalServerError(w, err) + return + } + // This is not a 204, even though we write nothing, for compatibility + // reasons. + utils.WriteResponse(w, http.StatusOK, "") +} diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index b275fa4d1..dbe194cd4 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -428,6 +428,91 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '500': // "$ref": "#/responses/InternalError" r.HandleFunc(VersionedPath("/containers/{name:..*}/wait"), APIHandler(s.Context, generic.WaitContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/{nameOrID}/attach compat attach + // --- + // tags: + // - containers (compat) + // summary: Attach to a container + // description: Hijacks the connection to forward the container's standard streams to the client. + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: detachKeys + // required: false + // type: string + // description: keys to use for detaching from the container + // - in: query + // name: logs + // required: false + // type: bool + // description: Not yet supported + // - in: query + // name: stream + // required: false + // type: bool + // default: true + // description: If passed, must be set to true; stream=false is not yet supported + // - in: query + // name: stdout + // required: false + // type: bool + // description: Attach to container STDOUT + // - in: query + // name: stderr + // required: false + // type: bool + // description: Attach to container STDERR + // - in: query + // name: stdin + // required: false + // type: bool + // description: Attach to container STDIN + // produces: + // - application/json + // responses: + // '101': + // description: No error, connection has been hijacked for transporting streams. + // '400': + // "$ref": "#/responses/BadParamError" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/attach"), APIHandler(s.Context, handlers.AttachContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/{nameOrID}/resize compat resize + // --- + // tags: + // - containers (compat) + // summary: Resize a container's TTY + // description: Resize the terminal attached to a container (for use with Attach). + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: h + // type: int + // required: false + // description: Height to set for the terminal, in characters + // - in: query + // name: w + // type: int + // required: false + // description: Width to set for the terminal, in characters + // produces: + // - application/json + // responses: + // '200': + // description: no error + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/resize"), APIHandler(s.Context, handlers.ResizeContainer)).Methods(http.MethodPost) /* libpod endpoints @@ -823,5 +908,90 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '500': // "$ref": "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/attach libpod attach + // --- + // tags: + // - containers + // summary: Attach to a container + // description: Hijacks the connection to forward the container's standard streams to the client. + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: detachKeys + // required: false + // type: string + // description: keys to use for detaching from the container + // - in: query + // name: logs + // required: false + // type: bool + // description: Not yet supported + // - in: query + // name: stream + // required: false + // type: bool + // default: true + // description: If passed, must be set to true; stream=false is not yet supported + // - in: query + // name: stdout + // required: false + // type: bool + // description: Attach to container STDOUT + // - in: query + // name: stderr + // required: false + // type: bool + // description: Attach to container STDERR + // - in: query + // name: stdin + // required: false + // type: bool + // description: Attach to container STDIN + // produces: + // - application/json + // responses: + // '101': + // description: No error, connection has been hijacked for transporting streams. + // '400': + // "$ref": "#/responses/BadParamError" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/attach"), APIHandler(s.Context, handlers.AttachContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/resize libpod resize + // --- + // tags: + // - containers + // summary: Resize a container's TTY + // description: Resize the terminal attached to a container (for use with Attach). + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: h + // type: int + // required: false + // description: Height to set for the terminal, in characters + // - in: query + // name: w + // type: int + // required: false + // description: Width to set for the terminal, in characters + // produces: + // - application/json + // responses: + // '200': + // description: no error + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/resize"), APIHandler(s.Context, handlers.ResizeContainer)).Methods(http.MethodPost) return nil } |