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