github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/api/handlers/compat/containers_attach.go (about) 1 package compat 2 3 import ( 4 "fmt" 5 "net/http" 6 7 "github.com/containers/libpod/libpod" 8 "github.com/containers/libpod/libpod/define" 9 "github.com/containers/libpod/pkg/api/handlers/utils" 10 "github.com/gorilla/schema" 11 "github.com/pkg/errors" 12 "github.com/sirupsen/logrus" 13 "k8s.io/client-go/tools/remotecommand" 14 ) 15 16 func AttachContainer(w http.ResponseWriter, r *http.Request) { 17 runtime := r.Context().Value("runtime").(*libpod.Runtime) 18 decoder := r.Context().Value("decoder").(*schema.Decoder) 19 20 query := struct { 21 DetachKeys string `schema:"detachKeys"` 22 Logs bool `schema:"logs"` 23 Stream bool `schema:"stream"` 24 Stdin bool `schema:"stdin"` 25 Stdout bool `schema:"stdout"` 26 Stderr bool `schema:"stderr"` 27 }{ 28 Stream: true, 29 } 30 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 31 utils.Error(w, "Error parsing parameters", http.StatusBadRequest, err) 32 return 33 } 34 35 // Detach keys: explicitly set to "" is very different from unset 36 // TODO: Our format for parsing these may be different from Docker. 37 var detachKeys *string 38 if _, found := r.URL.Query()["detachKeys"]; found { 39 detachKeys = &query.DetachKeys 40 } 41 42 streams := new(libpod.HTTPAttachStreams) 43 streams.Stdout = true 44 streams.Stderr = true 45 streams.Stdin = true 46 useStreams := false 47 if _, found := r.URL.Query()["stdin"]; found { 48 streams.Stdin = query.Stdin 49 useStreams = true 50 } 51 if _, found := r.URL.Query()["stdout"]; found { 52 streams.Stdout = query.Stdout 53 useStreams = true 54 } 55 if _, found := r.URL.Query()["stderr"]; found { 56 streams.Stderr = query.Stderr 57 useStreams = true 58 } 59 if !useStreams { 60 streams = nil 61 } 62 if useStreams && !streams.Stdout && !streams.Stderr && !streams.Stdin { 63 utils.Error(w, "Parameter conflict", http.StatusBadRequest, errors.Errorf("at least one of stdin, stdout, stderr must be true")) 64 return 65 } 66 67 // At least one of these must be set 68 if !query.Stream && !query.Logs { 69 utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("at least one of Logs or Stream must be set")) 70 return 71 } 72 73 name := utils.GetName(r) 74 ctr, err := runtime.LookupContainer(name) 75 if err != nil { 76 utils.ContainerNotFound(w, name, err) 77 return 78 } 79 80 state, err := ctr.State() 81 if err != nil { 82 utils.InternalServerError(w, err) 83 return 84 } 85 // For Docker compatibility, we need to re-initialize containers in these states. 86 if state == define.ContainerStateConfigured || state == define.ContainerStateExited { 87 if err := ctr.Init(r.Context()); err != nil { 88 utils.InternalServerError(w, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID())) 89 return 90 } 91 } else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) { 92 utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers")) 93 return 94 } 95 96 // Hijack the connection 97 hijacker, ok := w.(http.Hijacker) 98 if !ok { 99 utils.InternalServerError(w, errors.Errorf("unable to hijack connection")) 100 return 101 } 102 103 connection, buffer, err := hijacker.Hijack() 104 if err != nil { 105 utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection")) 106 return 107 } 108 109 // This header string sourced from Docker: 110 // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go 111 // Using literally to ensure compatability with existing clients. 112 fmt.Fprintf(connection, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") 113 114 logrus.Debugf("Hijack for attach of container %s successful", ctr.ID()) 115 116 // Perform HTTP attach. 117 // HTTPAttach will handle everything about the connection from here on 118 // (including closing it and writing errors to it). 119 if err := ctr.HTTPAttach(connection, buffer, streams, detachKeys, nil, query.Stream, query.Logs); err != nil { 120 // We can't really do anything about errors anymore. HTTPAttach 121 // should be writing them to the connection. 122 logrus.Errorf("Error attaching to container %s: %v", ctr.ID(), err) 123 } 124 125 logrus.Debugf("Attach for container %s completed successfully", ctr.ID()) 126 } 127 128 func ResizeContainer(w http.ResponseWriter, r *http.Request) { 129 runtime := r.Context().Value("runtime").(*libpod.Runtime) 130 decoder := r.Context().Value("decoder").(*schema.Decoder) 131 132 query := struct { 133 Height uint16 `schema:"h"` 134 Width uint16 `schema:"w"` 135 }{} 136 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 137 // This is not a 400, despite the fact that is should be, for 138 // compatibility reasons. 139 utils.InternalServerError(w, errors.Wrapf(err, "error parsing query options")) 140 return 141 } 142 143 name := utils.GetName(r) 144 ctr, err := runtime.LookupContainer(name) 145 if err != nil { 146 utils.ContainerNotFound(w, name, err) 147 return 148 } 149 150 newSize := remotecommand.TerminalSize{ 151 Width: query.Width, 152 Height: query.Height, 153 } 154 if err := ctr.AttachResize(newSize); err != nil { 155 utils.InternalServerError(w, err) 156 return 157 } 158 // This is not a 204, even though we write nothing, for compatibility 159 // reasons. 160 utils.WriteResponse(w, http.StatusOK, "") 161 }