github.com/nullne/docker@v1.13.0-rc1/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 "golang.org/x/net/context" 22 "golang.org/x/net/websocket" 23 ) 24 25 func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 26 if err := httputils.ParseForm(r); err != nil { 27 return err 28 } 29 filter, err := filters.FromParam(r.Form.Get("filters")) 30 if err != nil { 31 return err 32 } 33 34 config := &types.ContainerListOptions{ 35 All: httputils.BoolValue(r, "all"), 36 Size: httputils.BoolValue(r, "size"), 37 Since: r.Form.Get("since"), 38 Before: r.Form.Get("before"), 39 Filters: filter, 40 } 41 42 if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { 43 limit, err := strconv.Atoi(tmpLimit) 44 if err != nil { 45 return err 46 } 47 config.Limit = limit 48 } 49 50 containers, err := s.backend.Containers(config) 51 if err != nil { 52 return err 53 } 54 55 return httputils.WriteJSON(w, http.StatusOK, containers) 56 } 57 58 func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 59 if err := httputils.ParseForm(r); err != nil { 60 return err 61 } 62 63 stream := httputils.BoolValueOrDefault(r, "stream", true) 64 if !stream { 65 w.Header().Set("Content-Type", "application/json") 66 } 67 68 config := &backend.ContainerStatsConfig{ 69 Stream: stream, 70 OutStream: w, 71 Version: string(httputils.VersionFromContext(ctx)), 72 } 73 74 return s.backend.ContainerStats(ctx, vars["name"], config) 75 } 76 77 func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 78 if err := httputils.ParseForm(r); err != nil { 79 return err 80 } 81 82 // Args are validated before the stream starts because when it starts we're 83 // sending HTTP 200 by writing an empty chunk of data to tell the client that 84 // daemon is going to stream. By sending this initial HTTP 200 we can't report 85 // any error after the stream starts (i.e. container not found, wrong parameters) 86 // with the appropriate status code. 87 stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr") 88 if !(stdout || stderr) { 89 return fmt.Errorf("Bad parameters: you must choose at least one stream") 90 } 91 92 containerName := vars["name"] 93 logsConfig := &backend.ContainerLogsConfig{ 94 ContainerLogsOptions: 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 OutStream: w, 104 } 105 106 chStarted := make(chan struct{}) 107 if err := s.backend.ContainerLogs(ctx, containerName, logsConfig, chStarted); err != nil { 108 select { 109 case <-chStarted: 110 // The client may be expecting all of the data we're sending to 111 // be multiplexed, so send it through OutStream, which will 112 // have been set up to handle that if needed. 113 fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %v\n", err) 114 default: 115 return err 116 } 117 } 118 119 return nil 120 } 121 122 func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 123 return s.backend.ContainerExport(vars["name"], w) 124 } 125 126 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 127 // If contentLength is -1, we can assumed chunked encoding 128 // or more technically that the length is unknown 129 // https://golang.org/src/pkg/net/http/request.go#L139 130 // net/http otherwise seems to swallow any headers related to chunked encoding 131 // including r.TransferEncoding 132 // allow a nil body for backwards compatibility 133 134 version := httputils.VersionFromContext(ctx) 135 var hostConfig *container.HostConfig 136 // A non-nil json object is at least 7 characters. 137 if r.ContentLength > 7 || r.ContentLength == -1 { 138 if versions.GreaterThanOrEqualTo(version, "1.24") { 139 return validationError{fmt.Errorf("starting container with non-empty request body was deprecated since v1.10 and removed in v1.12")} 140 } 141 142 if err := httputils.CheckForJSON(r); err != nil { 143 return err 144 } 145 146 c, err := s.decoder.DecodeHostConfig(r.Body) 147 if err != nil { 148 return err 149 } 150 hostConfig = c 151 } 152 153 if err := httputils.ParseForm(r); err != nil { 154 return err 155 } 156 157 checkpoint := r.Form.Get("checkpoint") 158 checkpointDir := r.Form.Get("checkpoint-dir") 159 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 160 if err := s.backend.ContainerStart(vars["name"], hostConfig, validateHostname, checkpoint, checkpointDir); err != nil { 161 return err 162 } 163 164 w.WriteHeader(http.StatusNoContent) 165 return nil 166 } 167 168 func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 169 if err := httputils.ParseForm(r); err != nil { 170 return err 171 } 172 173 var seconds *int 174 if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" { 175 valSeconds, err := strconv.Atoi(tmpSeconds) 176 if err != nil { 177 return err 178 } 179 seconds = &valSeconds 180 } 181 182 if err := s.backend.ContainerStop(vars["name"], seconds); err != nil { 183 return err 184 } 185 w.WriteHeader(http.StatusNoContent) 186 187 return nil 188 } 189 190 type errContainerIsRunning interface { 191 ContainerIsRunning() bool 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 err 207 } 208 } 209 210 if err := s.backend.ContainerKill(name, uint64(sig)); err != nil { 211 var isStopped bool 212 if e, ok := err.(errContainerIsRunning); ok { 213 isStopped = !e.ContainerIsRunning() 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 fmt.Errorf("Cannot kill container %s: %v", name, err) 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 status, err := s.backend.ContainerWait(vars["name"], -1*time.Second) 282 if err != nil { 283 return err 284 } 285 286 return httputils.WriteJSON(w, http.StatusOK, &container.ContainerWaitOKBody{ 287 StatusCode: int64(status), 288 }) 289 } 290 291 func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 292 changes, err := s.backend.ContainerChanges(vars["name"]) 293 if err != nil { 294 return err 295 } 296 297 return httputils.WriteJSON(w, http.StatusOK, changes) 298 } 299 300 func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 301 if err := httputils.ParseForm(r); err != nil { 302 return err 303 } 304 305 procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args")) 306 if err != nil { 307 return err 308 } 309 310 return httputils.WriteJSON(w, http.StatusOK, procList) 311 } 312 313 func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 314 if err := httputils.ParseForm(r); err != nil { 315 return err 316 } 317 318 name := vars["name"] 319 newName := r.Form.Get("name") 320 if err := s.backend.ContainerRename(name, newName); err != nil { 321 return err 322 } 323 w.WriteHeader(http.StatusNoContent) 324 return nil 325 } 326 327 func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 328 if err := httputils.ParseForm(r); err != nil { 329 return err 330 } 331 if err := httputils.CheckForJSON(r); err != nil { 332 return err 333 } 334 335 version := httputils.VersionFromContext(ctx) 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 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 350 resp, err := s.backend.ContainerUpdate(name, hostConfig, validateHostname) 351 if err != nil { 352 return err 353 } 354 355 return httputils.WriteJSON(w, http.StatusOK, resp) 356 } 357 358 func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 359 if err := httputils.ParseForm(r); err != nil { 360 return err 361 } 362 if err := httputils.CheckForJSON(r); err != nil { 363 return err 364 } 365 366 name := r.Form.Get("name") 367 368 config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body) 369 if err != nil { 370 return err 371 } 372 version := httputils.VersionFromContext(ctx) 373 adjustCPUShares := versions.LessThan(version, "1.19") 374 375 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 376 ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{ 377 Name: name, 378 Config: config, 379 HostConfig: hostConfig, 380 NetworkingConfig: networkingConfig, 381 AdjustCPUShares: adjustCPUShares, 382 }, validateHostname) 383 if err != nil { 384 return err 385 } 386 387 return httputils.WriteJSON(w, http.StatusCreated, ccr) 388 } 389 390 func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 391 if err := httputils.ParseForm(r); err != nil { 392 return err 393 } 394 395 name := vars["name"] 396 config := &types.ContainerRmConfig{ 397 ForceRemove: httputils.BoolValue(r, "force"), 398 RemoveVolume: httputils.BoolValue(r, "v"), 399 RemoveLink: httputils.BoolValue(r, "link"), 400 } 401 402 if err := s.backend.ContainerRm(name, config); err != nil { 403 return err 404 } 405 406 w.WriteHeader(http.StatusNoContent) 407 408 return nil 409 } 410 411 func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 412 if err := httputils.ParseForm(r); err != nil { 413 return err 414 } 415 416 height, err := strconv.Atoi(r.Form.Get("h")) 417 if err != nil { 418 return err 419 } 420 width, err := strconv.Atoi(r.Form.Get("w")) 421 if err != nil { 422 return err 423 } 424 425 return s.backend.ContainerResize(vars["name"], height, width) 426 } 427 428 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 429 err := httputils.ParseForm(r) 430 if err != nil { 431 return err 432 } 433 containerName := vars["name"] 434 435 _, upgrade := r.Header["Upgrade"] 436 detachKeys := r.FormValue("detachKeys") 437 438 hijacker, ok := w.(http.Hijacker) 439 if !ok { 440 return fmt.Errorf("error attaching to container %s, hijack connection missing", containerName) 441 } 442 443 setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) { 444 conn, _, err := hijacker.Hijack() 445 if err != nil { 446 return nil, nil, nil, err 447 } 448 449 // set raw mode 450 conn.Write([]byte{}) 451 452 if upgrade { 453 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") 454 } else { 455 fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 456 } 457 458 closer := func() error { 459 httputils.CloseStreams(conn) 460 return nil 461 } 462 return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil 463 } 464 465 attachConfig := &backend.ContainerAttachConfig{ 466 GetStreams: setupStreams, 467 UseStdin: httputils.BoolValue(r, "stdin"), 468 UseStdout: httputils.BoolValue(r, "stdout"), 469 UseStderr: httputils.BoolValue(r, "stderr"), 470 Logs: httputils.BoolValue(r, "logs"), 471 Stream: httputils.BoolValue(r, "stream"), 472 DetachKeys: detachKeys, 473 MuxStreams: true, 474 } 475 476 if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil { 477 logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err) 478 // Remember to close stream if error happens 479 conn, _, errHijack := hijacker.Hijack() 480 if errHijack == nil { 481 statusCode := httputils.GetHTTPErrorStatusCode(err) 482 statusText := http.StatusText(statusCode) 483 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()) 484 httputils.CloseStreams(conn) 485 } else { 486 logrus.Errorf("Error Hijacking: %v", err) 487 } 488 } 489 return nil 490 } 491 492 func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 493 if err := httputils.ParseForm(r); err != nil { 494 return err 495 } 496 containerName := vars["name"] 497 498 var err error 499 detachKeys := r.FormValue("detachKeys") 500 501 done := make(chan struct{}) 502 started := make(chan struct{}) 503 504 setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) { 505 wsChan := make(chan *websocket.Conn) 506 h := func(conn *websocket.Conn) { 507 wsChan <- conn 508 <-done 509 } 510 511 srv := websocket.Server{Handler: h, Handshake: nil} 512 go func() { 513 close(started) 514 srv.ServeHTTP(w, r) 515 }() 516 517 conn := <-wsChan 518 return conn, conn, conn, nil 519 } 520 521 attachConfig := &backend.ContainerAttachConfig{ 522 GetStreams: setupStreams, 523 Logs: httputils.BoolValue(r, "logs"), 524 Stream: httputils.BoolValue(r, "stream"), 525 DetachKeys: detachKeys, 526 UseStdin: true, 527 UseStdout: true, 528 UseStderr: true, 529 MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr 530 } 531 532 err = s.backend.ContainerAttach(containerName, attachConfig) 533 close(done) 534 select { 535 case <-started: 536 logrus.Errorf("Error attaching websocket: %s", err) 537 return nil 538 default: 539 } 540 return err 541 } 542 543 func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 544 if err := httputils.ParseForm(r); err != nil { 545 return err 546 } 547 548 if err := httputils.CheckForJSON(r); err != nil { 549 return err 550 } 551 552 var cfg types.ContainersPruneConfig 553 if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil { 554 return err 555 } 556 557 pruneReport, err := s.backend.ContainersPrune(&cfg) 558 if err != nil { 559 return err 560 } 561 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 562 }