github.com/moby/docker@v26.1.3+incompatible/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/containerd/log" 11 "github.com/docker/docker/api/server/httputils" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/container" 14 "github.com/docker/docker/api/types/versions" 15 "github.com/docker/docker/errdefs" 16 "github.com/docker/docker/pkg/stdcopy" 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 log.G(ctx).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 var ( 75 execName = vars["name"] 76 stdin, inStream io.ReadCloser 77 stdout, stderr, outStream io.Writer 78 ) 79 80 execStartCheck := &types.ExecStartCheck{} 81 if err := httputils.ReadJSON(r, execStartCheck); err != nil { 82 return err 83 } 84 85 if exists, err := s.backend.ExecExists(execName); !exists { 86 return err 87 } 88 89 if execStartCheck.ConsoleSize != nil { 90 version := httputils.VersionFromContext(ctx) 91 92 // Not supported before 1.42 93 if versions.LessThan(version, "1.42") { 94 execStartCheck.ConsoleSize = nil 95 } 96 97 // No console without tty 98 if !execStartCheck.Tty { 99 execStartCheck.ConsoleSize = nil 100 } 101 } 102 103 if !execStartCheck.Detach { 104 var err error 105 // Setting up the streaming http interface. 106 inStream, outStream, err = httputils.HijackConnection(w) 107 if err != nil { 108 return err 109 } 110 defer httputils.CloseStreams(inStream, outStream) 111 112 if _, ok := r.Header["Upgrade"]; ok { 113 contentType := types.MediaTypeRawStream 114 if !execStartCheck.Tty && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") { 115 contentType = types.MediaTypeMultiplexedStream 116 } 117 fmt.Fprint(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: "+contentType+"\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n") 118 } else { 119 fmt.Fprint(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n") 120 } 121 122 // copy headers that were removed as part of hijack 123 if err := w.Header().WriteSubset(outStream, nil); err != nil { 124 return err 125 } 126 fmt.Fprint(outStream, "\r\n") 127 128 stdin = inStream 129 stdout = outStream 130 if !execStartCheck.Tty { 131 stderr = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 132 stdout = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 133 } 134 } 135 136 options := container.ExecStartOptions{ 137 Stdin: stdin, 138 Stdout: stdout, 139 Stderr: stderr, 140 ConsoleSize: execStartCheck.ConsoleSize, 141 } 142 143 // Now run the user process in container. 144 // Maybe we should we pass ctx here if we're not detaching? 145 if err := s.backend.ContainerExecStart(context.Background(), execName, options); err != nil { 146 if execStartCheck.Detach { 147 return err 148 } 149 stdout.Write([]byte(err.Error() + "\r\n")) 150 log.G(ctx).Errorf("Error running exec %s in container: %v", execName, err) 151 } 152 return nil 153 } 154 155 func (s *containerRouter) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 156 if err := httputils.ParseForm(r); err != nil { 157 return err 158 } 159 height, err := strconv.Atoi(r.Form.Get("h")) 160 if err != nil { 161 return errdefs.InvalidParameter(err) 162 } 163 width, err := strconv.Atoi(r.Form.Get("w")) 164 if err != nil { 165 return errdefs.InvalidParameter(err) 166 } 167 168 return s.backend.ContainerExecResize(vars["name"], height, width) 169 }