github.com/portworx/docker@v1.12.1/api/server/router/container/container_routes.go (about) 1 package container 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/http" 8 "strconv" 9 "strings" 10 "syscall" 11 "time" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/docker/api/server/httputils" 15 "github.com/docker/docker/api/types/backend" 16 "github.com/docker/docker/pkg/ioutils" 17 "github.com/docker/docker/pkg/signal" 18 "github.com/docker/engine-api/types" 19 "github.com/docker/engine-api/types/container" 20 "github.com/docker/engine-api/types/filters" 21 "github.com/docker/engine-api/types/versions" 22 "golang.org/x/net/context" 23 "golang.org/x/net/websocket" 24 ) 25 26 func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 27 if err := httputils.ParseForm(r); err != nil { 28 return err 29 } 30 filter, err := filters.FromParam(r.Form.Get("filters")) 31 if err != nil { 32 return err 33 } 34 35 config := &types.ContainerListOptions{ 36 All: httputils.BoolValue(r, "all"), 37 Size: httputils.BoolValue(r, "size"), 38 Since: r.Form.Get("since"), 39 Before: r.Form.Get("before"), 40 Filter: filter, 41 } 42 43 if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { 44 limit, err := strconv.Atoi(tmpLimit) 45 if err != nil { 46 return err 47 } 48 config.Limit = limit 49 } 50 51 containers, err := s.backend.Containers(config) 52 if err != nil { 53 return err 54 } 55 56 return httputils.WriteJSON(w, http.StatusOK, containers) 57 } 58 59 func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 60 if err := httputils.ParseForm(r); err != nil { 61 return err 62 } 63 64 stream := httputils.BoolValueOrDefault(r, "stream", true) 65 if !stream { 66 w.Header().Set("Content-Type", "application/json") 67 } 68 69 config := &backend.ContainerStatsConfig{ 70 Stream: stream, 71 OutStream: w, 72 Version: string(httputils.VersionFromContext(ctx)), 73 } 74 75 return s.backend.ContainerStats(ctx, vars["name"], config) 76 } 77 78 func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 79 if err := httputils.ParseForm(r); err != nil { 80 return err 81 } 82 83 // Args are validated before the stream starts because when it starts we're 84 // sending HTTP 200 by writing an empty chunk of data to tell the client that 85 // daemon is going to stream. By sending this initial HTTP 200 we can't report 86 // any error after the stream starts (i.e. container not found, wrong parameters) 87 // with the appropriate status code. 88 stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr") 89 if !(stdout || stderr) { 90 return fmt.Errorf("Bad parameters: you must choose at least one stream") 91 } 92 93 containerName := vars["name"] 94 logsConfig := &backend.ContainerLogsConfig{ 95 ContainerLogsOptions: types.ContainerLogsOptions{ 96 Follow: httputils.BoolValue(r, "follow"), 97 Timestamps: httputils.BoolValue(r, "timestamps"), 98 Since: r.Form.Get("since"), 99 Tail: r.Form.Get("tail"), 100 ShowStdout: stdout, 101 ShowStderr: stderr, 102 Details: httputils.BoolValue(r, "details"), 103 }, 104 OutStream: w, 105 } 106 107 chStarted := make(chan struct{}) 108 if err := s.backend.ContainerLogs(ctx, containerName, logsConfig, chStarted); err != nil { 109 select { 110 case <-chStarted: 111 // The client may be expecting all of the data we're sending to 112 // be multiplexed, so send it through OutStream, which will 113 // have been set up to handle that if needed. 114 fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %v\n", err) 115 default: 116 return err 117 } 118 } 119 120 return nil 121 } 122 123 func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 124 return s.backend.ContainerExport(vars["name"], w) 125 } 126 127 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 128 // If contentLength is -1, we can assumed chunked encoding 129 // or more technically that the length is unknown 130 // https://golang.org/src/pkg/net/http/request.go#L139 131 // net/http otherwise seems to swallow any headers related to chunked encoding 132 // including r.TransferEncoding 133 // allow a nil body for backwards compatibility 134 135 version := httputils.VersionFromContext(ctx) 136 var hostConfig *container.HostConfig 137 // A non-nil json object is at least 7 characters. 138 if r.ContentLength > 7 || r.ContentLength == -1 { 139 if versions.GreaterThanOrEqualTo(version, "1.24") { 140 return validationError{fmt.Errorf("starting container with HostConfig was deprecated since v1.10 and removed in v1.12")} 141 } 142 143 if err := httputils.CheckForJSON(r); err != nil { 144 return err 145 } 146 147 c, err := s.decoder.DecodeHostConfig(r.Body) 148 if err != nil { 149 return err 150 } 151 hostConfig = c 152 } 153 154 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 155 if err := s.backend.ContainerStart(vars["name"], hostConfig, validateHostname); err != nil { 156 return err 157 } 158 w.WriteHeader(http.StatusNoContent) 159 return nil 160 } 161 162 func (s *containerRouter) postContainersStop(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 167 seconds, _ := strconv.Atoi(r.Form.Get("t")) 168 169 if err := s.backend.ContainerStop(vars["name"], seconds); err != nil { 170 return err 171 } 172 w.WriteHeader(http.StatusNoContent) 173 174 return nil 175 } 176 177 type errContainerIsRunning interface { 178 ContainerIsRunning() bool 179 } 180 181 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 182 if err := httputils.ParseForm(r); err != nil { 183 return err 184 } 185 186 var sig syscall.Signal 187 name := vars["name"] 188 189 // If we have a signal, look at it. Otherwise, do nothing 190 if sigStr := r.Form.Get("signal"); sigStr != "" { 191 var err error 192 if sig, err = signal.ParseSignal(sigStr); err != nil { 193 return err 194 } 195 } 196 197 if err := s.backend.ContainerKill(name, uint64(sig)); err != nil { 198 var isStopped bool 199 if e, ok := err.(errContainerIsRunning); ok { 200 isStopped = !e.ContainerIsRunning() 201 } 202 203 // Return error that's not caused because the container is stopped. 204 // Return error if the container is not running and the api is >= 1.20 205 // to keep backwards compatibility. 206 version := httputils.VersionFromContext(ctx) 207 if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped { 208 return fmt.Errorf("Cannot kill container %s: %v", name, err) 209 } 210 } 211 212 w.WriteHeader(http.StatusNoContent) 213 return nil 214 } 215 216 func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 217 if err := httputils.ParseForm(r); err != nil { 218 return err 219 } 220 221 timeout, _ := strconv.Atoi(r.Form.Get("t")) 222 223 if err := s.backend.ContainerRestart(vars["name"], timeout); err != nil { 224 return err 225 } 226 227 w.WriteHeader(http.StatusNoContent) 228 229 return nil 230 } 231 232 func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 233 if err := httputils.ParseForm(r); err != nil { 234 return err 235 } 236 237 if err := s.backend.ContainerPause(vars["name"]); err != nil { 238 return err 239 } 240 241 w.WriteHeader(http.StatusNoContent) 242 243 return nil 244 } 245 246 func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 247 if err := httputils.ParseForm(r); err != nil { 248 return err 249 } 250 251 if err := s.backend.ContainerUnpause(vars["name"]); err != nil { 252 return err 253 } 254 255 w.WriteHeader(http.StatusNoContent) 256 257 return nil 258 } 259 260 func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 261 status, err := s.backend.ContainerWait(vars["name"], -1*time.Second) 262 if err != nil { 263 return err 264 } 265 266 return httputils.WriteJSON(w, http.StatusOK, &types.ContainerWaitResponse{ 267 StatusCode: status, 268 }) 269 } 270 271 func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 272 changes, err := s.backend.ContainerChanges(vars["name"]) 273 if err != nil { 274 return err 275 } 276 277 return httputils.WriteJSON(w, http.StatusOK, changes) 278 } 279 280 func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 281 if err := httputils.ParseForm(r); err != nil { 282 return err 283 } 284 285 procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args")) 286 if err != nil { 287 return err 288 } 289 290 return httputils.WriteJSON(w, http.StatusOK, procList) 291 } 292 293 func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 294 if err := httputils.ParseForm(r); err != nil { 295 return err 296 } 297 298 name := vars["name"] 299 newName := r.Form.Get("name") 300 if err := s.backend.ContainerRename(name, newName); err != nil { 301 return err 302 } 303 w.WriteHeader(http.StatusNoContent) 304 return nil 305 } 306 307 func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 308 if err := httputils.ParseForm(r); err != nil { 309 return err 310 } 311 if err := httputils.CheckForJSON(r); err != nil { 312 return err 313 } 314 315 version := httputils.VersionFromContext(ctx) 316 var updateConfig container.UpdateConfig 317 318 decoder := json.NewDecoder(r.Body) 319 if err := decoder.Decode(&updateConfig); err != nil { 320 return err 321 } 322 323 hostConfig := &container.HostConfig{ 324 Resources: updateConfig.Resources, 325 RestartPolicy: updateConfig.RestartPolicy, 326 } 327 328 name := vars["name"] 329 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 330 warnings, err := s.backend.ContainerUpdate(name, hostConfig, validateHostname) 331 if err != nil { 332 return err 333 } 334 335 return httputils.WriteJSON(w, http.StatusOK, &types.ContainerUpdateResponse{ 336 Warnings: warnings, 337 }) 338 } 339 340 func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 341 if err := httputils.ParseForm(r); err != nil { 342 return err 343 } 344 if err := httputils.CheckForJSON(r); err != nil { 345 return err 346 } 347 348 name := r.Form.Get("name") 349 350 config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body) 351 if err != nil { 352 return err 353 } 354 version := httputils.VersionFromContext(ctx) 355 adjustCPUShares := versions.LessThan(version, "1.19") 356 357 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 358 ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{ 359 Name: name, 360 Config: config, 361 HostConfig: hostConfig, 362 NetworkingConfig: networkingConfig, 363 AdjustCPUShares: adjustCPUShares, 364 }, validateHostname) 365 if err != nil { 366 return err 367 } 368 369 return httputils.WriteJSON(w, http.StatusCreated, ccr) 370 } 371 372 func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 373 if err := httputils.ParseForm(r); err != nil { 374 return err 375 } 376 377 name := vars["name"] 378 config := &types.ContainerRmConfig{ 379 ForceRemove: httputils.BoolValue(r, "force"), 380 RemoveVolume: httputils.BoolValue(r, "v"), 381 RemoveLink: httputils.BoolValue(r, "link"), 382 } 383 384 if err := s.backend.ContainerRm(name, config); err != nil { 385 // Force a 404 for the empty string 386 if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { 387 return fmt.Errorf("no such container: \"\"") 388 } 389 return err 390 } 391 392 w.WriteHeader(http.StatusNoContent) 393 394 return nil 395 } 396 397 func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 398 if err := httputils.ParseForm(r); err != nil { 399 return err 400 } 401 402 height, err := strconv.Atoi(r.Form.Get("h")) 403 if err != nil { 404 return err 405 } 406 width, err := strconv.Atoi(r.Form.Get("w")) 407 if err != nil { 408 return err 409 } 410 411 return s.backend.ContainerResize(vars["name"], height, width) 412 } 413 414 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 415 err := httputils.ParseForm(r) 416 if err != nil { 417 return err 418 } 419 containerName := vars["name"] 420 421 _, upgrade := r.Header["Upgrade"] 422 detachKeys := r.FormValue("detachKeys") 423 424 hijacker, ok := w.(http.Hijacker) 425 if !ok { 426 return fmt.Errorf("error attaching to container %s, hijack connection missing", containerName) 427 } 428 429 setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) { 430 conn, _, err := hijacker.Hijack() 431 if err != nil { 432 return nil, nil, nil, err 433 } 434 435 // set raw mode 436 conn.Write([]byte{}) 437 438 if upgrade { 439 fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") 440 } else { 441 fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 442 } 443 444 closer := func() error { 445 httputils.CloseStreams(conn) 446 return nil 447 } 448 return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil 449 } 450 451 attachConfig := &backend.ContainerAttachConfig{ 452 GetStreams: setupStreams, 453 UseStdin: httputils.BoolValue(r, "stdin"), 454 UseStdout: httputils.BoolValue(r, "stdout"), 455 UseStderr: httputils.BoolValue(r, "stderr"), 456 Logs: httputils.BoolValue(r, "logs"), 457 Stream: httputils.BoolValue(r, "stream"), 458 DetachKeys: detachKeys, 459 MuxStreams: true, 460 } 461 462 if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil { 463 logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err) 464 // Remember to close stream if error happens 465 conn, _, errHijack := hijacker.Hijack() 466 if errHijack == nil { 467 statusCode := httputils.GetHTTPErrorStatusCode(err) 468 statusText := http.StatusText(statusCode) 469 fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n%s\r\n", statusCode, statusText, err.Error()) 470 httputils.CloseStreams(conn) 471 } else { 472 logrus.Errorf("Error Hijacking: %v", err) 473 } 474 } 475 return nil 476 } 477 478 func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 479 if err := httputils.ParseForm(r); err != nil { 480 return err 481 } 482 containerName := vars["name"] 483 484 var err error 485 detachKeys := r.FormValue("detachKeys") 486 487 done := make(chan struct{}) 488 started := make(chan struct{}) 489 490 setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) { 491 wsChan := make(chan *websocket.Conn) 492 h := func(conn *websocket.Conn) { 493 wsChan <- conn 494 <-done 495 } 496 497 srv := websocket.Server{Handler: h, Handshake: nil} 498 go func() { 499 close(started) 500 srv.ServeHTTP(w, r) 501 }() 502 503 conn := <-wsChan 504 return conn, conn, conn, nil 505 } 506 507 attachConfig := &backend.ContainerAttachConfig{ 508 GetStreams: setupStreams, 509 Logs: httputils.BoolValue(r, "logs"), 510 Stream: httputils.BoolValue(r, "stream"), 511 DetachKeys: detachKeys, 512 UseStdin: true, 513 UseStdout: true, 514 UseStderr: true, 515 MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr 516 } 517 518 err = s.backend.ContainerAttach(containerName, attachConfig) 519 close(done) 520 select { 521 case <-started: 522 logrus.Errorf("Error attaching websocket: %s", err) 523 return nil 524 default: 525 } 526 return err 527 }