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