summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2020-01-16 21:10:53 +0100
committerGitHub <noreply@github.com>2020-01-16 21:10:53 +0100
commit74b89da27c5da20ddcbff5ea3689795ad2b720f5 (patch)
tree0a4516323bc9dd5bedf4ef75595892dcf7a27b63 /pkg
parent79fbe7252e35b086e168c55c32b6c7b8e83496bc (diff)
parentac47e80b07ddc1e56e7c4fd6b0deca9f3bdc5f54 (diff)
downloadpodman-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')
-rw-r--r--pkg/api/handlers/containers_attach.go159
-rw-r--r--pkg/api/server/register_containers.go170
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
}