github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/api/server/router/container/exec.go (about) 1 package container // import "github.com/docker/docker/api/server/router/container" 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "strconv" 9 10 "github.com/docker/docker/api/server/httputils" 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/api/types/versions" 14 "github.com/docker/docker/errdefs" 15 "github.com/docker/docker/pkg/stdcopy" 16 "github.com/sirupsen/logrus" 17 ) 18 19 func (s *containerRouter) getExecByID(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 20 eConfig, err := s.backend.ContainerExecInspect(vars["id"]) 21 if err != nil { 22 return err 23 } 24 25 return httputils.WriteJSON(w, http.StatusOK, eConfig) 26 } 27 28 type execCommandError struct{} 29 30 func (execCommandError) Error() string { 31 return "No exec command specified" 32 } 33 34 func (execCommandError) InvalidParameter() {} 35 36 func (s *containerRouter) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 37 if err := httputils.ParseForm(r); err != nil { 38 return err 39 } 40 41 execConfig := &types.ExecConfig{} 42 if err := httputils.ReadJSON(r, execConfig); err != nil { 43 return err 44 } 45 46 if len(execConfig.Cmd) == 0 { 47 return execCommandError{} 48 } 49 50 version := httputils.VersionFromContext(ctx) 51 if versions.LessThan(version, "1.42") { 52 // Not supported by API versions before 1.42 53 execConfig.ConsoleSize = nil 54 } 55 56 // Register an instance of Exec in container. 57 id, err := s.backend.ContainerExecCreate(vars["name"], execConfig) 58 if err != nil { 59 logrus.Errorf("Error setting up exec command in container %s: %v", vars["name"], err) 60 return err 61 } 62 63 return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ 64 ID: id, 65 }) 66 } 67 68 // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. 69 func (s *containerRouter) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 70 if err := httputils.ParseForm(r); err != nil { 71 return err 72 } 73 74 version := httputils.VersionFromContext(ctx) 75 if versions.LessThan(version, "1.22") { 76 // API versions before 1.22 did not enforce application/json content-type. 77 // Allow older clients to work by patching the content-type. 78 if r.Header.Get("Content-Type") != "application/json" { 79 r.Header.Set("Content-Type", "application/json") 80 } 81 } 82 83 var ( 84 execName = vars["name"] 85 stdin, inStream io.ReadCloser 86 stdout, stderr, outStream io.Writer 87 ) 88 89 execStartCheck := &types.ExecStartCheck{} 90 if err := httputils.ReadJSON(r, execStartCheck); err != nil { 91 return err 92 } 93 94 if exists, err := s.backend.ExecExists(execName); !exists { 95 return err 96 } 97 98 if execStartCheck.ConsoleSize != nil { 99 // Not supported before 1.42 100 if versions.LessThan(version, "1.42") { 101 execStartCheck.ConsoleSize = nil 102 } 103 104 // No console without tty 105 if !execStartCheck.Tty { 106 execStartCheck.ConsoleSize = nil 107 } 108 } 109 110 if !execStartCheck.Detach { 111 var err error 112 // Setting up the streaming http interface. 113 inStream, outStream, err = httputils.HijackConnection(w) 114 if err != nil { 115 return err 116 } 117 defer httputils.CloseStreams(inStream, outStream) 118 119 if _, ok := r.Header["Upgrade"]; ok { 120 contentType := types.MediaTypeRawStream 121 if !execStartCheck.Tty && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") { 122 contentType = types.MediaTypeMultiplexedStream 123 } 124 fmt.Fprint(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: "+contentType+"\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n") 125 } else { 126 fmt.Fprint(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n") 127 } 128 129 // copy headers that were removed as part of hijack 130 if err := w.Header().WriteSubset(outStream, nil); err != nil { 131 return err 132 } 133 fmt.Fprint(outStream, "\r\n") 134 135 stdin = inStream 136 stdout = outStream 137 if !execStartCheck.Tty { 138 stderr = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 139 stdout = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 140 } 141 } 142 143 options := container.ExecStartOptions{ 144 Stdin: stdin, 145 Stdout: stdout, 146 Stderr: stderr, 147 ConsoleSize: execStartCheck.ConsoleSize, 148 } 149 150 // Now run the user process in container. 151 // Maybe we should we pass ctx here if we're not detaching? 152 if err := s.backend.ContainerExecStart(context.Background(), execName, options); err != nil { 153 if execStartCheck.Detach { 154 return err 155 } 156 stdout.Write([]byte(err.Error() + "\r\n")) 157 logrus.Errorf("Error running exec %s in container: %v", execName, err) 158 } 159 return nil 160 } 161 162 func (s *containerRouter) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 163 if err := httputils.ParseForm(r); err != nil { 164 return err 165 } 166 height, err := strconv.Atoi(r.Form.Get("h")) 167 if err != nil { 168 return errdefs.InvalidParameter(err) 169 } 170 width, err := strconv.Atoi(r.Form.Get("w")) 171 if err != nil { 172 return errdefs.InvalidParameter(err) 173 } 174 175 return s.backend.ContainerExecResize(vars["name"], height, width) 176 }