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