github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/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 "strings" 10 "syscall" 11 "time" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/docker/api/server/httputils" 15 "github.com/docker/docker/api/types/backend" 16 "github.com/docker/docker/pkg/ioutils" 17 "github.com/docker/docker/pkg/signal" 18 "github.com/docker/docker/pkg/term" 19 "github.com/docker/docker/runconfig" 20 "github.com/docker/engine-api/types" 21 "github.com/docker/engine-api/types/container" 22 "github.com/docker/engine-api/types/filters" 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.FromParam(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 Filter: 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 var closeNotifier <-chan bool 71 if notifier, ok := w.(http.CloseNotifier); ok { 72 closeNotifier = notifier.CloseNotify() 73 } 74 75 config := &backend.ContainerStatsConfig{ 76 Stream: stream, 77 OutStream: w, 78 Stop: closeNotifier, 79 Version: string(httputils.VersionFromContext(ctx)), 80 } 81 82 return s.backend.ContainerStats(vars["name"], config) 83 } 84 85 func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 86 if err := httputils.ParseForm(r); err != nil { 87 return err 88 } 89 90 // Args are validated before the stream starts because when it starts we're 91 // sending HTTP 200 by writing an empty chunk of data to tell the client that 92 // daemon is going to stream. By sending this initial HTTP 200 we can't report 93 // any error after the stream starts (i.e. container not found, wrong parameters) 94 // with the appropriate status code. 95 stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr") 96 if !(stdout || stderr) { 97 return fmt.Errorf("Bad parameters: you must choose at least one stream") 98 } 99 100 var closeNotifier <-chan bool 101 if notifier, ok := w.(http.CloseNotifier); ok { 102 closeNotifier = notifier.CloseNotify() 103 } 104 105 containerName := vars["name"] 106 logsConfig := &backend.ContainerLogsConfig{ 107 ContainerLogsOptions: types.ContainerLogsOptions{ 108 Follow: httputils.BoolValue(r, "follow"), 109 Timestamps: httputils.BoolValue(r, "timestamps"), 110 Since: r.Form.Get("since"), 111 Tail: r.Form.Get("tail"), 112 ShowStdout: stdout, 113 ShowStderr: stderr, 114 }, 115 OutStream: w, 116 Stop: closeNotifier, 117 } 118 119 chStarted := make(chan struct{}) 120 if err := s.backend.ContainerLogs(containerName, logsConfig, chStarted); err != nil { 121 select { 122 case <-chStarted: 123 // The client may be expecting all of the data we're sending to 124 // be multiplexed, so send it through OutStream, which will 125 // have been set up to handle that if needed. 126 fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %v\n", err) 127 default: 128 return err 129 } 130 } 131 132 return nil 133 } 134 135 func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 136 return s.backend.ContainerExport(vars["name"], w) 137 } 138 139 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 140 // If contentLength is -1, we can assumed chunked encoding 141 // or more technically that the length is unknown 142 // https://golang.org/src/pkg/net/http/request.go#L139 143 // net/http otherwise seems to swallow any headers related to chunked encoding 144 // including r.TransferEncoding 145 // allow a nil body for backwards compatibility 146 var hostConfig *container.HostConfig 147 if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { 148 if err := httputils.CheckForJSON(r); err != nil { 149 return err 150 } 151 152 c, err := runconfig.DecodeHostConfig(r.Body) 153 if err != nil { 154 return err 155 } 156 157 hostConfig = c 158 } 159 160 if err := s.backend.ContainerStart(vars["name"], hostConfig); err != nil { 161 return err 162 } 163 w.WriteHeader(http.StatusNoContent) 164 return nil 165 } 166 167 func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 168 if err := httputils.ParseForm(r); err != nil { 169 return err 170 } 171 172 seconds, _ := strconv.Atoi(r.Form.Get("t")) 173 174 if err := s.backend.ContainerStop(vars["name"], seconds); err != nil { 175 return err 176 } 177 w.WriteHeader(http.StatusNoContent) 178 179 return nil 180 } 181 182 type errContainerIsRunning interface { 183 ContainerIsRunning() bool 184 } 185 186 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 187 if err := httputils.ParseForm(r); err != nil { 188 return err 189 } 190 191 var sig syscall.Signal 192 name := vars["name"] 193 194 // If we have a signal, look at it. Otherwise, do nothing 195 if sigStr := r.Form.Get("signal"); sigStr != "" { 196 var err error 197 if sig, err = signal.ParseSignal(sigStr); err != nil { 198 return err 199 } 200 } 201 202 if err := s.backend.ContainerKill(name, uint64(sig)); err != nil { 203 var isStopped bool 204 if e, ok := err.(errContainerIsRunning); ok { 205 isStopped = !e.ContainerIsRunning() 206 } 207 208 // Return error that's not caused because the container is stopped. 209 // Return error if the container is not running and the api is >= 1.20 210 // to keep backwards compatibility. 211 version := httputils.VersionFromContext(ctx) 212 if version.GreaterThanOrEqualTo("1.20") || !isStopped { 213 return fmt.Errorf("Cannot kill container %s: %v", name, err) 214 } 215 } 216 217 w.WriteHeader(http.StatusNoContent) 218 return nil 219 } 220 221 func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 222 if err := httputils.ParseForm(r); err != nil { 223 return err 224 } 225 226 timeout, _ := strconv.Atoi(r.Form.Get("t")) 227 228 if err := s.backend.ContainerRestart(vars["name"], timeout); err != nil { 229 return err 230 } 231 232 w.WriteHeader(http.StatusNoContent) 233 234 return nil 235 } 236 237 func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 238 if err := httputils.ParseForm(r); err != nil { 239 return err 240 } 241 242 if err := s.backend.ContainerPause(vars["name"]); err != nil { 243 return err 244 } 245 246 w.WriteHeader(http.StatusNoContent) 247 248 return nil 249 } 250 251 func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 252 if err := httputils.ParseForm(r); err != nil { 253 return err 254 } 255 256 if err := s.backend.ContainerUnpause(vars["name"]); err != nil { 257 return err 258 } 259 260 w.WriteHeader(http.StatusNoContent) 261 262 return nil 263 } 264 265 func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 266 status, err := s.backend.ContainerWait(vars["name"], -1*time.Second) 267 if err != nil { 268 return err 269 } 270 271 return httputils.WriteJSON(w, http.StatusOK, &types.ContainerWaitResponse{ 272 StatusCode: status, 273 }) 274 } 275 276 func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 277 changes, err := s.backend.ContainerChanges(vars["name"]) 278 if err != nil { 279 return err 280 } 281 282 return httputils.WriteJSON(w, http.StatusOK, changes) 283 } 284 285 func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 286 if err := httputils.ParseForm(r); err != nil { 287 return err 288 } 289 290 procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args")) 291 if err != nil { 292 return err 293 } 294 295 return httputils.WriteJSON(w, http.StatusOK, procList) 296 } 297 298 func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 299 if err := httputils.ParseForm(r); err != nil { 300 return err 301 } 302 303 name := vars["name"] 304 newName := r.Form.Get("name") 305 if err := s.backend.ContainerRename(name, newName); err != nil { 306 return err 307 } 308 w.WriteHeader(http.StatusNoContent) 309 return nil 310 } 311 312 func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 313 if err := httputils.ParseForm(r); err != nil { 314 return err 315 } 316 if err := httputils.CheckForJSON(r); err != nil { 317 return err 318 } 319 320 var updateConfig container.UpdateConfig 321 322 decoder := json.NewDecoder(r.Body) 323 if err := decoder.Decode(&updateConfig); err != nil { 324 return err 325 } 326 327 hostConfig := &container.HostConfig{ 328 Resources: updateConfig.Resources, 329 RestartPolicy: updateConfig.RestartPolicy, 330 } 331 332 name := vars["name"] 333 warnings, err := s.backend.ContainerUpdate(name, hostConfig) 334 if err != nil { 335 return err 336 } 337 338 return httputils.WriteJSON(w, http.StatusOK, &types.ContainerUpdateResponse{ 339 Warnings: warnings, 340 }) 341 } 342 343 func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 344 if err := httputils.ParseForm(r); err != nil { 345 return err 346 } 347 if err := httputils.CheckForJSON(r); err != nil { 348 return err 349 } 350 351 name := r.Form.Get("name") 352 353 config, hostConfig, networkingConfig, err := runconfig.DecodeContainerConfig(r.Body) 354 if err != nil { 355 return err 356 } 357 version := httputils.VersionFromContext(ctx) 358 adjustCPUShares := version.LessThan("1.19") 359 360 ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{ 361 Name: name, 362 Config: config, 363 HostConfig: hostConfig, 364 NetworkingConfig: networkingConfig, 365 AdjustCPUShares: adjustCPUShares, 366 }) 367 if err != nil { 368 return err 369 } 370 371 return httputils.WriteJSON(w, http.StatusCreated, ccr) 372 } 373 374 func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 375 if err := httputils.ParseForm(r); err != nil { 376 return err 377 } 378 379 name := vars["name"] 380 config := &types.ContainerRmConfig{ 381 ForceRemove: httputils.BoolValue(r, "force"), 382 RemoveVolume: httputils.BoolValue(r, "v"), 383 RemoveLink: httputils.BoolValue(r, "link"), 384 } 385 386 if err := s.backend.ContainerRm(name, config); err != nil { 387 // Force a 404 for the empty string 388 if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { 389 return fmt.Errorf("no such container: \"\"") 390 } 391 return err 392 } 393 394 w.WriteHeader(http.StatusNoContent) 395 396 return nil 397 } 398 399 func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 400 if err := httputils.ParseForm(r); err != nil { 401 return err 402 } 403 404 height, err := strconv.Atoi(r.Form.Get("h")) 405 if err != nil { 406 return err 407 } 408 width, err := strconv.Atoi(r.Form.Get("w")) 409 if err != nil { 410 return err 411 } 412 413 return s.backend.ContainerResize(vars["name"], height, width) 414 } 415 416 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 417 err := httputils.ParseForm(r) 418 if err != nil { 419 return err 420 } 421 containerName := vars["name"] 422 423 _, upgrade := r.Header["Upgrade"] 424 425 keys := []byte{} 426 detachKeys := r.FormValue("detachKeys") 427 if detachKeys != "" { 428 keys, err = term.ToBytes(detachKeys) 429 if err != nil { 430 logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys) 431 } 432 } 433 434 hijacker, ok := w.(http.Hijacker) 435 if !ok { 436 return fmt.Errorf("error attaching to container %s, hijack connection missing", containerName) 437 } 438 439 setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) { 440 conn, _, err := hijacker.Hijack() 441 if err != nil { 442 return nil, nil, nil, err 443 } 444 445 // set raw mode 446 conn.Write([]byte{}) 447 448 if upgrade { 449 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") 450 } else { 451 fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 452 } 453 454 closer := func() error { 455 httputils.CloseStreams(conn) 456 return nil 457 } 458 return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil 459 } 460 461 attachConfig := &backend.ContainerAttachConfig{ 462 GetStreams: setupStreams, 463 UseStdin: httputils.BoolValue(r, "stdin"), 464 UseStdout: httputils.BoolValue(r, "stdout"), 465 UseStderr: httputils.BoolValue(r, "stderr"), 466 Logs: httputils.BoolValue(r, "logs"), 467 Stream: httputils.BoolValue(r, "stream"), 468 DetachKeys: keys, 469 MuxStreams: true, 470 } 471 472 return s.backend.ContainerAttach(containerName, attachConfig) 473 } 474 475 func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 476 if err := httputils.ParseForm(r); err != nil { 477 return err 478 } 479 containerName := vars["name"] 480 481 var keys []byte 482 var err error 483 detachKeys := r.FormValue("detachKeys") 484 if detachKeys != "" { 485 keys, err = term.ToBytes(detachKeys) 486 if err != nil { 487 logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys) 488 } 489 } 490 491 done := make(chan struct{}) 492 started := make(chan struct{}) 493 494 setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) { 495 wsChan := make(chan *websocket.Conn) 496 h := func(conn *websocket.Conn) { 497 wsChan <- conn 498 <-done 499 } 500 501 srv := websocket.Server{Handler: h, Handshake: nil} 502 go func() { 503 close(started) 504 srv.ServeHTTP(w, r) 505 }() 506 507 conn := <-wsChan 508 return conn, conn, conn, nil 509 } 510 511 attachConfig := &backend.ContainerAttachConfig{ 512 GetStreams: setupStreams, 513 Logs: httputils.BoolValue(r, "logs"), 514 Stream: httputils.BoolValue(r, "stream"), 515 DetachKeys: keys, 516 UseStdin: true, 517 UseStdout: true, 518 UseStderr: true, 519 MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr 520 } 521 522 err = s.backend.ContainerAttach(containerName, attachConfig) 523 close(done) 524 select { 525 case <-started: 526 logrus.Errorf("Error attaching websocket: %s", err) 527 return nil 528 default: 529 } 530 return err 531 }