github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/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/distribution/registry/api/errcode" 15 "github.com/docker/docker/api/server/httputils" 16 "github.com/docker/docker/daemon" 17 derr "github.com/docker/docker/errors" 18 "github.com/docker/docker/pkg/ioutils" 19 "github.com/docker/docker/pkg/signal" 20 "github.com/docker/docker/pkg/term" 21 "github.com/docker/docker/runconfig" 22 "github.com/docker/docker/utils" 23 "github.com/docker/engine-api/types" 24 "github.com/docker/engine-api/types/container" 25 timetypes "github.com/docker/engine-api/types/time" 26 "golang.org/x/net/context" 27 "golang.org/x/net/websocket" 28 ) 29 30 func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 31 if err := httputils.ParseForm(r); err != nil { 32 return err 33 } 34 35 config := &daemon.ContainersConfig{ 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: r.Form.Get("filters"), 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 var out io.Writer 66 if !stream { 67 w.Header().Set("Content-Type", "application/json") 68 out = w 69 } else { 70 wf := ioutils.NewWriteFlusher(w) 71 out = wf 72 defer wf.Close() 73 } 74 75 var closeNotifier <-chan bool 76 if notifier, ok := w.(http.CloseNotifier); ok { 77 closeNotifier = notifier.CloseNotify() 78 } 79 80 config := &daemon.ContainerStatsConfig{ 81 Stream: stream, 82 OutStream: out, 83 Stop: closeNotifier, 84 Version: httputils.VersionFromContext(ctx), 85 } 86 87 return s.backend.ContainerStats(vars["name"], config) 88 } 89 90 func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 91 if err := httputils.ParseForm(r); err != nil { 92 return err 93 } 94 95 // Args are validated before the stream starts because when it starts we're 96 // sending HTTP 200 by writing an empty chunk of data to tell the client that 97 // daemon is going to stream. By sending this initial HTTP 200 we can't report 98 // any error after the stream starts (i.e. container not found, wrong parameters) 99 // with the appropriate status code. 100 stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr") 101 if !(stdout || stderr) { 102 return fmt.Errorf("Bad parameters: you must choose at least one stream") 103 } 104 105 var since time.Time 106 if r.Form.Get("since") != "" { 107 s, n, err := timetypes.ParseTimestamps(r.Form.Get("since"), 0) 108 if err != nil { 109 return err 110 } 111 since = time.Unix(s, n) 112 } 113 114 var closeNotifier <-chan bool 115 if notifier, ok := w.(http.CloseNotifier); ok { 116 closeNotifier = notifier.CloseNotify() 117 } 118 119 containerName := vars["name"] 120 121 if !s.backend.Exists(containerName) { 122 return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) 123 } 124 125 // write an empty chunk of data (this is to ensure that the 126 // HTTP Response is sent immediately, even if the container has 127 // not yet produced any data) 128 w.WriteHeader(http.StatusOK) 129 if flusher, ok := w.(http.Flusher); ok { 130 flusher.Flush() 131 } 132 133 output := ioutils.NewWriteFlusher(w) 134 defer output.Close() 135 136 logsConfig := &daemon.ContainerLogsConfig{ 137 Follow: httputils.BoolValue(r, "follow"), 138 Timestamps: httputils.BoolValue(r, "timestamps"), 139 Since: since, 140 Tail: r.Form.Get("tail"), 141 UseStdout: stdout, 142 UseStderr: stderr, 143 OutStream: output, 144 Stop: closeNotifier, 145 } 146 147 if err := s.backend.ContainerLogs(containerName, logsConfig); err != nil { 148 // The client may be expecting all of the data we're sending to 149 // be multiplexed, so send it through OutStream, which will 150 // have been set up to handle that if needed. 151 fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err)) 152 } 153 154 return nil 155 } 156 157 func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 158 return s.backend.ContainerExport(vars["name"], w) 159 } 160 161 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 162 // If contentLength is -1, we can assumed chunked encoding 163 // or more technically that the length is unknown 164 // https://golang.org/src/pkg/net/http/request.go#L139 165 // net/http otherwise seems to swallow any headers related to chunked encoding 166 // including r.TransferEncoding 167 // allow a nil body for backwards compatibility 168 var hostConfig *container.HostConfig 169 if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { 170 if err := httputils.CheckForJSON(r); err != nil { 171 return err 172 } 173 174 c, err := runconfig.DecodeHostConfig(r.Body) 175 if err != nil { 176 return err 177 } 178 179 hostConfig = c 180 } 181 182 if err := s.backend.ContainerStart(vars["name"], hostConfig); err != nil { 183 return err 184 } 185 w.WriteHeader(http.StatusNoContent) 186 return nil 187 } 188 189 func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 190 if err := httputils.ParseForm(r); err != nil { 191 return err 192 } 193 194 seconds, _ := strconv.Atoi(r.Form.Get("t")) 195 196 if err := s.backend.ContainerStop(vars["name"], seconds); err != nil { 197 return err 198 } 199 w.WriteHeader(http.StatusNoContent) 200 201 return nil 202 } 203 204 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 205 if err := httputils.ParseForm(r); err != nil { 206 return err 207 } 208 209 var sig syscall.Signal 210 name := vars["name"] 211 212 // If we have a signal, look at it. Otherwise, do nothing 213 if sigStr := r.Form.Get("signal"); sigStr != "" { 214 var err error 215 if sig, err = signal.ParseSignal(sigStr); err != nil { 216 return err 217 } 218 } 219 220 if err := s.backend.ContainerKill(name, uint64(sig)); err != nil { 221 theErr, isDerr := err.(errcode.ErrorCoder) 222 isStopped := isDerr && theErr.ErrorCode() == derr.ErrorCodeNotRunning 223 224 // Return error that's not caused because the container is stopped. 225 // Return error if the container is not running and the api is >= 1.20 226 // to keep backwards compatibility. 227 version := httputils.VersionFromContext(ctx) 228 if version.GreaterThanOrEqualTo("1.20") || !isStopped { 229 return fmt.Errorf("Cannot kill container %s: %v", name, utils.GetErrorMessage(err)) 230 } 231 } 232 233 w.WriteHeader(http.StatusNoContent) 234 return nil 235 } 236 237 func (s *containerRouter) postContainersRestart(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 timeout, _ := strconv.Atoi(r.Form.Get("t")) 243 244 if err := s.backend.ContainerRestart(vars["name"], timeout); 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, &types.ContainerWaitResponse{ 288 StatusCode: 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 } 346 347 name := vars["name"] 348 warnings, err := s.backend.ContainerUpdate(name, hostConfig) 349 if err != nil { 350 return err 351 } 352 353 return httputils.WriteJSON(w, http.StatusOK, &types.ContainerUpdateResponse{ 354 Warnings: warnings, 355 }) 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 := runconfig.DecodeContainerConfig(r.Body) 369 if err != nil { 370 return err 371 } 372 version := httputils.VersionFromContext(ctx) 373 adjustCPUShares := version.LessThan("1.19") 374 375 ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{ 376 Name: name, 377 Config: config, 378 HostConfig: hostConfig, 379 NetworkingConfig: networkingConfig, 380 AdjustCPUShares: adjustCPUShares, 381 }) 382 if err != nil { 383 return err 384 } 385 386 return httputils.WriteJSON(w, http.StatusCreated, ccr) 387 } 388 389 func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 390 if err := httputils.ParseForm(r); err != nil { 391 return err 392 } 393 394 name := vars["name"] 395 config := &types.ContainerRmConfig{ 396 ForceRemove: httputils.BoolValue(r, "force"), 397 RemoveVolume: httputils.BoolValue(r, "v"), 398 RemoveLink: httputils.BoolValue(r, "link"), 399 } 400 401 if err := s.backend.ContainerRm(name, config); err != nil { 402 // Force a 404 for the empty string 403 if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { 404 return fmt.Errorf("no such container: \"\"") 405 } 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 440 keys := []byte{} 441 detachKeys := r.FormValue("detachKeys") 442 if detachKeys != "" { 443 keys, err = term.ToBytes(detachKeys) 444 if err != nil { 445 logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys) 446 } 447 } 448 449 attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{ 450 Hijacker: w.(http.Hijacker), 451 Upgrade: upgrade, 452 UseStdin: httputils.BoolValue(r, "stdin"), 453 UseStdout: httputils.BoolValue(r, "stdout"), 454 UseStderr: httputils.BoolValue(r, "stderr"), 455 Logs: httputils.BoolValue(r, "logs"), 456 Stream: httputils.BoolValue(r, "stream"), 457 DetachKeys: keys, 458 } 459 460 return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig) 461 } 462 463 func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 464 if err := httputils.ParseForm(r); err != nil { 465 return err 466 } 467 containerName := vars["name"] 468 469 if !s.backend.Exists(containerName) { 470 return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) 471 } 472 473 var keys []byte 474 var err error 475 detachKeys := r.FormValue("detachKeys") 476 if detachKeys != "" { 477 keys, err = term.ToBytes(detachKeys) 478 if err != nil { 479 logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys) 480 } 481 } 482 483 h := websocket.Handler(func(ws *websocket.Conn) { 484 defer ws.Close() 485 486 wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{ 487 InStream: ws, 488 OutStream: ws, 489 ErrStream: ws, 490 Logs: httputils.BoolValue(r, "logs"), 491 Stream: httputils.BoolValue(r, "stream"), 492 DetachKeys: keys, 493 } 494 495 if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil { 496 logrus.Errorf("Error attaching websocket: %s", utils.GetErrorMessage(err)) 497 } 498 }) 499 ws := websocket.Server{Handler: h, Handshake: nil} 500 ws.ServeHTTP(w, r) 501 502 return nil 503 }