github.com/moby/docker@v26.1.3+incompatible/api/server/router/container/container_routes.go (about) 1 package container // import "github.com/docker/docker/api/server/router/container" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "runtime" 10 "strconv" 11 "strings" 12 13 "github.com/containerd/containerd/platforms" 14 "github.com/containerd/log" 15 "github.com/docker/docker/api/server/httpstatus" 16 "github.com/docker/docker/api/server/httputils" 17 "github.com/docker/docker/api/types" 18 "github.com/docker/docker/api/types/backend" 19 "github.com/docker/docker/api/types/container" 20 "github.com/docker/docker/api/types/filters" 21 "github.com/docker/docker/api/types/mount" 22 "github.com/docker/docker/api/types/network" 23 "github.com/docker/docker/api/types/versions" 24 containerpkg "github.com/docker/docker/container" 25 "github.com/docker/docker/errdefs" 26 "github.com/docker/docker/pkg/ioutils" 27 "github.com/docker/docker/runconfig" 28 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 29 "github.com/pkg/errors" 30 "golang.org/x/net/websocket" 31 ) 32 33 func (s *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 34 if err := httputils.ParseForm(r); err != nil { 35 return err 36 } 37 38 if err := httputils.CheckForJSON(r); err != nil { 39 return err 40 } 41 42 config, _, _, err := s.decoder.DecodeConfig(r.Body) 43 if err != nil && !errors.Is(err, io.EOF) { // Do not fail if body is empty. 44 return err 45 } 46 47 ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag")) 48 if err != nil { 49 return errdefs.InvalidParameter(err) 50 } 51 52 imgID, err := s.backend.CreateImageFromContainer(ctx, r.Form.Get("container"), &backend.CreateImageConfig{ 53 Pause: httputils.BoolValueOrDefault(r, "pause", true), // TODO(dnephin): remove pause arg, and always pause in backend 54 Tag: ref, 55 Author: r.Form.Get("author"), 56 Comment: r.Form.Get("comment"), 57 Config: config, 58 Changes: r.Form["changes"], 59 }) 60 if err != nil { 61 return err 62 } 63 64 return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID}) 65 } 66 67 func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 68 if err := httputils.ParseForm(r); err != nil { 69 return err 70 } 71 filter, err := filters.FromJSON(r.Form.Get("filters")) 72 if err != nil { 73 return err 74 } 75 76 config := &container.ListOptions{ 77 All: httputils.BoolValue(r, "all"), 78 Size: httputils.BoolValue(r, "size"), 79 Since: r.Form.Get("since"), 80 Before: r.Form.Get("before"), 81 Filters: filter, 82 } 83 84 if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { 85 limit, err := strconv.Atoi(tmpLimit) 86 if err != nil { 87 return err 88 } 89 config.Limit = limit 90 } 91 92 containers, err := s.backend.Containers(ctx, config) 93 if err != nil { 94 return err 95 } 96 97 return httputils.WriteJSON(w, http.StatusOK, containers) 98 } 99 100 func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 101 if err := httputils.ParseForm(r); err != nil { 102 return err 103 } 104 105 stream := httputils.BoolValueOrDefault(r, "stream", true) 106 if !stream { 107 w.Header().Set("Content-Type", "application/json") 108 } 109 var oneShot bool 110 if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.41") { 111 oneShot = httputils.BoolValueOrDefault(r, "one-shot", false) 112 } 113 114 return s.backend.ContainerStats(ctx, vars["name"], &backend.ContainerStatsConfig{ 115 Stream: stream, 116 OneShot: oneShot, 117 OutStream: w, 118 }) 119 } 120 121 func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 122 if err := httputils.ParseForm(r); err != nil { 123 return err 124 } 125 126 // Args are validated before the stream starts because when it starts we're 127 // sending HTTP 200 by writing an empty chunk of data to tell the client that 128 // daemon is going to stream. By sending this initial HTTP 200 we can't report 129 // any error after the stream starts (i.e. container not found, wrong parameters) 130 // with the appropriate status code. 131 stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr") 132 if !(stdout || stderr) { 133 return errdefs.InvalidParameter(errors.New("Bad parameters: you must choose at least one stream")) 134 } 135 136 containerName := vars["name"] 137 logsConfig := &container.LogsOptions{ 138 Follow: httputils.BoolValue(r, "follow"), 139 Timestamps: httputils.BoolValue(r, "timestamps"), 140 Since: r.Form.Get("since"), 141 Until: r.Form.Get("until"), 142 Tail: r.Form.Get("tail"), 143 ShowStdout: stdout, 144 ShowStderr: stderr, 145 Details: httputils.BoolValue(r, "details"), 146 } 147 148 msgs, tty, err := s.backend.ContainerLogs(ctx, containerName, logsConfig) 149 if err != nil { 150 return err 151 } 152 153 contentType := types.MediaTypeRawStream 154 if !tty && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") { 155 contentType = types.MediaTypeMultiplexedStream 156 } 157 w.Header().Set("Content-Type", contentType) 158 159 // if has a tty, we're not muxing streams. if it doesn't, we are. simple. 160 // this is the point of no return for writing a response. once we call 161 // WriteLogStream, the response has been started and errors will be 162 // returned in band by WriteLogStream 163 httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty) 164 return nil 165 } 166 167 func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 168 return s.backend.ContainerExport(ctx, vars["name"], w) 169 } 170 171 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 172 // If contentLength is -1, we can assumed chunked encoding 173 // or more technically that the length is unknown 174 // https://golang.org/src/pkg/net/http/request.go#L139 175 // net/http otherwise seems to swallow any headers related to chunked encoding 176 // including r.TransferEncoding 177 // allow a nil body for backwards compatibility 178 // 179 // A non-nil json object is at least 7 characters. 180 if r.ContentLength > 7 || r.ContentLength == -1 { 181 return errdefs.InvalidParameter(errors.New("starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24")) 182 } 183 184 if err := httputils.ParseForm(r); err != nil { 185 return err 186 } 187 188 if err := s.backend.ContainerStart(ctx, vars["name"], r.Form.Get("checkpoint"), r.Form.Get("checkpoint-dir")); err != nil { 189 return err 190 } 191 192 w.WriteHeader(http.StatusNoContent) 193 return nil 194 } 195 196 func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 197 if err := httputils.ParseForm(r); err != nil { 198 return err 199 } 200 201 var ( 202 options container.StopOptions 203 version = httputils.VersionFromContext(ctx) 204 ) 205 if versions.GreaterThanOrEqualTo(version, "1.42") { 206 options.Signal = r.Form.Get("signal") 207 } 208 if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" { 209 valSeconds, err := strconv.Atoi(tmpSeconds) 210 if err != nil { 211 return err 212 } 213 options.Timeout = &valSeconds 214 } 215 216 if err := s.backend.ContainerStop(ctx, vars["name"], options); err != nil { 217 return err 218 } 219 220 w.WriteHeader(http.StatusNoContent) 221 return nil 222 } 223 224 func (s *containerRouter) postContainersKill(_ context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 225 if err := httputils.ParseForm(r); err != nil { 226 return err 227 } 228 229 name := vars["name"] 230 if err := s.backend.ContainerKill(name, r.Form.Get("signal")); err != nil { 231 return errors.Wrapf(err, "cannot kill container: %s", name) 232 } 233 234 w.WriteHeader(http.StatusNoContent) 235 return nil 236 } 237 238 func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 239 if err := httputils.ParseForm(r); err != nil { 240 return err 241 } 242 243 var ( 244 options container.StopOptions 245 version = httputils.VersionFromContext(ctx) 246 ) 247 if versions.GreaterThanOrEqualTo(version, "1.42") { 248 options.Signal = r.Form.Get("signal") 249 } 250 if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" { 251 valSeconds, err := strconv.Atoi(tmpSeconds) 252 if err != nil { 253 return err 254 } 255 options.Timeout = &valSeconds 256 } 257 258 if err := s.backend.ContainerRestart(ctx, vars["name"], options); err != nil { 259 return err 260 } 261 262 w.WriteHeader(http.StatusNoContent) 263 return nil 264 } 265 266 func (s *containerRouter) postContainersPause(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.ContainerPause(vars["name"]); err != nil { 272 return err 273 } 274 275 w.WriteHeader(http.StatusNoContent) 276 277 return nil 278 } 279 280 func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 281 if err := httputils.ParseForm(r); err != nil { 282 return err 283 } 284 285 if err := s.backend.ContainerUnpause(vars["name"]); err != nil { 286 return err 287 } 288 289 w.WriteHeader(http.StatusNoContent) 290 291 return nil 292 } 293 294 func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 295 // Behavior changed in version 1.30 to handle wait condition and to 296 // return headers immediately. 297 version := httputils.VersionFromContext(ctx) 298 legacyBehaviorPre130 := versions.LessThan(version, "1.30") 299 legacyRemovalWaitPre134 := false 300 301 // The wait condition defaults to "not-running". 302 waitCondition := containerpkg.WaitConditionNotRunning 303 if !legacyBehaviorPre130 { 304 if err := httputils.ParseForm(r); err != nil { 305 return err 306 } 307 if v := r.Form.Get("condition"); v != "" { 308 switch container.WaitCondition(v) { 309 case container.WaitConditionNotRunning: 310 waitCondition = containerpkg.WaitConditionNotRunning 311 case container.WaitConditionNextExit: 312 waitCondition = containerpkg.WaitConditionNextExit 313 case container.WaitConditionRemoved: 314 waitCondition = containerpkg.WaitConditionRemoved 315 legacyRemovalWaitPre134 = versions.LessThan(version, "1.34") 316 default: 317 return errdefs.InvalidParameter(errors.Errorf("invalid condition: %q", v)) 318 } 319 } 320 } 321 322 waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition) 323 if err != nil { 324 return err 325 } 326 327 w.Header().Set("Content-Type", "application/json") 328 329 if !legacyBehaviorPre130 { 330 // Write response header immediately. 331 w.WriteHeader(http.StatusOK) 332 if flusher, ok := w.(http.Flusher); ok { 333 flusher.Flush() 334 } 335 } 336 337 // Block on the result of the wait operation. 338 status := <-waitC 339 340 // With API < 1.34, wait on WaitConditionRemoved did not return 341 // in case container removal failed. The only way to report an 342 // error back to the client is to not write anything (i.e. send 343 // an empty response which will be treated as an error). 344 if legacyRemovalWaitPre134 && status.Err() != nil { 345 return nil 346 } 347 348 var waitError *container.WaitExitError 349 if status.Err() != nil { 350 waitError = &container.WaitExitError{Message: status.Err().Error()} 351 } 352 353 return json.NewEncoder(w).Encode(&container.WaitResponse{ 354 StatusCode: int64(status.ExitCode()), 355 Error: waitError, 356 }) 357 } 358 359 func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 360 changes, err := s.backend.ContainerChanges(ctx, vars["name"]) 361 if err != nil { 362 return err 363 } 364 365 return httputils.WriteJSON(w, http.StatusOK, changes) 366 } 367 368 func (s *containerRouter) getContainersTop(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 373 procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args")) 374 if err != nil { 375 return err 376 } 377 378 return httputils.WriteJSON(w, http.StatusOK, procList) 379 } 380 381 func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 382 if err := httputils.ParseForm(r); err != nil { 383 return err 384 } 385 386 name := vars["name"] 387 newName := r.Form.Get("name") 388 if err := s.backend.ContainerRename(name, newName); err != nil { 389 return err 390 } 391 w.WriteHeader(http.StatusNoContent) 392 return nil 393 } 394 395 func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 396 if err := httputils.ParseForm(r); err != nil { 397 return err 398 } 399 400 var updateConfig container.UpdateConfig 401 if err := httputils.ReadJSON(r, &updateConfig); err != nil { 402 return err 403 } 404 if versions.LessThan(httputils.VersionFromContext(ctx), "1.40") { 405 updateConfig.PidsLimit = nil 406 } 407 408 if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") { 409 // Ignore KernelMemory removed in API 1.42. 410 updateConfig.KernelMemory = 0 411 } 412 413 if updateConfig.PidsLimit != nil && *updateConfig.PidsLimit <= 0 { 414 // Both `0` and `-1` are accepted to set "unlimited" when updating. 415 // Historically, any negative value was accepted, so treat them as 416 // "unlimited" as well. 417 var unlimited int64 418 updateConfig.PidsLimit = &unlimited 419 } 420 421 hostConfig := &container.HostConfig{ 422 Resources: updateConfig.Resources, 423 RestartPolicy: updateConfig.RestartPolicy, 424 } 425 426 name := vars["name"] 427 resp, err := s.backend.ContainerUpdate(name, hostConfig) 428 if err != nil { 429 return err 430 } 431 432 return httputils.WriteJSON(w, http.StatusOK, resp) 433 } 434 435 func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 436 if err := httputils.ParseForm(r); err != nil { 437 return err 438 } 439 if err := httputils.CheckForJSON(r); err != nil { 440 return err 441 } 442 443 name := r.Form.Get("name") 444 445 config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body) 446 if err != nil { 447 if errors.Is(err, io.EOF) { 448 return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body")) 449 } 450 return err 451 } 452 453 if config == nil { 454 return errdefs.InvalidParameter(runconfig.ErrEmptyConfig) 455 } 456 if hostConfig == nil { 457 hostConfig = &container.HostConfig{} 458 } 459 if networkingConfig == nil { 460 networkingConfig = &network.NetworkingConfig{} 461 } 462 if networkingConfig.EndpointsConfig == nil { 463 networkingConfig.EndpointsConfig = make(map[string]*network.EndpointSettings) 464 } 465 // The NetworkMode "default" is used as a way to express a container should 466 // be attached to the OS-dependant default network, in an OS-independent 467 // way. Doing this conversion as soon as possible ensures we have less 468 // NetworkMode to handle down the path (including in the 469 // backward-compatibility layer we have just below). 470 // 471 // Note that this is not the only place where this conversion has to be 472 // done (as there are various other places where containers get created). 473 if hostConfig.NetworkMode == "" || hostConfig.NetworkMode.IsDefault() { 474 hostConfig.NetworkMode = runconfig.DefaultDaemonNetworkMode() 475 if nw, ok := networkingConfig.EndpointsConfig[network.NetworkDefault]; ok { 476 networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()] = nw 477 delete(networkingConfig.EndpointsConfig, network.NetworkDefault) 478 } 479 } 480 481 version := httputils.VersionFromContext(ctx) 482 483 // When using API 1.24 and under, the client is responsible for removing the container 484 if versions.LessThan(version, "1.25") { 485 hostConfig.AutoRemove = false 486 } 487 488 if versions.LessThan(version, "1.40") { 489 // Ignore BindOptions.NonRecursive because it was added in API 1.40. 490 for _, m := range hostConfig.Mounts { 491 if bo := m.BindOptions; bo != nil { 492 bo.NonRecursive = false 493 } 494 } 495 496 // Ignore KernelMemoryTCP because it was added in API 1.40. 497 hostConfig.KernelMemoryTCP = 0 498 499 // Older clients (API < 1.40) expects the default to be shareable, make them happy 500 if hostConfig.IpcMode.IsEmpty() { 501 hostConfig.IpcMode = container.IPCModeShareable 502 } 503 } 504 505 if versions.LessThan(version, "1.41") { 506 // Older clients expect the default to be "host" on cgroup v1 hosts 507 if !s.cgroup2 && hostConfig.CgroupnsMode.IsEmpty() { 508 hostConfig.CgroupnsMode = container.CgroupnsModeHost 509 } 510 } 511 512 var platform *ocispec.Platform 513 if versions.GreaterThanOrEqualTo(version, "1.41") { 514 if v := r.Form.Get("platform"); v != "" { 515 p, err := platforms.Parse(v) 516 if err != nil { 517 return errdefs.InvalidParameter(err) 518 } 519 platform = &p 520 } 521 } 522 523 if versions.LessThan(version, "1.42") { 524 for _, m := range hostConfig.Mounts { 525 // Ignore BindOptions.CreateMountpoint because it was added in API 1.42. 526 if bo := m.BindOptions; bo != nil { 527 bo.CreateMountpoint = false 528 } 529 530 // These combinations are invalid, but weren't validated in API < 1.42. 531 // We reset them here, so that validation doesn't produce an error. 532 if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume { 533 m.VolumeOptions = nil 534 } 535 if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs { 536 m.TmpfsOptions = nil 537 } 538 if bo := m.BindOptions; bo != nil { 539 // Ignore BindOptions.CreateMountpoint because it was added in API 1.42. 540 bo.CreateMountpoint = false 541 } 542 } 543 544 if runtime.GOOS == "linux" { 545 // ConsoleSize is not respected by Linux daemon before API 1.42 546 hostConfig.ConsoleSize = [2]uint{0, 0} 547 } 548 } 549 550 if versions.GreaterThanOrEqualTo(version, "1.42") { 551 // Ignore KernelMemory removed in API 1.42. 552 hostConfig.KernelMemory = 0 553 for _, m := range hostConfig.Mounts { 554 if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume { 555 return errdefs.InvalidParameter(fmt.Errorf("VolumeOptions must not be specified on mount type %q", m.Type)) 556 } 557 if o := m.BindOptions; o != nil && m.Type != mount.TypeBind { 558 return errdefs.InvalidParameter(fmt.Errorf("BindOptions must not be specified on mount type %q", m.Type)) 559 } 560 if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs { 561 return errdefs.InvalidParameter(fmt.Errorf("TmpfsOptions must not be specified on mount type %q", m.Type)) 562 } 563 } 564 } 565 566 if versions.LessThan(version, "1.43") { 567 // Ignore Annotations because it was added in API v1.43. 568 hostConfig.Annotations = nil 569 } 570 571 defaultReadOnlyNonRecursive := false 572 if versions.LessThan(version, "1.44") { 573 if config.Healthcheck != nil { 574 // StartInterval was added in API 1.44 575 config.Healthcheck.StartInterval = 0 576 } 577 578 // Set ReadOnlyNonRecursive to true because it was added in API 1.44 579 // Before that all read-only mounts were non-recursive. 580 // Keep that behavior for clients on older APIs. 581 defaultReadOnlyNonRecursive = true 582 583 for _, m := range hostConfig.Mounts { 584 if m.Type == mount.TypeBind { 585 if m.BindOptions != nil && m.BindOptions.ReadOnlyForceRecursive { 586 // NOTE: that technically this is a breaking change for older 587 // API versions, and we should ignore the new field. 588 // However, this option may be incorrectly set by a client with 589 // the expectation that the failing to apply recursive read-only 590 // is enforced, so we decided to produce an error instead, 591 // instead of silently ignoring. 592 return errdefs.InvalidParameter(errors.New("BindOptions.ReadOnlyForceRecursive needs API v1.44 or newer")) 593 } 594 } 595 } 596 597 // Creating a container connected to several networks is not supported until v1.44. 598 if len(networkingConfig.EndpointsConfig) > 1 { 599 l := make([]string, 0, len(networkingConfig.EndpointsConfig)) 600 for k := range networkingConfig.EndpointsConfig { 601 l = append(l, k) 602 } 603 return errdefs.InvalidParameter(errors.Errorf("Container cannot be created with multiple network endpoints: %s", strings.Join(l, ", "))) 604 } 605 } 606 607 if versions.LessThan(version, "1.45") { 608 for _, m := range hostConfig.Mounts { 609 if m.VolumeOptions != nil && m.VolumeOptions.Subpath != "" { 610 return errdefs.InvalidParameter(errors.New("VolumeOptions.Subpath needs API v1.45 or newer")) 611 } 612 } 613 } 614 615 var warnings []string 616 if warn, err := handleMACAddressBC(config, hostConfig, networkingConfig, version); err != nil { 617 return err 618 } else if warn != "" { 619 warnings = append(warnings, warn) 620 } 621 622 if hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 { 623 // Don't set a limit if either no limit was specified, or "unlimited" was 624 // explicitly set. 625 // Both `0` and `-1` are accepted as "unlimited", and historically any 626 // negative value was accepted, so treat those as "unlimited" as well. 627 hostConfig.PidsLimit = nil 628 } 629 630 ccr, err := s.backend.ContainerCreate(ctx, backend.ContainerCreateConfig{ 631 Name: name, 632 Config: config, 633 HostConfig: hostConfig, 634 NetworkingConfig: networkingConfig, 635 Platform: platform, 636 DefaultReadOnlyNonRecursive: defaultReadOnlyNonRecursive, 637 }) 638 if err != nil { 639 return err 640 } 641 ccr.Warnings = append(ccr.Warnings, warnings...) 642 return httputils.WriteJSON(w, http.StatusCreated, ccr) 643 } 644 645 // handleMACAddressBC takes care of backward-compatibility for the container-wide MAC address by mutating the 646 // networkingConfig to set the endpoint-specific MACAddress field introduced in API v1.44. It returns a warning message 647 // or an error if the container-wide field was specified for API >= v1.44. 648 func handleMACAddressBC(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, version string) (string, error) { 649 deprecatedMacAddress := config.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. 650 651 // For older versions of the API, migrate the container-wide MAC address to EndpointsConfig. 652 if versions.LessThan(version, "1.44") { 653 if deprecatedMacAddress == "" { 654 // If a MAC address is supplied in EndpointsConfig, discard it because the old API 655 // would have ignored it. 656 for _, ep := range networkingConfig.EndpointsConfig { 657 ep.MacAddress = "" 658 } 659 return "", nil 660 } 661 if !hostConfig.NetworkMode.IsBridge() && !hostConfig.NetworkMode.IsUserDefined() { 662 return "", runconfig.ErrConflictContainerNetworkAndMac 663 } 664 665 // There cannot be more than one entry in EndpointsConfig with API < 1.44. 666 667 // If there's no EndpointsConfig, create a place to store the configured address. It is 668 // safe to use NetworkMode as the network name, whether it's a name or id/short-id, as 669 // it will be normalised later and there is no other EndpointSettings object that might 670 // refer to this network/endpoint. 671 if len(networkingConfig.EndpointsConfig) == 0 { 672 nwName := hostConfig.NetworkMode.NetworkName() 673 networkingConfig.EndpointsConfig[nwName] = &network.EndpointSettings{} 674 } 675 // There's exactly one network in EndpointsConfig, either from the API or just-created. 676 // Migrate the container-wide setting to it. 677 // No need to check for a match between NetworkMode and the names/ids in EndpointsConfig, 678 // the old version of the API would have applied the address to this network anyway. 679 for _, ep := range networkingConfig.EndpointsConfig { 680 ep.MacAddress = deprecatedMacAddress 681 } 682 return "", nil 683 } 684 685 // The container-wide MacAddress parameter is deprecated and should now be specified in EndpointsConfig. 686 if deprecatedMacAddress == "" { 687 return "", nil 688 } 689 var warning string 690 if hostConfig.NetworkMode.IsBridge() || hostConfig.NetworkMode.IsUserDefined() { 691 nwName := hostConfig.NetworkMode.NetworkName() 692 // If there's no endpoint config, create a place to store the configured address. 693 if len(networkingConfig.EndpointsConfig) == 0 { 694 networkingConfig.EndpointsConfig[nwName] = &network.EndpointSettings{ 695 MacAddress: deprecatedMacAddress, 696 } 697 } else { 698 // There is existing endpoint config - if it's not indexed by NetworkMode.Name(), we 699 // can't tell which network the container-wide settings was intended for. NetworkMode, 700 // the keys in EndpointsConfig and the NetworkID in EndpointsConfig may mix network 701 // name/id/short-id. It's not safe to create EndpointsConfig under the NetworkMode 702 // name to store the container-wide MAC address, because that may result in two sets 703 // of EndpointsConfig for the same network and one set will be discarded later. So, 704 // reject the request ... 705 ep, ok := networkingConfig.EndpointsConfig[nwName] 706 if !ok { 707 return "", errdefs.InvalidParameter(errors.New("if a container-wide MAC address is supplied, HostConfig.NetworkMode must match the identity of a network in NetworkSettings.Networks")) 708 } 709 // ep is the endpoint that needs the container-wide MAC address; migrate the address 710 // to it, or bail out if there's a mismatch. 711 if ep.MacAddress == "" { 712 ep.MacAddress = deprecatedMacAddress 713 } else if ep.MacAddress != deprecatedMacAddress { 714 return "", errdefs.InvalidParameter(errors.New("the container-wide MAC address must match the endpoint-specific MAC address for the main network, or be left empty")) 715 } 716 } 717 } 718 warning = "The container-wide MacAddress field is now deprecated. It should be specified in EndpointsConfig instead." 719 config.MacAddress = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. 720 721 return warning, nil 722 } 723 724 func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 725 if err := httputils.ParseForm(r); err != nil { 726 return err 727 } 728 729 name := vars["name"] 730 config := &backend.ContainerRmConfig{ 731 ForceRemove: httputils.BoolValue(r, "force"), 732 RemoveVolume: httputils.BoolValue(r, "v"), 733 RemoveLink: httputils.BoolValue(r, "link"), 734 } 735 736 if err := s.backend.ContainerRm(name, config); err != nil { 737 return err 738 } 739 740 w.WriteHeader(http.StatusNoContent) 741 742 return nil 743 } 744 745 func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 746 if err := httputils.ParseForm(r); err != nil { 747 return err 748 } 749 750 height, err := strconv.Atoi(r.Form.Get("h")) 751 if err != nil { 752 return errdefs.InvalidParameter(err) 753 } 754 width, err := strconv.Atoi(r.Form.Get("w")) 755 if err != nil { 756 return errdefs.InvalidParameter(err) 757 } 758 759 return s.backend.ContainerResize(vars["name"], height, width) 760 } 761 762 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 763 err := httputils.ParseForm(r) 764 if err != nil { 765 return err 766 } 767 containerName := vars["name"] 768 769 _, upgrade := r.Header["Upgrade"] 770 detachKeys := r.FormValue("detachKeys") 771 772 hijacker, ok := w.(http.Hijacker) 773 if !ok { 774 return errdefs.InvalidParameter(errors.Errorf("error attaching to container %s, hijack connection missing", containerName)) 775 } 776 777 contentType := types.MediaTypeRawStream 778 setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) { 779 conn, _, err := hijacker.Hijack() 780 if err != nil { 781 return nil, nil, nil, err 782 } 783 784 // set raw mode 785 conn.Write([]byte{}) 786 787 if upgrade { 788 if multiplexed && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") { 789 contentType = types.MediaTypeMultiplexedStream 790 } 791 fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: "+contentType+"\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") 792 } else { 793 fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 794 } 795 796 closer := func() error { 797 httputils.CloseStreams(conn) 798 return nil 799 } 800 return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil 801 } 802 803 attachConfig := &backend.ContainerAttachConfig{ 804 GetStreams: setupStreams, 805 UseStdin: httputils.BoolValue(r, "stdin"), 806 UseStdout: httputils.BoolValue(r, "stdout"), 807 UseStderr: httputils.BoolValue(r, "stderr"), 808 Logs: httputils.BoolValue(r, "logs"), 809 Stream: httputils.BoolValue(r, "stream"), 810 DetachKeys: detachKeys, 811 MuxStreams: true, 812 } 813 814 if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil { 815 log.G(ctx).WithError(err).Errorf("Handler for %s %s returned error", r.Method, r.URL.Path) 816 // Remember to close stream if error happens 817 conn, _, errHijack := hijacker.Hijack() 818 if errHijack != nil { 819 log.G(ctx).WithError(err).Errorf("Handler for %s %s: unable to close stream; error when hijacking connection", r.Method, r.URL.Path) 820 } else { 821 statusCode := httpstatus.FromError(err) 822 statusText := http.StatusText(statusCode) 823 fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: %s\r\n\r\n%s\r\n", statusCode, statusText, contentType, err.Error()) 824 httputils.CloseStreams(conn) 825 } 826 } 827 return nil 828 } 829 830 func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 831 if err := httputils.ParseForm(r); err != nil { 832 return err 833 } 834 containerName := vars["name"] 835 836 var err error 837 detachKeys := r.FormValue("detachKeys") 838 839 done := make(chan struct{}) 840 started := make(chan struct{}) 841 842 version := httputils.VersionFromContext(ctx) 843 844 setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) { 845 wsChan := make(chan *websocket.Conn) 846 h := func(conn *websocket.Conn) { 847 wsChan <- conn 848 <-done 849 } 850 851 srv := websocket.Server{Handler: h, Handshake: nil} 852 go func() { 853 close(started) 854 srv.ServeHTTP(w, r) 855 }() 856 857 conn := <-wsChan 858 // In case version 1.28 and above, a binary frame will be sent. 859 // See 28176 for details. 860 if versions.GreaterThanOrEqualTo(version, "1.28") { 861 conn.PayloadType = websocket.BinaryFrame 862 } 863 return conn, conn, conn, nil 864 } 865 866 useStdin, useStdout, useStderr := true, true, true 867 if versions.GreaterThanOrEqualTo(version, "1.42") { 868 useStdin = httputils.BoolValue(r, "stdin") 869 useStdout = httputils.BoolValue(r, "stdout") 870 useStderr = httputils.BoolValue(r, "stderr") 871 } 872 873 attachConfig := &backend.ContainerAttachConfig{ 874 GetStreams: setupStreams, 875 UseStdin: useStdin, 876 UseStdout: useStdout, 877 UseStderr: useStderr, 878 Logs: httputils.BoolValue(r, "logs"), 879 Stream: httputils.BoolValue(r, "stream"), 880 DetachKeys: detachKeys, 881 MuxStreams: false, // never multiplex, as we rely on websocket to manage distinct streams 882 } 883 884 err = s.backend.ContainerAttach(containerName, attachConfig) 885 close(done) 886 select { 887 case <-started: 888 if err != nil { 889 log.G(ctx).Errorf("Error attaching websocket: %s", err) 890 } else { 891 log.G(ctx).Debug("websocket connection was closed by client") 892 } 893 return nil 894 default: 895 } 896 return err 897 } 898 899 func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 900 if err := httputils.ParseForm(r); err != nil { 901 return err 902 } 903 904 pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) 905 if err != nil { 906 return err 907 } 908 909 pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters) 910 if err != nil { 911 return err 912 } 913 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 914 }