github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/api/server/router/container/container_routes.go (about) 1 package container // import "github.com/docker/docker/api/server/router/container" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "strconv" 10 "syscall" 11 12 "github.com/docker/docker/api/server/httputils" 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/api/types/backend" 15 "github.com/docker/docker/api/types/container" 16 "github.com/docker/docker/api/types/filters" 17 "github.com/docker/docker/api/types/versions" 18 containerpkg "github.com/docker/docker/container" 19 "github.com/docker/docker/errdefs" 20 "github.com/docker/docker/pkg/ioutils" 21 "github.com/docker/docker/pkg/signal" 22 "github.com/pkg/errors" 23 "github.com/sirupsen/logrus" 24 "golang.org/x/net/websocket" 25 ) 26 27 func (s *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 28 if err := httputils.ParseForm(r); err != nil { 29 return err 30 } 31 32 if err := httputils.CheckForJSON(r); err != nil { 33 return err 34 } 35 36 // TODO: remove pause arg, and always pause in backend 37 pause := httputils.BoolValue(r, "pause") 38 version := httputils.VersionFromContext(ctx) 39 if r.FormValue("pause") == "" && versions.GreaterThanOrEqualTo(version, "1.13") { 40 pause = true 41 } 42 43 config, _, _, err := s.decoder.DecodeConfig(r.Body) 44 if err != nil && err != io.EOF { //Do not fail if body is empty. 45 return err 46 } 47 48 commitCfg := &backend.CreateImageConfig{ 49 Pause: pause, 50 Repo: r.Form.Get("repo"), 51 Tag: r.Form.Get("tag"), 52 Author: r.Form.Get("author"), 53 Comment: r.Form.Get("comment"), 54 Config: config, 55 Changes: r.Form["changes"], 56 } 57 58 imgID, err := s.backend.CreateImageFromContainer(r.Form.Get("container"), commitCfg) 59 if err != nil { 60 return err 61 } 62 63 return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID}) 64 } 65 66 func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 67 if err := httputils.ParseForm(r); err != nil { 68 return err 69 } 70 filter, err := filters.FromJSON(r.Form.Get("filters")) 71 if err != nil { 72 return err 73 } 74 75 config := &types.ContainerListOptions{ 76 All: httputils.BoolValue(r, "all"), 77 Size: httputils.BoolValue(r, "size"), 78 Since: r.Form.Get("since"), 79 Before: r.Form.Get("before"), 80 Filters: filter, 81 } 82 83 if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { 84 limit, err := strconv.Atoi(tmpLimit) 85 if err != nil { 86 return err 87 } 88 config.Limit = limit 89 } 90 91 containers, err := s.backend.Containers(config) 92 if err != nil { 93 return err 94 } 95 96 return httputils.WriteJSON(w, http.StatusOK, containers) 97 } 98 99 func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 100 if err := httputils.ParseForm(r); err != nil { 101 return err 102 } 103 104 stream := httputils.BoolValueOrDefault(r, "stream", true) 105 if !stream { 106 w.Header().Set("Content-Type", "application/json") 107 } 108 109 config := &backend.ContainerStatsConfig{ 110 Stream: stream, 111 OutStream: w, 112 Version: httputils.VersionFromContext(ctx), 113 } 114 115 return s.backend.ContainerStats(ctx, vars["name"], config) 116 } 117 118 func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 119 if err := httputils.ParseForm(r); err != nil { 120 return err 121 } 122 123 // Args are validated before the stream starts because when it starts we're 124 // sending HTTP 200 by writing an empty chunk of data to tell the client that 125 // daemon is going to stream. By sending this initial HTTP 200 we can't report 126 // any error after the stream starts (i.e. container not found, wrong parameters) 127 // with the appropriate status code. 128 stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr") 129 if !(stdout || stderr) { 130 return errdefs.InvalidParameter(errors.New("Bad parameters: you must choose at least one stream")) 131 } 132 133 containerName := vars["name"] 134 logsConfig := &types.ContainerLogsOptions{ 135 Follow: httputils.BoolValue(r, "follow"), 136 Timestamps: httputils.BoolValue(r, "timestamps"), 137 Since: r.Form.Get("since"), 138 Until: r.Form.Get("until"), 139 Tail: r.Form.Get("tail"), 140 ShowStdout: stdout, 141 ShowStderr: stderr, 142 Details: httputils.BoolValue(r, "details"), 143 } 144 145 msgs, tty, err := s.backend.ContainerLogs(ctx, containerName, logsConfig) 146 if err != nil { 147 return err 148 } 149 150 // if has a tty, we're not muxing streams. if it doesn't, we are. simple. 151 // this is the point of no return for writing a response. once we call 152 // WriteLogStream, the response has been started and errors will be 153 // returned in band by WriteLogStream 154 httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty) 155 return nil 156 } 157 158 func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 159 return s.backend.ContainerExport(vars["name"], w) 160 } 161 162 type bodyOnStartError struct{} 163 164 func (bodyOnStartError) Error() string { 165 return "starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24" 166 } 167 168 func (bodyOnStartError) InvalidParameter() {} 169 170 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 171 // If contentLength is -1, we can assumed chunked encoding 172 // or more technically that the length is unknown 173 // https://golang.org/src/pkg/net/http/request.go#L139 174 // net/http otherwise seems to swallow any headers related to chunked encoding 175 // including r.TransferEncoding 176 // allow a nil body for backwards compatibility 177 178 version := httputils.VersionFromContext(ctx) 179 var hostConfig *container.HostConfig 180 // A non-nil json object is at least 7 characters. 181 if r.ContentLength > 7 || r.ContentLength == -1 { 182 if versions.GreaterThanOrEqualTo(version, "1.24") { 183 return bodyOnStartError{} 184 } 185 186 if err := httputils.CheckForJSON(r); err != nil { 187 return err 188 } 189 190 c, err := s.decoder.DecodeHostConfig(r.Body) 191 if err != nil { 192 return err 193 } 194 hostConfig = c 195 } 196 197 if err := httputils.ParseForm(r); err != nil { 198 return err 199 } 200 201 checkpoint := r.Form.Get("checkpoint") 202 checkpointDir := r.Form.Get("checkpoint-dir") 203 if err := s.backend.ContainerStart(vars["name"], hostConfig, checkpoint, checkpointDir); err != nil { 204 return err 205 } 206 207 w.WriteHeader(http.StatusNoContent) 208 return nil 209 } 210 211 func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 212 if err := httputils.ParseForm(r); err != nil { 213 return err 214 } 215 216 var seconds *int 217 if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" { 218 valSeconds, err := strconv.Atoi(tmpSeconds) 219 if err != nil { 220 return err 221 } 222 seconds = &valSeconds 223 } 224 225 if err := s.backend.ContainerStop(vars["name"], seconds); err != nil { 226 return err 227 } 228 w.WriteHeader(http.StatusNoContent) 229 230 return nil 231 } 232 233 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 234 if err := httputils.ParseForm(r); err != nil { 235 return err 236 } 237 238 var sig syscall.Signal 239 name := vars["name"] 240 241 // If we have a signal, look at it. Otherwise, do nothing 242 if sigStr := r.Form.Get("signal"); sigStr != "" { 243 var err error 244 if sig, err = signal.ParseSignal(sigStr); err != nil { 245 return errdefs.InvalidParameter(err) 246 } 247 } 248 249 if err := s.backend.ContainerKill(name, uint64(sig)); err != nil { 250 var isStopped bool 251 if errdefs.IsConflict(err) { 252 isStopped = true 253 } 254 255 // Return error that's not caused because the container is stopped. 256 // Return error if the container is not running and the api is >= 1.20 257 // to keep backwards compatibility. 258 version := httputils.VersionFromContext(ctx) 259 if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped { 260 return errors.Wrapf(err, "Cannot kill container: %s", name) 261 } 262 } 263 264 w.WriteHeader(http.StatusNoContent) 265 return nil 266 } 267 268 func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 269 if err := httputils.ParseForm(r); err != nil { 270 return err 271 } 272 273 var seconds *int 274 if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" { 275 valSeconds, err := strconv.Atoi(tmpSeconds) 276 if err != nil { 277 return err 278 } 279 seconds = &valSeconds 280 } 281 282 if err := s.backend.ContainerRestart(vars["name"], seconds); err != nil { 283 return err 284 } 285 286 w.WriteHeader(http.StatusNoContent) 287 288 return nil 289 } 290 291 func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 292 if err := httputils.ParseForm(r); err != nil { 293 return err 294 } 295 296 if err := s.backend.ContainerPause(vars["name"]); err != nil { 297 return err 298 } 299 300 w.WriteHeader(http.StatusNoContent) 301 302 return nil 303 } 304 305 func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 306 if err := httputils.ParseForm(r); err != nil { 307 return err 308 } 309 310 if err := s.backend.ContainerUnpause(vars["name"]); err != nil { 311 return err 312 } 313 314 w.WriteHeader(http.StatusNoContent) 315 316 return nil 317 } 318 319 func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 320 // Behavior changed in version 1.30 to handle wait condition and to 321 // return headers immediately. 322 version := httputils.VersionFromContext(ctx) 323 legacyBehaviorPre130 := versions.LessThan(version, "1.30") 324 legacyRemovalWaitPre134 := false 325 326 // The wait condition defaults to "not-running". 327 waitCondition := containerpkg.WaitConditionNotRunning 328 if !legacyBehaviorPre130 { 329 if err := httputils.ParseForm(r); err != nil { 330 return err 331 } 332 switch container.WaitCondition(r.Form.Get("condition")) { 333 case container.WaitConditionNextExit: 334 waitCondition = containerpkg.WaitConditionNextExit 335 case container.WaitConditionRemoved: 336 waitCondition = containerpkg.WaitConditionRemoved 337 legacyRemovalWaitPre134 = versions.LessThan(version, "1.34") 338 } 339 } 340 341 waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition) 342 if err != nil { 343 return err 344 } 345 346 w.Header().Set("Content-Type", "application/json") 347 348 if !legacyBehaviorPre130 { 349 // Write response header immediately. 350 w.WriteHeader(http.StatusOK) 351 if flusher, ok := w.(http.Flusher); ok { 352 flusher.Flush() 353 } 354 } 355 356 // Block on the result of the wait operation. 357 status := <-waitC 358 359 // With API < 1.34, wait on WaitConditionRemoved did not return 360 // in case container removal failed. The only way to report an 361 // error back to the client is to not write anything (i.e. send 362 // an empty response which will be treated as an error). 363 if legacyRemovalWaitPre134 && status.Err() != nil { 364 return nil 365 } 366 367 var waitError *container.ContainerWaitOKBodyError 368 if status.Err() != nil { 369 waitError = &container.ContainerWaitOKBodyError{Message: status.Err().Error()} 370 } 371 372 return json.NewEncoder(w).Encode(&container.ContainerWaitOKBody{ 373 StatusCode: int64(status.ExitCode()), 374 Error: waitError, 375 }) 376 } 377 378 func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 379 changes, err := s.backend.ContainerChanges(vars["name"]) 380 if err != nil { 381 return err 382 } 383 384 return httputils.WriteJSON(w, http.StatusOK, changes) 385 } 386 387 func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 388 if err := httputils.ParseForm(r); err != nil { 389 return err 390 } 391 392 procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args")) 393 if err != nil { 394 return err 395 } 396 397 return httputils.WriteJSON(w, http.StatusOK, procList) 398 } 399 400 func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 401 if err := httputils.ParseForm(r); err != nil { 402 return err 403 } 404 405 name := vars["name"] 406 newName := r.Form.Get("name") 407 if err := s.backend.ContainerRename(name, newName); err != nil { 408 return err 409 } 410 w.WriteHeader(http.StatusNoContent) 411 return nil 412 } 413 414 func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 415 if err := httputils.ParseForm(r); err != nil { 416 return err 417 } 418 if err := httputils.CheckForJSON(r); err != nil { 419 return err 420 } 421 422 var updateConfig container.UpdateConfig 423 424 decoder := json.NewDecoder(r.Body) 425 if err := decoder.Decode(&updateConfig); err != nil { 426 return err 427 } 428 if versions.LessThan(httputils.VersionFromContext(ctx), "1.40") { 429 updateConfig.PidsLimit = nil 430 } 431 if updateConfig.PidsLimit != nil && *updateConfig.PidsLimit <= 0 { 432 // Both `0` and `-1` are accepted to set "unlimited" when updating. 433 // Historically, any negative value was accepted, so treat them as 434 // "unlimited" as well. 435 var unlimited int64 436 updateConfig.PidsLimit = &unlimited 437 } 438 439 hostConfig := &container.HostConfig{ 440 Resources: updateConfig.Resources, 441 RestartPolicy: updateConfig.RestartPolicy, 442 } 443 444 name := vars["name"] 445 resp, err := s.backend.ContainerUpdate(name, hostConfig) 446 if err != nil { 447 return err 448 } 449 450 return httputils.WriteJSON(w, http.StatusOK, resp) 451 } 452 453 func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 454 if err := httputils.ParseForm(r); err != nil { 455 return err 456 } 457 if err := httputils.CheckForJSON(r); err != nil { 458 return err 459 } 460 461 name := r.Form.Get("name") 462 463 config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body) 464 if err != nil { 465 return err 466 } 467 version := httputils.VersionFromContext(ctx) 468 adjustCPUShares := versions.LessThan(version, "1.19") 469 470 // When using API 1.24 and under, the client is responsible for removing the container 471 if hostConfig != nil && versions.LessThan(version, "1.25") { 472 hostConfig.AutoRemove = false 473 } 474 475 if hostConfig != nil && versions.LessThan(version, "1.40") { 476 // Ignore BindOptions.NonRecursive because it was added in API 1.40. 477 for _, m := range hostConfig.Mounts { 478 if bo := m.BindOptions; bo != nil { 479 bo.NonRecursive = false 480 } 481 } 482 // Ignore KernelMemoryTCP because it was added in API 1.40. 483 hostConfig.KernelMemoryTCP = 0 484 485 // Ignore Capabilities because it was added in API 1.40. 486 hostConfig.Capabilities = nil 487 488 // Older clients (API < 1.40) expects the default to be shareable, make them happy 489 if hostConfig.IpcMode.IsEmpty() { 490 hostConfig.IpcMode = container.IpcMode("shareable") 491 } 492 } 493 494 if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 { 495 // Don't set a limit if either no limit was specified, or "unlimited" was 496 // explicitly set. 497 // Both `0` and `-1` are accepted as "unlimited", and historically any 498 // negative value was accepted, so treat those as "unlimited" as well. 499 hostConfig.PidsLimit = nil 500 } 501 502 ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{ 503 Name: name, 504 Config: config, 505 HostConfig: hostConfig, 506 NetworkingConfig: networkingConfig, 507 AdjustCPUShares: adjustCPUShares, 508 }) 509 if err != nil { 510 return err 511 } 512 513 return httputils.WriteJSON(w, http.StatusCreated, ccr) 514 } 515 516 func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 517 if err := httputils.ParseForm(r); err != nil { 518 return err 519 } 520 521 name := vars["name"] 522 config := &types.ContainerRmConfig{ 523 ForceRemove: httputils.BoolValue(r, "force"), 524 RemoveVolume: httputils.BoolValue(r, "v"), 525 RemoveLink: httputils.BoolValue(r, "link"), 526 } 527 528 if err := s.backend.ContainerRm(name, config); err != nil { 529 return err 530 } 531 532 w.WriteHeader(http.StatusNoContent) 533 534 return nil 535 } 536 537 func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 538 if err := httputils.ParseForm(r); err != nil { 539 return err 540 } 541 542 height, err := strconv.Atoi(r.Form.Get("h")) 543 if err != nil { 544 return errdefs.InvalidParameter(err) 545 } 546 width, err := strconv.Atoi(r.Form.Get("w")) 547 if err != nil { 548 return errdefs.InvalidParameter(err) 549 } 550 551 return s.backend.ContainerResize(vars["name"], height, width) 552 } 553 554 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 555 err := httputils.ParseForm(r) 556 if err != nil { 557 return err 558 } 559 containerName := vars["name"] 560 561 _, upgrade := r.Header["Upgrade"] 562 detachKeys := r.FormValue("detachKeys") 563 564 hijacker, ok := w.(http.Hijacker) 565 if !ok { 566 return errdefs.InvalidParameter(errors.Errorf("error attaching to container %s, hijack connection missing", containerName)) 567 } 568 569 setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) { 570 conn, _, err := hijacker.Hijack() 571 if err != nil { 572 return nil, nil, nil, err 573 } 574 575 // set raw mode 576 conn.Write([]byte{}) 577 578 if upgrade { 579 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") 580 } else { 581 fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 582 } 583 584 closer := func() error { 585 httputils.CloseStreams(conn) 586 return nil 587 } 588 return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil 589 } 590 591 attachConfig := &backend.ContainerAttachConfig{ 592 GetStreams: setupStreams, 593 UseStdin: httputils.BoolValue(r, "stdin"), 594 UseStdout: httputils.BoolValue(r, "stdout"), 595 UseStderr: httputils.BoolValue(r, "stderr"), 596 Logs: httputils.BoolValue(r, "logs"), 597 Stream: httputils.BoolValue(r, "stream"), 598 DetachKeys: detachKeys, 599 MuxStreams: true, 600 } 601 602 if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil { 603 logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err) 604 // Remember to close stream if error happens 605 conn, _, errHijack := hijacker.Hijack() 606 if errHijack == nil { 607 statusCode := errdefs.GetHTTPErrorStatusCode(err) 608 statusText := http.StatusText(statusCode) 609 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()) 610 httputils.CloseStreams(conn) 611 } else { 612 logrus.Errorf("Error Hijacking: %v", err) 613 } 614 } 615 return nil 616 } 617 618 func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 619 if err := httputils.ParseForm(r); err != nil { 620 return err 621 } 622 containerName := vars["name"] 623 624 var err error 625 detachKeys := r.FormValue("detachKeys") 626 627 done := make(chan struct{}) 628 started := make(chan struct{}) 629 630 version := httputils.VersionFromContext(ctx) 631 632 setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) { 633 wsChan := make(chan *websocket.Conn) 634 h := func(conn *websocket.Conn) { 635 wsChan <- conn 636 <-done 637 } 638 639 srv := websocket.Server{Handler: h, Handshake: nil} 640 go func() { 641 close(started) 642 srv.ServeHTTP(w, r) 643 }() 644 645 conn := <-wsChan 646 // In case version 1.28 and above, a binary frame will be sent. 647 // See 28176 for details. 648 if versions.GreaterThanOrEqualTo(version, "1.28") { 649 conn.PayloadType = websocket.BinaryFrame 650 } 651 return conn, conn, conn, nil 652 } 653 654 attachConfig := &backend.ContainerAttachConfig{ 655 GetStreams: setupStreams, 656 Logs: httputils.BoolValue(r, "logs"), 657 Stream: httputils.BoolValue(r, "stream"), 658 DetachKeys: detachKeys, 659 UseStdin: true, 660 UseStdout: true, 661 UseStderr: true, 662 MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr 663 } 664 665 err = s.backend.ContainerAttach(containerName, attachConfig) 666 close(done) 667 select { 668 case <-started: 669 if err != nil { 670 logrus.Errorf("Error attaching websocket: %s", err) 671 } else { 672 logrus.Debug("websocket connection was closed by client") 673 } 674 return nil 675 default: 676 } 677 return err 678 } 679 680 func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 681 if err := httputils.ParseForm(r); err != nil { 682 return err 683 } 684 685 pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) 686 if err != nil { 687 return errdefs.InvalidParameter(err) 688 } 689 690 pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters) 691 if err != nil { 692 return err 693 } 694 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 695 }