github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+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 hostConfig.NetworkMode == "" { 460 hostConfig.NetworkMode = "default" 461 } 462 if networkingConfig == nil { 463 networkingConfig = &network.NetworkingConfig{} 464 } 465 if networkingConfig.EndpointsConfig == nil { 466 networkingConfig.EndpointsConfig = make(map[string]*network.EndpointSettings) 467 } 468 469 version := httputils.VersionFromContext(ctx) 470 471 // When using API 1.24 and under, the client is responsible for removing the container 472 if versions.LessThan(version, "1.25") { 473 hostConfig.AutoRemove = false 474 } 475 476 if versions.LessThan(version, "1.40") { 477 // Ignore BindOptions.NonRecursive because it was added in API 1.40. 478 for _, m := range hostConfig.Mounts { 479 if bo := m.BindOptions; bo != nil { 480 bo.NonRecursive = false 481 } 482 } 483 484 // Ignore KernelMemoryTCP because it was added in API 1.40. 485 hostConfig.KernelMemoryTCP = 0 486 487 // Older clients (API < 1.40) expects the default to be shareable, make them happy 488 if hostConfig.IpcMode.IsEmpty() { 489 hostConfig.IpcMode = container.IPCModeShareable 490 } 491 } 492 493 if versions.LessThan(version, "1.41") { 494 // Older clients expect the default to be "host" on cgroup v1 hosts 495 if !s.cgroup2 && hostConfig.CgroupnsMode.IsEmpty() { 496 hostConfig.CgroupnsMode = container.CgroupnsModeHost 497 } 498 } 499 500 var platform *ocispec.Platform 501 if versions.GreaterThanOrEqualTo(version, "1.41") { 502 if v := r.Form.Get("platform"); v != "" { 503 p, err := platforms.Parse(v) 504 if err != nil { 505 return errdefs.InvalidParameter(err) 506 } 507 platform = &p 508 } 509 } 510 511 if versions.LessThan(version, "1.42") { 512 for _, m := range hostConfig.Mounts { 513 // Ignore BindOptions.CreateMountpoint because it was added in API 1.42. 514 if bo := m.BindOptions; bo != nil { 515 bo.CreateMountpoint = false 516 } 517 518 // These combinations are invalid, but weren't validated in API < 1.42. 519 // We reset them here, so that validation doesn't produce an error. 520 if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume { 521 m.VolumeOptions = nil 522 } 523 if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs { 524 m.TmpfsOptions = nil 525 } 526 if bo := m.BindOptions; bo != nil { 527 // Ignore BindOptions.CreateMountpoint because it was added in API 1.42. 528 bo.CreateMountpoint = false 529 } 530 } 531 532 if runtime.GOOS == "linux" { 533 // ConsoleSize is not respected by Linux daemon before API 1.42 534 hostConfig.ConsoleSize = [2]uint{0, 0} 535 } 536 } 537 538 if versions.GreaterThanOrEqualTo(version, "1.42") { 539 // Ignore KernelMemory removed in API 1.42. 540 hostConfig.KernelMemory = 0 541 for _, m := range hostConfig.Mounts { 542 if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume { 543 return errdefs.InvalidParameter(fmt.Errorf("VolumeOptions must not be specified on mount type %q", m.Type)) 544 } 545 if o := m.BindOptions; o != nil && m.Type != mount.TypeBind { 546 return errdefs.InvalidParameter(fmt.Errorf("BindOptions must not be specified on mount type %q", m.Type)) 547 } 548 if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs { 549 return errdefs.InvalidParameter(fmt.Errorf("TmpfsOptions must not be specified on mount type %q", m.Type)) 550 } 551 } 552 } 553 554 if versions.LessThan(version, "1.43") { 555 // Ignore Annotations because it was added in API v1.43. 556 hostConfig.Annotations = nil 557 } 558 559 defaultReadOnlyNonRecursive := false 560 if versions.LessThan(version, "1.44") { 561 if config.Healthcheck != nil { 562 // StartInterval was added in API 1.44 563 config.Healthcheck.StartInterval = 0 564 } 565 566 // Set ReadOnlyNonRecursive to true because it was added in API 1.44 567 // Before that all read-only mounts were non-recursive. 568 // Keep that behavior for clients on older APIs. 569 defaultReadOnlyNonRecursive = true 570 571 for _, m := range hostConfig.Mounts { 572 if m.Type == mount.TypeBind { 573 if m.BindOptions != nil && m.BindOptions.ReadOnlyForceRecursive { 574 // NOTE: that technically this is a breaking change for older 575 // API versions, and we should ignore the new field. 576 // However, this option may be incorrectly set by a client with 577 // the expectation that the failing to apply recursive read-only 578 // is enforced, so we decided to produce an error instead, 579 // instead of silently ignoring. 580 return errdefs.InvalidParameter(errors.New("BindOptions.ReadOnlyForceRecursive needs API v1.44 or newer")) 581 } 582 } 583 } 584 585 // Creating a container connected to several networks is not supported until v1.44. 586 if len(networkingConfig.EndpointsConfig) > 1 { 587 l := make([]string, 0, len(networkingConfig.EndpointsConfig)) 588 for k := range networkingConfig.EndpointsConfig { 589 l = append(l, k) 590 } 591 return errdefs.InvalidParameter(errors.Errorf("Container cannot be created with multiple network endpoints: %s", strings.Join(l, ", "))) 592 } 593 } 594 595 if versions.LessThan(version, "1.45") { 596 for _, m := range hostConfig.Mounts { 597 if m.VolumeOptions != nil && m.VolumeOptions.Subpath != "" { 598 return errdefs.InvalidParameter(errors.New("VolumeOptions.Subpath needs API v1.45 or newer")) 599 } 600 } 601 } 602 603 var warnings []string 604 if warn, err := handleMACAddressBC(config, hostConfig, networkingConfig, version); err != nil { 605 return err 606 } else if warn != "" { 607 warnings = append(warnings, warn) 608 } 609 610 if hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 { 611 // Don't set a limit if either no limit was specified, or "unlimited" was 612 // explicitly set. 613 // Both `0` and `-1` are accepted as "unlimited", and historically any 614 // negative value was accepted, so treat those as "unlimited" as well. 615 hostConfig.PidsLimit = nil 616 } 617 618 ccr, err := s.backend.ContainerCreate(ctx, backend.ContainerCreateConfig{ 619 Name: name, 620 Config: config, 621 HostConfig: hostConfig, 622 NetworkingConfig: networkingConfig, 623 Platform: platform, 624 DefaultReadOnlyNonRecursive: defaultReadOnlyNonRecursive, 625 }) 626 if err != nil { 627 return err 628 } 629 ccr.Warnings = append(ccr.Warnings, warnings...) 630 return httputils.WriteJSON(w, http.StatusCreated, ccr) 631 } 632 633 // handleMACAddressBC takes care of backward-compatibility for the container-wide MAC address by mutating the 634 // networkingConfig to set the endpoint-specific MACAddress field introduced in API v1.44. It returns a warning message 635 // or an error if the container-wide field was specified for API >= v1.44. 636 func handleMACAddressBC(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, version string) (string, error) { 637 deprecatedMacAddress := config.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. 638 639 // For older versions of the API, migrate the container-wide MAC address to EndpointsConfig. 640 if versions.LessThan(version, "1.44") { 641 if deprecatedMacAddress == "" { 642 // If a MAC address is supplied in EndpointsConfig, discard it because the old API 643 // would have ignored it. 644 for _, ep := range networkingConfig.EndpointsConfig { 645 ep.MacAddress = "" 646 } 647 return "", nil 648 } 649 if !hostConfig.NetworkMode.IsDefault() && !hostConfig.NetworkMode.IsBridge() && !hostConfig.NetworkMode.IsUserDefined() { 650 return "", runconfig.ErrConflictContainerNetworkAndMac 651 } 652 653 // There cannot be more than one entry in EndpointsConfig with API < 1.44. 654 655 // If there's no EndpointsConfig, create a place to store the configured address. It is 656 // safe to use NetworkMode as the network name, whether it's a name or id/short-id, as 657 // it will be normalised later and there is no other EndpointSettings object that might 658 // refer to this network/endpoint. 659 if len(networkingConfig.EndpointsConfig) == 0 { 660 nwName := hostConfig.NetworkMode.NetworkName() 661 networkingConfig.EndpointsConfig[nwName] = &network.EndpointSettings{} 662 } 663 // There's exactly one network in EndpointsConfig, either from the API or just-created. 664 // Migrate the container-wide setting to it. 665 // No need to check for a match between NetworkMode and the names/ids in EndpointsConfig, 666 // the old version of the API would have applied the address to this network anyway. 667 for _, ep := range networkingConfig.EndpointsConfig { 668 ep.MacAddress = deprecatedMacAddress 669 } 670 return "", nil 671 } 672 673 // The container-wide MacAddress parameter is deprecated and should now be specified in EndpointsConfig. 674 if deprecatedMacAddress == "" { 675 return "", nil 676 } 677 var warning string 678 if hostConfig.NetworkMode.IsDefault() || hostConfig.NetworkMode.IsBridge() || hostConfig.NetworkMode.IsUserDefined() { 679 nwName := hostConfig.NetworkMode.NetworkName() 680 // If there's no endpoint config, create a place to store the configured address. 681 if len(networkingConfig.EndpointsConfig) == 0 { 682 networkingConfig.EndpointsConfig[nwName] = &network.EndpointSettings{ 683 MacAddress: deprecatedMacAddress, 684 } 685 } else { 686 // There is existing endpoint config - if it's not indexed by NetworkMode.Name(), we 687 // can't tell which network the container-wide settings was intended for. NetworkMode, 688 // the keys in EndpointsConfig and the NetworkID in EndpointsConfig may mix network 689 // name/id/short-id. It's not safe to create EndpointsConfig under the NetworkMode 690 // name to store the container-wide MAC address, because that may result in two sets 691 // of EndpointsConfig for the same network and one set will be discarded later. So, 692 // reject the request ... 693 ep, ok := networkingConfig.EndpointsConfig[nwName] 694 if !ok { 695 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")) 696 } 697 // ep is the endpoint that needs the container-wide MAC address; migrate the address 698 // to it, or bail out if there's a mismatch. 699 if ep.MacAddress == "" { 700 ep.MacAddress = deprecatedMacAddress 701 } else if ep.MacAddress != deprecatedMacAddress { 702 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")) 703 } 704 } 705 } 706 warning = "The container-wide MacAddress field is now deprecated. It should be specified in EndpointsConfig instead." 707 config.MacAddress = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. 708 709 return warning, nil 710 } 711 712 func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 713 if err := httputils.ParseForm(r); err != nil { 714 return err 715 } 716 717 name := vars["name"] 718 config := &backend.ContainerRmConfig{ 719 ForceRemove: httputils.BoolValue(r, "force"), 720 RemoveVolume: httputils.BoolValue(r, "v"), 721 RemoveLink: httputils.BoolValue(r, "link"), 722 } 723 724 if err := s.backend.ContainerRm(name, config); err != nil { 725 return err 726 } 727 728 w.WriteHeader(http.StatusNoContent) 729 730 return nil 731 } 732 733 func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 734 if err := httputils.ParseForm(r); err != nil { 735 return err 736 } 737 738 height, err := strconv.Atoi(r.Form.Get("h")) 739 if err != nil { 740 return errdefs.InvalidParameter(err) 741 } 742 width, err := strconv.Atoi(r.Form.Get("w")) 743 if err != nil { 744 return errdefs.InvalidParameter(err) 745 } 746 747 return s.backend.ContainerResize(vars["name"], height, width) 748 } 749 750 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 751 err := httputils.ParseForm(r) 752 if err != nil { 753 return err 754 } 755 containerName := vars["name"] 756 757 _, upgrade := r.Header["Upgrade"] 758 detachKeys := r.FormValue("detachKeys") 759 760 hijacker, ok := w.(http.Hijacker) 761 if !ok { 762 return errdefs.InvalidParameter(errors.Errorf("error attaching to container %s, hijack connection missing", containerName)) 763 } 764 765 contentType := types.MediaTypeRawStream 766 setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) { 767 conn, _, err := hijacker.Hijack() 768 if err != nil { 769 return nil, nil, nil, err 770 } 771 772 // set raw mode 773 conn.Write([]byte{}) 774 775 if upgrade { 776 if multiplexed && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") { 777 contentType = types.MediaTypeMultiplexedStream 778 } 779 fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: "+contentType+"\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") 780 } else { 781 fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 782 } 783 784 closer := func() error { 785 httputils.CloseStreams(conn) 786 return nil 787 } 788 return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil 789 } 790 791 attachConfig := &backend.ContainerAttachConfig{ 792 GetStreams: setupStreams, 793 UseStdin: httputils.BoolValue(r, "stdin"), 794 UseStdout: httputils.BoolValue(r, "stdout"), 795 UseStderr: httputils.BoolValue(r, "stderr"), 796 Logs: httputils.BoolValue(r, "logs"), 797 Stream: httputils.BoolValue(r, "stream"), 798 DetachKeys: detachKeys, 799 MuxStreams: true, 800 } 801 802 if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil { 803 log.G(ctx).WithError(err).Errorf("Handler for %s %s returned error", r.Method, r.URL.Path) 804 // Remember to close stream if error happens 805 conn, _, errHijack := hijacker.Hijack() 806 if errHijack != nil { 807 log.G(ctx).WithError(err).Errorf("Handler for %s %s: unable to close stream; error when hijacking connection", r.Method, r.URL.Path) 808 } else { 809 statusCode := httpstatus.FromError(err) 810 statusText := http.StatusText(statusCode) 811 fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: %s\r\n\r\n%s\r\n", statusCode, statusText, contentType, err.Error()) 812 httputils.CloseStreams(conn) 813 } 814 } 815 return nil 816 } 817 818 func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 819 if err := httputils.ParseForm(r); err != nil { 820 return err 821 } 822 containerName := vars["name"] 823 824 var err error 825 detachKeys := r.FormValue("detachKeys") 826 827 done := make(chan struct{}) 828 started := make(chan struct{}) 829 830 version := httputils.VersionFromContext(ctx) 831 832 setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) { 833 wsChan := make(chan *websocket.Conn) 834 h := func(conn *websocket.Conn) { 835 wsChan <- conn 836 <-done 837 } 838 839 srv := websocket.Server{Handler: h, Handshake: nil} 840 go func() { 841 close(started) 842 srv.ServeHTTP(w, r) 843 }() 844 845 conn := <-wsChan 846 // In case version 1.28 and above, a binary frame will be sent. 847 // See 28176 for details. 848 if versions.GreaterThanOrEqualTo(version, "1.28") { 849 conn.PayloadType = websocket.BinaryFrame 850 } 851 return conn, conn, conn, nil 852 } 853 854 useStdin, useStdout, useStderr := true, true, true 855 if versions.GreaterThanOrEqualTo(version, "1.42") { 856 useStdin = httputils.BoolValue(r, "stdin") 857 useStdout = httputils.BoolValue(r, "stdout") 858 useStderr = httputils.BoolValue(r, "stderr") 859 } 860 861 attachConfig := &backend.ContainerAttachConfig{ 862 GetStreams: setupStreams, 863 UseStdin: useStdin, 864 UseStdout: useStdout, 865 UseStderr: useStderr, 866 Logs: httputils.BoolValue(r, "logs"), 867 Stream: httputils.BoolValue(r, "stream"), 868 DetachKeys: detachKeys, 869 MuxStreams: false, // never multiplex, as we rely on websocket to manage distinct streams 870 } 871 872 err = s.backend.ContainerAttach(containerName, attachConfig) 873 close(done) 874 select { 875 case <-started: 876 if err != nil { 877 log.G(ctx).Errorf("Error attaching websocket: %s", err) 878 } else { 879 log.G(ctx).Debug("websocket connection was closed by client") 880 } 881 return nil 882 default: 883 } 884 return err 885 } 886 887 func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 888 if err := httputils.ParseForm(r); err != nil { 889 return err 890 } 891 892 pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) 893 if err != nil { 894 return err 895 } 896 897 pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters) 898 if err != nil { 899 return err 900 } 901 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 902 }