github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration-cli/docker_api_containers_test.go (about) 1 package main 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "encoding/json" 8 "fmt" 9 "io" 10 "net/http" 11 "os" 12 "path/filepath" 13 "regexp" 14 "runtime" 15 "strings" 16 "testing" 17 "time" 18 19 "github.com/Prakhar-Agarwal-byte/moby/api/types" 20 "github.com/Prakhar-Agarwal-byte/moby/api/types/container" 21 "github.com/Prakhar-Agarwal-byte/moby/api/types/mount" 22 "github.com/Prakhar-Agarwal-byte/moby/api/types/network" 23 "github.com/Prakhar-Agarwal-byte/moby/api/types/versions" 24 "github.com/Prakhar-Agarwal-byte/moby/client" 25 dconfig "github.com/Prakhar-Agarwal-byte/moby/daemon/config" 26 "github.com/Prakhar-Agarwal-byte/moby/errdefs" 27 "github.com/Prakhar-Agarwal-byte/moby/integration-cli/cli" 28 "github.com/Prakhar-Agarwal-byte/moby/integration-cli/cli/build" 29 "github.com/Prakhar-Agarwal-byte/moby/pkg/stringid" 30 "github.com/Prakhar-Agarwal-byte/moby/testutil" 31 "github.com/Prakhar-Agarwal-byte/moby/testutil/request" 32 "github.com/Prakhar-Agarwal-byte/moby/volume" 33 "github.com/docker/go-connections/nat" 34 "gotest.tools/v3/assert" 35 is "gotest.tools/v3/assert/cmp" 36 "gotest.tools/v3/poll" 37 ) 38 39 func (s *DockerAPISuite) TestContainerAPIGetAll(c *testing.T) { 40 startCount := getContainerCount(c) 41 const name = "getall" 42 cli.DockerCmd(c, "run", "--name", name, "busybox", "true") 43 44 apiClient, err := client.NewClientWithOpts(client.FromEnv) 45 assert.NilError(c, err) 46 defer apiClient.Close() 47 48 ctx := testutil.GetContext(c) 49 containers, err := apiClient.ContainerList(ctx, container.ListOptions{ 50 All: true, 51 }) 52 assert.NilError(c, err) 53 assert.Equal(c, len(containers), startCount+1) 54 actual := containers[0].Names[0] 55 assert.Equal(c, actual, "/"+name) 56 } 57 58 // regression test for empty json field being omitted #13691 59 func (s *DockerAPISuite) TestContainerAPIGetJSONNoFieldsOmitted(c *testing.T) { 60 startCount := getContainerCount(c) 61 cli.DockerCmd(c, "run", "busybox", "true") 62 63 apiClient, err := client.NewClientWithOpts(client.FromEnv) 64 assert.NilError(c, err) 65 defer apiClient.Close() 66 67 options := container.ListOptions{ 68 All: true, 69 } 70 ctx := testutil.GetContext(c) 71 containers, err := apiClient.ContainerList(ctx, options) 72 assert.NilError(c, err) 73 assert.Equal(c, len(containers), startCount+1) 74 actual := fmt.Sprintf("%+v", containers[0]) 75 76 // empty Labels field triggered this bug, make sense to check for everything 77 // cause even Ports for instance can trigger this bug 78 // better safe than sorry.. 79 fields := []string{ 80 "ID", 81 "Names", 82 "Image", 83 "Command", 84 "Created", 85 "Ports", 86 "Labels", 87 "Status", 88 "NetworkSettings", 89 } 90 91 // decoding into types.Container do not work since it eventually unmarshal 92 // and empty field to an empty go map, so we just check for a string 93 for _, f := range fields { 94 if !strings.Contains(actual, f) { 95 c.Fatalf("Field %s is missing and it shouldn't", f) 96 } 97 } 98 } 99 100 func (s *DockerAPISuite) TestContainerAPIGetExport(c *testing.T) { 101 // Not supported on Windows as Windows does not support docker export 102 testRequires(c, DaemonIsLinux) 103 const name = "exportcontainer" 104 cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test") 105 106 apiClient, err := client.NewClientWithOpts(client.FromEnv) 107 assert.NilError(c, err) 108 defer apiClient.Close() 109 110 body, err := apiClient.ContainerExport(testutil.GetContext(c), name) 111 assert.NilError(c, err) 112 defer body.Close() 113 found := false 114 for tarReader := tar.NewReader(body); ; { 115 h, err := tarReader.Next() 116 if err != nil && err == io.EOF { 117 break 118 } 119 if h.Name == "test" { 120 found = true 121 break 122 } 123 } 124 assert.Assert(c, found, "The created test file has not been found in the exported image") 125 } 126 127 func (s *DockerAPISuite) TestContainerAPIGetChanges(c *testing.T) { 128 // Not supported on Windows as Windows does not support docker diff (/containers/name/changes) 129 testRequires(c, DaemonIsLinux) 130 const name = "changescontainer" 131 cli.DockerCmd(c, "run", "--name", name, "busybox", "rm", "/etc/passwd") 132 133 apiClient, err := client.NewClientWithOpts(client.FromEnv) 134 assert.NilError(c, err) 135 defer apiClient.Close() 136 137 changes, err := apiClient.ContainerDiff(testutil.GetContext(c), name) 138 assert.NilError(c, err) 139 140 // Check the changelog for removal of /etc/passwd 141 success := false 142 for _, elem := range changes { 143 if elem.Path == "/etc/passwd" && elem.Kind == 2 { 144 success = true 145 } 146 } 147 assert.Assert(c, success, "/etc/passwd has been removed but is not present in the diff") 148 } 149 150 func (s *DockerAPISuite) TestGetContainerStats(c *testing.T) { 151 const name = "statscontainer" 152 runSleepingContainer(c, "--name", name) 153 154 type b struct { 155 stats types.ContainerStats 156 err error 157 } 158 159 bc := make(chan b, 1) 160 go func() { 161 apiClient, err := client.NewClientWithOpts(client.FromEnv) 162 assert.NilError(c, err) 163 defer apiClient.Close() 164 165 stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, true) 166 assert.NilError(c, err) 167 bc <- b{stats, err} 168 }() 169 170 // allow some time to stream the stats from the container 171 time.Sleep(4 * time.Second) 172 cli.DockerCmd(c, "rm", "-f", name) 173 174 // collect the results from the stats stream or timeout and fail 175 // if the stream was not disconnected. 176 select { 177 case <-time.After(2 * time.Second): 178 c.Fatal("stream was not closed after container was removed") 179 case sr := <-bc: 180 dec := json.NewDecoder(sr.stats.Body) 181 defer sr.stats.Body.Close() 182 var s *types.Stats 183 // decode only one object from the stream 184 assert.NilError(c, dec.Decode(&s)) 185 } 186 } 187 188 func (s *DockerAPISuite) TestGetContainerStatsRmRunning(c *testing.T) { 189 id := runSleepingContainer(c) 190 191 buf := &ChannelBuffer{C: make(chan []byte, 1)} 192 defer buf.Close() 193 194 apiClient, err := client.NewClientWithOpts(client.FromEnv) 195 assert.NilError(c, err) 196 defer apiClient.Close() 197 198 stats, err := apiClient.ContainerStats(testutil.GetContext(c), id, true) 199 assert.NilError(c, err) 200 defer stats.Body.Close() 201 202 chErr := make(chan error, 1) 203 go func() { 204 _, err = io.Copy(buf, stats.Body) 205 chErr <- err 206 }() 207 208 b := make([]byte, 32) 209 // make sure we've got some stats 210 _, err = buf.ReadTimeout(b, 2*time.Second) 211 assert.NilError(c, err) 212 213 // Now remove without `-f` and make sure we are still pulling stats 214 _, _, err = dockerCmdWithError("rm", id) 215 assert.Assert(c, err != nil, "rm should have failed but didn't") 216 _, err = buf.ReadTimeout(b, 2*time.Second) 217 assert.NilError(c, err) 218 219 cli.DockerCmd(c, "rm", "-f", id) 220 assert.Assert(c, <-chErr == nil) 221 } 222 223 // ChannelBuffer holds a chan of byte array that can be populate in a goroutine. 224 type ChannelBuffer struct { 225 C chan []byte 226 } 227 228 // Write implements Writer. 229 func (c *ChannelBuffer) Write(b []byte) (int, error) { 230 c.C <- b 231 return len(b), nil 232 } 233 234 // Close closes the go channel. 235 func (c *ChannelBuffer) Close() error { 236 close(c.C) 237 return nil 238 } 239 240 // ReadTimeout reads the content of the channel in the specified byte array with 241 // the specified duration as timeout. 242 func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) { 243 select { 244 case b := <-c.C: 245 return copy(p[0:], b), nil 246 case <-time.After(n): 247 return -1, fmt.Errorf("timeout reading from channel") 248 } 249 } 250 251 // regression test for gh13421 252 // previous test was just checking one stat entry so it didn't fail (stats with 253 // stream false always return one stat) 254 func (s *DockerAPISuite) TestGetContainerStatsStream(c *testing.T) { 255 const name = "statscontainer" 256 runSleepingContainer(c, "--name", name) 257 258 type b struct { 259 stats types.ContainerStats 260 err error 261 } 262 263 bc := make(chan b, 1) 264 go func() { 265 apiClient, err := client.NewClientWithOpts(client.FromEnv) 266 assert.NilError(c, err) 267 defer apiClient.Close() 268 269 stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, true) 270 assert.NilError(c, err) 271 bc <- b{stats, err} 272 }() 273 274 // allow some time to stream the stats from the container 275 time.Sleep(4 * time.Second) 276 cli.DockerCmd(c, "rm", "-f", name) 277 278 // collect the results from the stats stream or timeout and fail 279 // if the stream was not disconnected. 280 select { 281 case <-time.After(2 * time.Second): 282 c.Fatal("stream was not closed after container was removed") 283 case sr := <-bc: 284 b, err := io.ReadAll(sr.stats.Body) 285 defer sr.stats.Body.Close() 286 assert.NilError(c, err) 287 s := string(b) 288 // count occurrences of "read" of types.Stats 289 if l := strings.Count(s, "read"); l < 2 { 290 c.Fatalf("Expected more than one stat streamed, got %d", l) 291 } 292 } 293 } 294 295 func (s *DockerAPISuite) TestGetContainerStatsNoStream(c *testing.T) { 296 const name = "statscontainer2" 297 runSleepingContainer(c, "--name", name) 298 299 type b struct { 300 stats types.ContainerStats 301 err error 302 } 303 304 bc := make(chan b, 1) 305 306 go func() { 307 apiClient, err := client.NewClientWithOpts(client.FromEnv) 308 assert.NilError(c, err) 309 defer apiClient.Close() 310 311 stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, false) 312 assert.NilError(c, err) 313 bc <- b{stats, err} 314 }() 315 316 // allow some time to stream the stats from the container 317 time.Sleep(4 * time.Second) 318 cli.DockerCmd(c, "rm", "-f", name) 319 320 // collect the results from the stats stream or timeout and fail 321 // if the stream was not disconnected. 322 select { 323 case <-time.After(2 * time.Second): 324 c.Fatal("stream was not closed after container was removed") 325 case sr := <-bc: 326 b, err := io.ReadAll(sr.stats.Body) 327 defer sr.stats.Body.Close() 328 assert.NilError(c, err) 329 s := string(b) 330 // count occurrences of `"read"` of types.Stats 331 assert.Assert(c, strings.Count(s, `"read"`) == 1, "Expected only one stat streamed, got %d", strings.Count(s, `"read"`)) 332 } 333 } 334 335 func (s *DockerAPISuite) TestGetStoppedContainerStats(c *testing.T) { 336 const name = "statscontainer3" 337 cli.DockerCmd(c, "create", "--name", name, "busybox", "ps") 338 339 chResp := make(chan error, 1) 340 341 // We expect an immediate response, but if it's not immediate, the test would hang, so put it in a goroutine 342 // below we'll check this on a timeout. 343 go func() { 344 apiClient, err := client.NewClientWithOpts(client.FromEnv) 345 assert.NilError(c, err) 346 defer apiClient.Close() 347 348 resp, err := apiClient.ContainerStats(testutil.GetContext(c), name, false) 349 assert.NilError(c, err) 350 defer resp.Body.Close() 351 chResp <- err 352 }() 353 354 select { 355 case err := <-chResp: 356 assert.NilError(c, err) 357 case <-time.After(10 * time.Second): 358 c.Fatal("timeout waiting for stats response for stopped container") 359 } 360 } 361 362 func (s *DockerAPISuite) TestContainerAPIPause(c *testing.T) { 363 // Problematic on Windows as Windows does not support pause 364 testRequires(c, DaemonIsLinux) 365 366 getPaused := func(c *testing.T) []string { 367 return strings.Fields(cli.DockerCmd(c, "ps", "-f", "status=paused", "-q", "-a").Combined()) 368 } 369 370 out := cli.DockerCmd(c, "run", "-d", "busybox", "sleep", "30").Combined() 371 ContainerID := strings.TrimSpace(out) 372 373 apiClient, err := client.NewClientWithOpts(client.FromEnv) 374 assert.NilError(c, err) 375 defer apiClient.Close() 376 377 err = apiClient.ContainerPause(testutil.GetContext(c), ContainerID) 378 assert.NilError(c, err) 379 380 pausedContainers := getPaused(c) 381 382 if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] { 383 c.Fatalf("there should be one paused container and not %d", len(pausedContainers)) 384 } 385 386 err = apiClient.ContainerUnpause(testutil.GetContext(c), ContainerID) 387 assert.NilError(c, err) 388 389 pausedContainers = getPaused(c) 390 assert.Equal(c, len(pausedContainers), 0, "There should be no paused container.") 391 } 392 393 func (s *DockerAPISuite) TestContainerAPITop(c *testing.T) { 394 testRequires(c, DaemonIsLinux) 395 out := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "top && true").Stdout() 396 id := strings.TrimSpace(out) 397 cli.WaitRun(c, id) 398 399 apiClient, err := client.NewClientWithOpts(client.FromEnv) 400 assert.NilError(c, err) 401 defer apiClient.Close() 402 403 // sort by comm[andline] to make sure order stays the same in case of PID rollover 404 top, err := apiClient.ContainerTop(testutil.GetContext(c), id, []string{"aux", "--sort=comm"}) 405 assert.NilError(c, err) 406 assert.Equal(c, len(top.Titles), 11, fmt.Sprintf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles)) 407 408 if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" { 409 c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) 410 } 411 assert.Equal(c, len(top.Processes), 2, fmt.Sprintf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes)) 412 assert.Equal(c, top.Processes[0][10], "/bin/sh -c top && true") 413 assert.Equal(c, top.Processes[1][10], "top") 414 } 415 416 func (s *DockerAPISuite) TestContainerAPITopWindows(c *testing.T) { 417 testRequires(c, DaemonIsWindows) 418 id := runSleepingContainer(c, "-d") 419 cli.WaitRun(c, id) 420 421 apiClient, err := client.NewClientWithOpts(client.FromEnv) 422 assert.NilError(c, err) 423 defer apiClient.Close() 424 425 top, err := apiClient.ContainerTop(testutil.GetContext(c), id, nil) 426 assert.NilError(c, err) 427 assert.Equal(c, len(top.Titles), 4, "expected 4 titles, found %d: %v", len(top.Titles), top.Titles) 428 429 if top.Titles[0] != "Name" || top.Titles[3] != "Private Working Set" { 430 c.Fatalf("expected `Name` at `Titles[0]` and `Private Working Set` at Titles[3]: %v", top.Titles) 431 } 432 assert.Assert(c, len(top.Processes) >= 2, "expected at least 2 processes, found %d: %v", len(top.Processes), top.Processes) 433 434 foundProcess := false 435 expectedProcess := "busybox.exe" 436 for _, process := range top.Processes { 437 if process[0] == expectedProcess { 438 foundProcess = true 439 break 440 } 441 } 442 443 assert.Assert(c, foundProcess, "expected to find %s: %v", expectedProcess, top.Processes) 444 } 445 446 func (s *DockerAPISuite) TestContainerAPICommit(c *testing.T) { 447 const cName = "testapicommit" 448 cli.DockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 449 450 apiClient, err := client.NewClientWithOpts(client.FromEnv) 451 assert.NilError(c, err) 452 defer apiClient.Close() 453 454 options := container.CommitOptions{ 455 Reference: "testcontainerapicommit:testtag", 456 } 457 458 img, err := apiClient.ContainerCommit(testutil.GetContext(c), cName, options) 459 assert.NilError(c, err) 460 461 cmd := inspectField(c, img.ID, "Config.Cmd") 462 assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd)) 463 464 // sanity check, make sure the image is what we think it is 465 cli.DockerCmd(c, "run", img.ID, "ls", "/test") 466 } 467 468 func (s *DockerAPISuite) TestContainerAPICommitWithLabelInConfig(c *testing.T) { 469 const cName = "testapicommitwithconfig" 470 cli.DockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 471 472 apiClient, err := client.NewClientWithOpts(client.FromEnv) 473 assert.NilError(c, err) 474 defer apiClient.Close() 475 476 config := container.Config{ 477 Labels: map[string]string{"key1": "value1", "key2": "value2"}, 478 } 479 480 options := container.CommitOptions{ 481 Reference: "testcontainerapicommitwithconfig", 482 Config: &config, 483 } 484 485 img, err := apiClient.ContainerCommit(testutil.GetContext(c), cName, options) 486 assert.NilError(c, err) 487 488 label1 := inspectFieldMap(c, img.ID, "Config.Labels", "key1") 489 assert.Equal(c, label1, "value1") 490 491 label2 := inspectFieldMap(c, img.ID, "Config.Labels", "key2") 492 assert.Equal(c, label2, "value2") 493 494 cmd := inspectField(c, img.ID, "Config.Cmd") 495 assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd)) 496 497 // sanity check, make sure the image is what we think it is 498 cli.DockerCmd(c, "run", img.ID, "ls", "/test") 499 } 500 501 func (s *DockerAPISuite) TestContainerAPIBadPort(c *testing.T) { 502 // TODO Windows to Windows CI - Port this test 503 testRequires(c, DaemonIsLinux) 504 505 config := container.Config{ 506 Image: "busybox", 507 Cmd: []string{"/bin/sh", "-c", "echo test"}, 508 } 509 510 hostConfig := container.HostConfig{ 511 PortBindings: nat.PortMap{ 512 "8080/tcp": []nat.PortBinding{ 513 { 514 HostIP: "", 515 HostPort: "aa80", 516 }, 517 }, 518 }, 519 } 520 521 apiClient, err := client.NewClientWithOpts(client.FromEnv) 522 assert.NilError(c, err) 523 defer apiClient.Close() 524 525 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 526 assert.ErrorContains(c, err, `invalid port specification: "aa80"`) 527 } 528 529 func (s *DockerAPISuite) TestContainerAPICreate(c *testing.T) { 530 config := container.Config{ 531 Image: "busybox", 532 Cmd: []string{"/bin/sh", "-c", "touch /test && ls /test"}, 533 } 534 535 apiClient, err := client.NewClientWithOpts(client.FromEnv) 536 assert.NilError(c, err) 537 defer apiClient.Close() 538 539 ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 540 assert.NilError(c, err) 541 542 out := cli.DockerCmd(c, "start", "-a", ctr.ID).Stdout() 543 assert.Equal(c, strings.TrimSpace(out), "/test") 544 } 545 546 func (s *DockerAPISuite) TestContainerAPICreateEmptyConfig(c *testing.T) { 547 apiClient, err := client.NewClientWithOpts(client.FromEnv) 548 assert.NilError(c, err) 549 defer apiClient.Close() 550 551 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &container.Config{}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 552 553 assert.ErrorContains(c, err, "no command specified") 554 } 555 556 func (s *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) { 557 // Windows does not support bridge 558 testRequires(c, DaemonIsLinux) 559 UtilCreateNetworkMode(c, "bridge") 560 } 561 562 func (s *DockerAPISuite) TestContainerAPICreateOtherNetworkModes(c *testing.T) { 563 // Windows does not support these network modes 564 testRequires(c, DaemonIsLinux, NotUserNamespace) 565 UtilCreateNetworkMode(c, "host") 566 UtilCreateNetworkMode(c, "container:web1") 567 } 568 569 func UtilCreateNetworkMode(c *testing.T, networkMode container.NetworkMode) { 570 config := container.Config{ 571 Image: "busybox", 572 } 573 574 hostConfig := container.HostConfig{ 575 NetworkMode: networkMode, 576 } 577 578 apiClient, err := client.NewClientWithOpts(client.FromEnv) 579 assert.NilError(c, err) 580 defer apiClient.Close() 581 582 ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 583 assert.NilError(c, err) 584 585 containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID) 586 assert.NilError(c, err) 587 588 assert.Equal(c, containerJSON.HostConfig.NetworkMode, networkMode, "Mismatched NetworkMode") 589 } 590 591 func (s *DockerAPISuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) { 592 // TODO Windows to Windows CI. The CpuShares part could be ported. 593 testRequires(c, DaemonIsLinux) 594 config := container.Config{ 595 Image: "busybox", 596 } 597 598 hostConfig := container.HostConfig{ 599 Resources: container.Resources{ 600 CPUShares: 512, 601 CpusetCpus: "0", 602 }, 603 } 604 605 apiClient, err := client.NewClientWithOpts(client.FromEnv) 606 assert.NilError(c, err) 607 defer apiClient.Close() 608 609 ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 610 assert.NilError(c, err) 611 612 containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID) 613 assert.NilError(c, err) 614 615 out := inspectField(c, containerJSON.ID, "HostConfig.CpuShares") 616 assert.Equal(c, out, "512") 617 618 outCpuset := inspectField(c, containerJSON.ID, "HostConfig.CpusetCpus") 619 assert.Equal(c, outCpuset, "0") 620 } 621 622 func (s *DockerAPISuite) TestContainerAPIVerifyHeader(c *testing.T) { 623 config := map[string]interface{}{ 624 "Image": "busybox", 625 } 626 627 create := func(ct string) (*http.Response, io.ReadCloser, error) { 628 jsonData := bytes.NewBuffer(nil) 629 assert.Assert(c, json.NewEncoder(jsonData).Encode(config) == nil) 630 return request.Post(testutil.GetContext(c), "/containers/create", request.RawContent(io.NopCloser(jsonData)), request.ContentType(ct)) 631 } 632 633 // Try with no content-type 634 res, body, err := create("") 635 assert.NilError(c, err) 636 // todo: we need to figure out a better way to compare between dockerd versions 637 // comparing between daemon API version is not precise. 638 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 639 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 640 } else { 641 assert.Assert(c, res.StatusCode != http.StatusOK) 642 } 643 body.Close() 644 645 // Try with wrong content-type 646 res, body, err = create("application/xml") 647 assert.NilError(c, err) 648 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 649 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 650 } else { 651 assert.Assert(c, res.StatusCode != http.StatusOK) 652 } 653 body.Close() 654 655 // now application/json 656 res, body, err = create("application/json") 657 assert.NilError(c, err) 658 assert.Equal(c, res.StatusCode, http.StatusCreated) 659 body.Close() 660 } 661 662 // Issue 14230. daemon should return 500 for invalid port syntax 663 func (s *DockerAPISuite) TestContainerAPIInvalidPortSyntax(c *testing.T) { 664 config := `{ 665 "Image": "busybox", 666 "HostConfig": { 667 "NetworkMode": "default", 668 "PortBindings": { 669 "19039;1230": [ 670 {} 671 ] 672 } 673 } 674 }` 675 676 res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON) 677 assert.NilError(c, err) 678 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 679 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 680 } else { 681 assert.Assert(c, res.StatusCode != http.StatusOK) 682 } 683 684 b, err := request.ReadBody(body) 685 assert.NilError(c, err) 686 assert.Assert(c, strings.Contains(string(b[:]), "invalid port")) 687 } 688 689 func (s *DockerAPISuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *testing.T) { 690 config := `{ 691 "Image": "busybox", 692 "HostConfig": { 693 "RestartPolicy": { 694 "Name": "something", 695 "MaximumRetryCount": 0 696 } 697 } 698 }` 699 700 res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON) 701 assert.NilError(c, err) 702 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 703 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 704 } else { 705 assert.Assert(c, res.StatusCode != http.StatusOK) 706 } 707 708 b, err := request.ReadBody(body) 709 assert.NilError(c, err) 710 assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy")) 711 } 712 713 func (s *DockerAPISuite) TestContainerAPIRestartPolicyRetryMismatch(c *testing.T) { 714 config := `{ 715 "Image": "busybox", 716 "HostConfig": { 717 "RestartPolicy": { 718 "Name": "always", 719 "MaximumRetryCount": 2 720 } 721 } 722 }` 723 724 res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON) 725 assert.NilError(c, err) 726 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 727 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 728 } else { 729 assert.Assert(c, res.StatusCode != http.StatusOK) 730 } 731 732 b, err := request.ReadBody(body) 733 assert.NilError(c, err) 734 assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy: maximum retry count can only be used with 'on-failure'")) 735 } 736 737 func (s *DockerAPISuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *testing.T) { 738 config := `{ 739 "Image": "busybox", 740 "HostConfig": { 741 "RestartPolicy": { 742 "Name": "on-failure", 743 "MaximumRetryCount": -2 744 } 745 } 746 }` 747 748 res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON) 749 assert.NilError(c, err) 750 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 751 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 752 } else { 753 assert.Assert(c, res.StatusCode != http.StatusOK) 754 } 755 756 b, err := request.ReadBody(body) 757 assert.NilError(c, err) 758 assert.Assert(c, strings.Contains(string(b[:]), "maximum retry count cannot be negative")) 759 } 760 761 func (s *DockerAPISuite) TestContainerAPIRestartPolicyDefaultRetryCount(c *testing.T) { 762 config := `{ 763 "Image": "busybox", 764 "HostConfig": { 765 "RestartPolicy": { 766 "Name": "on-failure", 767 "MaximumRetryCount": 0 768 } 769 } 770 }` 771 772 res, _, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON) 773 assert.NilError(c, err) 774 assert.Equal(c, res.StatusCode, http.StatusCreated) 775 } 776 777 // Issue 7941 - test to make sure a "null" in JSON is just ignored. 778 // W/o this fix a null in JSON would be parsed into a string var as "null" 779 func (s *DockerAPISuite) TestContainerAPIPostCreateNull(c *testing.T) { 780 config := `{ 781 "Hostname":"", 782 "Domainname":"", 783 "Memory":0, 784 "MemorySwap":0, 785 "CpuShares":0, 786 "Cpuset":null, 787 "AttachStdin":true, 788 "AttachStdout":true, 789 "AttachStderr":true, 790 "ExposedPorts":{}, 791 "Tty":true, 792 "OpenStdin":true, 793 "StdinOnce":true, 794 "Env":[], 795 "Cmd":"ls", 796 "Image":"busybox", 797 "Volumes":{}, 798 "WorkingDir":"", 799 "Entrypoint":null, 800 "NetworkDisabled":false, 801 "OnBuild":null}` 802 803 res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON) 804 assert.NilError(c, err) 805 assert.Equal(c, res.StatusCode, http.StatusCreated) 806 807 b, err := request.ReadBody(body) 808 assert.NilError(c, err) 809 type createResp struct { 810 ID string 811 } 812 var ctr createResp 813 assert.Assert(c, json.Unmarshal(b, &ctr) == nil) 814 out := inspectField(c, ctr.ID, "HostConfig.CpusetCpus") 815 assert.Equal(c, out, "") 816 817 outMemory := inspectField(c, ctr.ID, "HostConfig.Memory") 818 assert.Equal(c, outMemory, "0") 819 outMemorySwap := inspectField(c, ctr.ID, "HostConfig.MemorySwap") 820 assert.Equal(c, outMemorySwap, "0") 821 } 822 823 func (s *DockerAPISuite) TestCreateWithTooLowMemoryLimit(c *testing.T) { 824 // TODO Windows: Port once memory is supported 825 testRequires(c, DaemonIsLinux) 826 config := `{ 827 "Image": "busybox", 828 "Cmd": "ls", 829 "OpenStdin": true, 830 "CpuShares": 100, 831 "Memory": 524287 832 }` 833 834 res, body, err := request.Post(testutil.GetContext(c), "/containers/create", request.RawString(config), request.JSON) 835 assert.NilError(c, err) 836 b, err2 := request.ReadBody(body) 837 assert.Assert(c, err2 == nil) 838 839 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 840 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 841 } else { 842 assert.Assert(c, res.StatusCode != http.StatusOK) 843 } 844 assert.Assert(c, strings.Contains(string(b), "Minimum memory limit allowed is 6MB")) 845 } 846 847 func (s *DockerAPISuite) TestContainerAPIRename(c *testing.T) { 848 out := cli.DockerCmd(c, "run", "--name", "TestContainerAPIRename", "-d", "busybox", "sh").Stdout() 849 containerID := strings.TrimSpace(out) 850 const newName = "TestContainerAPIRenameNew" 851 852 apiClient, err := client.NewClientWithOpts(client.FromEnv) 853 assert.NilError(c, err) 854 defer apiClient.Close() 855 856 err = apiClient.ContainerRename(testutil.GetContext(c), containerID, newName) 857 assert.NilError(c, err) 858 859 name := inspectField(c, containerID, "Name") 860 assert.Equal(c, name, "/"+newName, "Failed to rename container") 861 } 862 863 func (s *DockerAPISuite) TestContainerAPIKill(c *testing.T) { 864 const name = "test-api-kill" 865 runSleepingContainer(c, "-i", "--name", name) 866 867 apiClient, err := client.NewClientWithOpts(client.FromEnv) 868 assert.NilError(c, err) 869 defer apiClient.Close() 870 871 err = apiClient.ContainerKill(testutil.GetContext(c), name, "SIGKILL") 872 assert.NilError(c, err) 873 874 state := inspectField(c, name, "State.Running") 875 assert.Equal(c, state, "false", fmt.Sprintf("got wrong State from container %s: %q", name, state)) 876 } 877 878 func (s *DockerAPISuite) TestContainerAPIRestart(c *testing.T) { 879 const name = "test-api-restart" 880 runSleepingContainer(c, "-di", "--name", name) 881 apiClient, err := client.NewClientWithOpts(client.FromEnv) 882 assert.NilError(c, err) 883 defer apiClient.Close() 884 885 timeout := 1 886 err = apiClient.ContainerRestart(testutil.GetContext(c), name, container.StopOptions{Timeout: &timeout}) 887 assert.NilError(c, err) 888 889 assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil) 890 } 891 892 func (s *DockerAPISuite) TestContainerAPIRestartNotimeoutParam(c *testing.T) { 893 const name = "test-api-restart-no-timeout-param" 894 id := runSleepingContainer(c, "-di", "--name", name) 895 cli.WaitRun(c, id) 896 897 apiClient, err := client.NewClientWithOpts(client.FromEnv) 898 assert.NilError(c, err) 899 defer apiClient.Close() 900 901 err = apiClient.ContainerRestart(testutil.GetContext(c), name, container.StopOptions{}) 902 assert.NilError(c, err) 903 904 assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil) 905 } 906 907 func (s *DockerAPISuite) TestContainerAPIStart(c *testing.T) { 908 const name = "testing-start" 909 config := container.Config{ 910 Image: "busybox", 911 Cmd: append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 912 OpenStdin: true, 913 } 914 915 apiClient, err := client.NewClientWithOpts(client.FromEnv) 916 assert.NilError(c, err) 917 defer apiClient.Close() 918 919 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name) 920 assert.NilError(c, err) 921 922 err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{}) 923 assert.NilError(c, err) 924 925 // second call to start should give 304 926 // maybe add ContainerStartWithRaw to test it 927 err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{}) 928 assert.NilError(c, err) 929 930 // TODO(tibor): figure out why this doesn't work on windows 931 } 932 933 func (s *DockerAPISuite) TestContainerAPIStop(c *testing.T) { 934 const name = "test-api-stop" 935 runSleepingContainer(c, "-i", "--name", name) 936 timeout := 30 937 938 apiClient, err := client.NewClientWithOpts(client.FromEnv) 939 assert.NilError(c, err) 940 defer apiClient.Close() 941 942 err = apiClient.ContainerStop(testutil.GetContext(c), name, container.StopOptions{ 943 Timeout: &timeout, 944 }) 945 assert.NilError(c, err) 946 assert.Assert(c, waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second) == nil) 947 948 // second call to start should give 304 949 // maybe add ContainerStartWithRaw to test it 950 err = apiClient.ContainerStop(testutil.GetContext(c), name, container.StopOptions{ 951 Timeout: &timeout, 952 }) 953 assert.NilError(c, err) 954 } 955 956 func (s *DockerAPISuite) TestContainerAPIWait(c *testing.T) { 957 const name = "test-api-wait" 958 959 sleepCmd := "/bin/sleep" 960 if testEnv.DaemonInfo.OSType == "windows" { 961 sleepCmd = "sleep" 962 } 963 cli.DockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2") 964 965 apiClient, err := client.NewClientWithOpts(client.FromEnv) 966 assert.NilError(c, err) 967 defer apiClient.Close() 968 969 waitResC, errC := apiClient.ContainerWait(testutil.GetContext(c), name, "") 970 971 select { 972 case err = <-errC: 973 assert.NilError(c, err) 974 case waitRes := <-waitResC: 975 assert.Equal(c, waitRes.StatusCode, int64(0)) 976 } 977 } 978 979 func (s *DockerAPISuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) { 980 const name = "test-container-api-copy" 981 cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 982 983 postData := types.CopyConfig{ 984 Resource: "/test.txt", 985 } 986 // no copy in client/ 987 res, _, err := request.Post(testutil.GetContext(c), "/containers/"+name+"/copy", request.JSONBody(postData)) 988 assert.NilError(c, err) 989 assert.Equal(c, res.StatusCode, http.StatusNotFound) 990 } 991 992 func (s *DockerAPISuite) TestContainerAPICopyPre124(c *testing.T) { 993 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 994 const name = "test-container-api-copy" 995 cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 996 997 postData := types.CopyConfig{ 998 Resource: "/test.txt", 999 } 1000 1001 res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1002 assert.NilError(c, err) 1003 assert.Equal(c, res.StatusCode, http.StatusOK) 1004 1005 found := false 1006 for tarReader := tar.NewReader(body); ; { 1007 h, err := tarReader.Next() 1008 if err != nil { 1009 if err == io.EOF { 1010 break 1011 } 1012 c.Fatal(err) 1013 } 1014 if h.Name == "test.txt" { 1015 found = true 1016 break 1017 } 1018 } 1019 assert.Assert(c, found) 1020 } 1021 1022 func (s *DockerAPISuite) TestContainerAPICopyResourcePathEmptyPre124(c *testing.T) { 1023 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1024 const name = "test-container-api-copy-resource-empty" 1025 cli.DockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1026 1027 postData := types.CopyConfig{ 1028 Resource: "", 1029 } 1030 1031 res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1032 assert.NilError(c, err) 1033 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 1034 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 1035 } else { 1036 assert.Assert(c, res.StatusCode != http.StatusOK) 1037 } 1038 b, err := request.ReadBody(body) 1039 assert.NilError(c, err) 1040 assert.Assert(c, is.Regexp("^Path cannot be empty\n$", string(b))) 1041 } 1042 1043 func (s *DockerAPISuite) TestContainerAPICopyResourcePathNotFoundPre124(c *testing.T) { 1044 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1045 const name = "test-container-api-copy-resource-not-found" 1046 cli.DockerCmd(c, "run", "--name", name, "busybox") 1047 1048 postData := types.CopyConfig{ 1049 Resource: "/notexist", 1050 } 1051 1052 res, body, err := request.Post(testutil.GetContext(c), "/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1053 assert.NilError(c, err) 1054 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 1055 assert.Equal(c, res.StatusCode, http.StatusInternalServerError) 1056 } else { 1057 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1058 } 1059 b, err := request.ReadBody(body) 1060 assert.NilError(c, err) 1061 assert.Assert(c, is.Regexp("^Could not find the file /notexist in container "+name+"\n$", string(b))) 1062 } 1063 1064 func (s *DockerAPISuite) TestContainerAPICopyContainerNotFoundPr124(c *testing.T) { 1065 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1066 postData := types.CopyConfig{ 1067 Resource: "/something", 1068 } 1069 1070 res, _, err := request.Post(testutil.GetContext(c), "/v1.23/containers/notexists/copy", request.JSONBody(postData)) 1071 assert.NilError(c, err) 1072 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1073 } 1074 1075 func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) { 1076 id := runSleepingContainer(c) 1077 cli.WaitRun(c, id) 1078 cli.DockerCmd(c, "stop", id) 1079 1080 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1081 assert.NilError(c, err) 1082 defer apiClient.Close() 1083 1084 err = apiClient.ContainerRemove(testutil.GetContext(c), id, container.RemoveOptions{}) 1085 assert.NilError(c, err) 1086 } 1087 1088 func (s *DockerAPISuite) TestContainerAPIDeleteNotExist(c *testing.T) { 1089 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1090 assert.NilError(c, err) 1091 defer apiClient.Close() 1092 1093 err = apiClient.ContainerRemove(testutil.GetContext(c), "doesnotexist", container.RemoveOptions{}) 1094 assert.ErrorContains(c, err, "No such container: doesnotexist") 1095 } 1096 1097 func (s *DockerAPISuite) TestContainerAPIDeleteForce(c *testing.T) { 1098 id := runSleepingContainer(c) 1099 cli.WaitRun(c, id) 1100 1101 removeOptions := container.RemoveOptions{ 1102 Force: true, 1103 } 1104 1105 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1106 assert.NilError(c, err) 1107 defer apiClient.Close() 1108 1109 err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions) 1110 assert.NilError(c, err) 1111 } 1112 1113 func (s *DockerAPISuite) TestContainerAPIDeleteRemoveLinks(c *testing.T) { 1114 // Windows does not support links 1115 testRequires(c, DaemonIsLinux) 1116 out := cli.DockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top").Stdout() 1117 id := strings.TrimSpace(out) 1118 cli.WaitRun(c, id) 1119 1120 out = cli.DockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top").Stdout() 1121 id2 := strings.TrimSpace(out) 1122 cli.WaitRun(c, id2) 1123 1124 links := inspectFieldJSON(c, id2, "HostConfig.Links") 1125 assert.Equal(c, links, `["/tlink1:/tlink2/tlink1"]`, "expected to have links between containers") 1126 1127 removeOptions := container.RemoveOptions{ 1128 RemoveLinks: true, 1129 } 1130 1131 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1132 assert.NilError(c, err) 1133 defer apiClient.Close() 1134 1135 err = apiClient.ContainerRemove(testutil.GetContext(c), "tlink2/tlink1", removeOptions) 1136 assert.NilError(c, err) 1137 1138 linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links") 1139 assert.Equal(c, linksPostRm, "null", "call to api deleteContainer links should have removed the specified links") 1140 } 1141 1142 func (s *DockerAPISuite) TestContainerAPIDeleteRemoveVolume(c *testing.T) { 1143 testRequires(c, testEnv.IsLocalDaemon) 1144 1145 vol := "/testvolume" 1146 if testEnv.DaemonInfo.OSType == "windows" { 1147 vol = `c:\testvolume` 1148 } 1149 1150 id := runSleepingContainer(c, "-v", vol) 1151 cli.WaitRun(c, id) 1152 1153 source, err := inspectMountSourceField(id, vol) 1154 assert.NilError(c, err) 1155 _, err = os.Stat(source) 1156 assert.NilError(c, err) 1157 1158 removeOptions := container.RemoveOptions{ 1159 Force: true, 1160 RemoveVolumes: true, 1161 } 1162 1163 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1164 assert.NilError(c, err) 1165 defer apiClient.Close() 1166 1167 err = apiClient.ContainerRemove(testutil.GetContext(c), id, removeOptions) 1168 assert.NilError(c, err) 1169 1170 _, err = os.Stat(source) 1171 assert.Assert(c, os.IsNotExist(err), "expected to get ErrNotExist error, got %v", err) 1172 } 1173 1174 // Regression test for https://github.com/Prakhar-Agarwal-byte/moby/issues/6231 1175 func (s *DockerAPISuite) TestContainerAPIChunkedEncoding(c *testing.T) { 1176 config := map[string]interface{}{ 1177 "Image": "busybox", 1178 "Cmd": append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 1179 "OpenStdin": true, 1180 } 1181 1182 resp, _, err := request.Post(testutil.GetContext(c), "/containers/create", request.JSONBody(config), request.With(func(req *http.Request) error { 1183 // This is a cheat to make the http request do chunked encoding 1184 // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite 1185 // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 1186 req.ContentLength = -1 1187 return nil 1188 })) 1189 assert.Assert(c, err == nil, "error creating container with chunked encoding") 1190 defer resp.Body.Close() 1191 assert.Equal(c, resp.StatusCode, http.StatusCreated) 1192 } 1193 1194 func (s *DockerAPISuite) TestContainerAPIPostContainerStop(c *testing.T) { 1195 containerID := runSleepingContainer(c) 1196 cli.WaitRun(c, containerID) 1197 1198 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1199 assert.NilError(c, err) 1200 defer apiClient.Close() 1201 1202 err = apiClient.ContainerStop(testutil.GetContext(c), containerID, container.StopOptions{}) 1203 assert.NilError(c, err) 1204 assert.Assert(c, waitInspect(containerID, "{{ .State.Running }}", "false", 60*time.Second) == nil) 1205 } 1206 1207 // #14170 1208 func (s *DockerAPISuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *testing.T) { 1209 config := container.Config{ 1210 Image: "busybox", 1211 Entrypoint: []string{"echo"}, 1212 Cmd: []string{"hello", "world"}, 1213 } 1214 1215 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1216 assert.NilError(c, err) 1217 defer apiClient.Close() 1218 1219 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest") 1220 assert.NilError(c, err) 1221 out := cli.DockerCmd(c, "start", "-a", "echotest").Combined() 1222 assert.Equal(c, strings.TrimSpace(out), "hello world") 1223 1224 config2 := struct { 1225 Image string 1226 Entrypoint string 1227 Cmd []string 1228 }{"busybox", "echo", []string{"hello", "world"}} 1229 _, _, err = request.Post(testutil.GetContext(c), "/containers/create?name=echotest2", request.JSONBody(config2)) 1230 assert.NilError(c, err) 1231 out = cli.DockerCmd(c, "start", "-a", "echotest2").Combined() 1232 assert.Equal(c, strings.TrimSpace(out), "hello world") 1233 } 1234 1235 // #14170 1236 func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T) { 1237 config := container.Config{ 1238 Image: "busybox", 1239 Cmd: []string{"echo", "hello", "world"}, 1240 } 1241 1242 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1243 assert.NilError(c, err) 1244 defer apiClient.Close() 1245 1246 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest") 1247 assert.NilError(c, err) 1248 out := cli.DockerCmd(c, "start", "-a", "echotest").Combined() 1249 assert.Equal(c, strings.TrimSpace(out), "hello world") 1250 1251 config2 := struct { 1252 Image string 1253 Entrypoint string 1254 Cmd string 1255 }{"busybox", "echo", "hello world"} 1256 _, _, err = request.Post(testutil.GetContext(c), "/containers/create?name=echotest2", request.JSONBody(config2)) 1257 assert.NilError(c, err) 1258 out = cli.DockerCmd(c, "start", "-a", "echotest2").Combined() 1259 assert.Equal(c, strings.TrimSpace(out), "hello world") 1260 } 1261 1262 // regression #14318 1263 // for backward compatibility testing with and without CAP_ prefix 1264 // and with upper and lowercase 1265 func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *testing.T) { 1266 // Windows doesn't support CapAdd/CapDrop 1267 testRequires(c, DaemonIsLinux) 1268 config := struct { 1269 Image string 1270 CapAdd string 1271 CapDrop string 1272 }{"busybox", "NET_ADMIN", "cap_sys_admin"} 1273 res, _, err := request.Post(testutil.GetContext(c), "/containers/create?name=capaddtest0", request.JSONBody(config)) 1274 assert.NilError(c, err) 1275 assert.Equal(c, res.StatusCode, http.StatusCreated) 1276 1277 config2 := container.Config{ 1278 Image: "busybox", 1279 } 1280 hostConfig := container.HostConfig{ 1281 CapAdd: []string{"net_admin", "SYS_ADMIN"}, 1282 CapDrop: []string{"SETGID", "CAP_SETPCAP"}, 1283 } 1284 1285 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1286 assert.NilError(c, err) 1287 defer apiClient.Close() 1288 1289 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config2, &hostConfig, &network.NetworkingConfig{}, nil, "capaddtest1") 1290 assert.NilError(c, err) 1291 } 1292 1293 // #14915 1294 func (s *DockerAPISuite) TestContainerAPICreateNoHostConfig118(c *testing.T) { 1295 testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later 1296 config := container.Config{ 1297 Image: "busybox", 1298 } 1299 1300 apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18")) 1301 assert.NilError(c, err) 1302 1303 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1304 assert.NilError(c, err) 1305 } 1306 1307 // Ensure an error occurs when you have a container read-only rootfs but you 1308 // extract an archive to a symlink in a writable volume which points to a 1309 // directory outside of the volume. 1310 func (s *DockerAPISuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *testing.T) { 1311 // Windows does not support read-only rootfs 1312 // Requires local volume mount bind. 1313 // --read-only + userns has remount issues 1314 testRequires(c, testEnv.IsLocalDaemon, NotUserNamespace, DaemonIsLinux) 1315 1316 testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-") 1317 defer os.RemoveAll(testVol) 1318 1319 makeTestContentInDir(c, testVol) 1320 1321 cID := makeTestContainer(c, testContainerOptions{ 1322 readOnly: true, 1323 volumes: defaultVolumes(testVol), // Our bind mount is at /vol2 1324 }) 1325 1326 // Attempt to extract to a symlink in the volume which points to a 1327 // directory outside the volume. This should cause an error because the 1328 // rootfs is read-only. 1329 apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.20")) 1330 assert.NilError(c, err) 1331 1332 err = apiClient.CopyToContainer(testutil.GetContext(c), cID, "/vol2/symlinkToAbsDir", nil, types.CopyToContainerOptions{}) 1333 assert.ErrorContains(c, err, "container rootfs is marked read-only") 1334 } 1335 1336 func (s *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T) { 1337 // Not supported on Windows 1338 testRequires(c, DaemonIsLinux) 1339 1340 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1341 assert.NilError(c, err) 1342 defer apiClient.Close() 1343 1344 config := container.Config{ 1345 Image: "busybox", 1346 } 1347 hostConfig1 := container.HostConfig{ 1348 Resources: container.Resources{ 1349 CpusetCpus: "1-42,,", 1350 }, 1351 } 1352 const name = "wrong-cpuset-cpus" 1353 1354 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig1, &network.NetworkingConfig{}, nil, name) 1355 expected := "Invalid value 1-42,, for cpuset cpus" 1356 assert.ErrorContains(c, err, expected) 1357 1358 hostConfig2 := container.HostConfig{ 1359 Resources: container.Resources{ 1360 CpusetMems: "42-3,1--", 1361 }, 1362 } 1363 const name2 = "wrong-cpuset-mems" 1364 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig2, &network.NetworkingConfig{}, nil, name2) 1365 expected = "Invalid value 42-3,1-- for cpuset mems" 1366 assert.ErrorContains(c, err, expected) 1367 } 1368 1369 func (s *DockerAPISuite) TestPostContainersCreateShmSizeNegative(c *testing.T) { 1370 // ShmSize is not supported on Windows 1371 testRequires(c, DaemonIsLinux) 1372 config := container.Config{ 1373 Image: "busybox", 1374 } 1375 hostConfig := container.HostConfig{ 1376 ShmSize: -1, 1377 } 1378 1379 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1380 assert.NilError(c, err) 1381 defer apiClient.Close() 1382 1383 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 1384 assert.ErrorContains(c, err, "SHM size can not be less than 0") 1385 } 1386 1387 func (s *DockerAPISuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testing.T) { 1388 // ShmSize is not supported on Windows 1389 testRequires(c, DaemonIsLinux) 1390 1391 config := container.Config{ 1392 Image: "busybox", 1393 Cmd: []string{"mount"}, 1394 } 1395 1396 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1397 assert.NilError(c, err) 1398 defer apiClient.Close() 1399 1400 ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1401 assert.NilError(c, err) 1402 1403 containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID) 1404 assert.NilError(c, err) 1405 1406 assert.Equal(c, containerJSON.HostConfig.ShmSize, dconfig.DefaultShmSize) 1407 1408 out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined() 1409 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1410 if !shmRegexp.MatchString(out) { 1411 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1412 } 1413 } 1414 1415 func (s *DockerAPISuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) { 1416 // ShmSize is not supported on Windows 1417 testRequires(c, DaemonIsLinux) 1418 config := container.Config{ 1419 Image: "busybox", 1420 Cmd: []string{"mount"}, 1421 } 1422 1423 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1424 assert.NilError(c, err) 1425 defer apiClient.Close() 1426 1427 ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1428 assert.NilError(c, err) 1429 1430 containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID) 1431 assert.NilError(c, err) 1432 1433 assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(67108864)) 1434 1435 out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined() 1436 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1437 if !shmRegexp.MatchString(out) { 1438 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1439 } 1440 } 1441 1442 func (s *DockerAPISuite) TestPostContainersCreateWithShmSize(c *testing.T) { 1443 // ShmSize is not supported on Windows 1444 testRequires(c, DaemonIsLinux) 1445 config := container.Config{ 1446 Image: "busybox", 1447 Cmd: []string{"mount"}, 1448 } 1449 1450 hostConfig := container.HostConfig{ 1451 ShmSize: 1073741824, 1452 } 1453 1454 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1455 assert.NilError(c, err) 1456 defer apiClient.Close() 1457 1458 ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 1459 assert.NilError(c, err) 1460 1461 containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID) 1462 assert.NilError(c, err) 1463 1464 assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(1073741824)) 1465 1466 out := cli.DockerCmd(c, "start", "-i", containerJSON.ID).Combined() 1467 shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`) 1468 if !shmRegex.MatchString(out) { 1469 c.Fatalf("Expected shm of 1GB in mount command, got %v", out) 1470 } 1471 } 1472 1473 func (s *DockerAPISuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *testing.T) { 1474 // Swappiness is not supported on Windows 1475 testRequires(c, DaemonIsLinux) 1476 config := container.Config{ 1477 Image: "busybox", 1478 } 1479 1480 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1481 assert.NilError(c, err) 1482 defer apiClient.Close() 1483 1484 ctr, err := apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1485 assert.NilError(c, err) 1486 1487 containerJSON, err := apiClient.ContainerInspect(testutil.GetContext(c), ctr.ID) 1488 assert.NilError(c, err) 1489 1490 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.31") { 1491 assert.Equal(c, *containerJSON.HostConfig.MemorySwappiness, int64(-1)) 1492 } else { 1493 assert.Assert(c, containerJSON.HostConfig.MemorySwappiness == nil) 1494 } 1495 } 1496 1497 // check validation is done daemon side and not only in cli 1498 func (s *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *testing.T) { 1499 // OomScoreAdj is not supported on Windows 1500 testRequires(c, DaemonIsLinux) 1501 1502 config := container.Config{ 1503 Image: "busybox", 1504 } 1505 1506 hostConfig := container.HostConfig{ 1507 OomScoreAdj: 1001, 1508 } 1509 1510 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1511 assert.NilError(c, err) 1512 defer apiClient.Close() 1513 1514 const name = "oomscoreadj-over" 1515 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name) 1516 1517 expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]" 1518 assert.ErrorContains(c, err, expected) 1519 1520 hostConfig = container.HostConfig{ 1521 OomScoreAdj: -1001, 1522 } 1523 1524 const name2 = "oomscoreadj-low" 1525 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, name2) 1526 1527 expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]" 1528 assert.ErrorContains(c, err, expected) 1529 } 1530 1531 // test case for #22210 where an empty container name caused panic. 1532 func (s *DockerAPISuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) { 1533 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1534 assert.NilError(c, err) 1535 defer apiClient.Close() 1536 1537 err = apiClient.ContainerRemove(testutil.GetContext(c), "", container.RemoveOptions{}) 1538 assert.Check(c, errdefs.IsNotFound(err)) 1539 } 1540 1541 func (s *DockerAPISuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) { 1542 // Problematic on Windows as Windows does not support stats 1543 testRequires(c, DaemonIsLinux) 1544 1545 const name = "testing-network-disabled" 1546 1547 config := container.Config{ 1548 Image: "busybox", 1549 Cmd: []string{"top"}, 1550 NetworkDisabled: true, 1551 } 1552 1553 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1554 assert.NilError(c, err) 1555 defer apiClient.Close() 1556 1557 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name) 1558 assert.NilError(c, err) 1559 1560 err = apiClient.ContainerStart(testutil.GetContext(c), name, container.StartOptions{}) 1561 assert.NilError(c, err) 1562 cli.WaitRun(c, name) 1563 1564 type b struct { 1565 stats types.ContainerStats 1566 err error 1567 } 1568 bc := make(chan b, 1) 1569 go func() { 1570 stats, err := apiClient.ContainerStats(testutil.GetContext(c), name, false) 1571 bc <- b{stats, err} 1572 }() 1573 1574 // allow some time to stream the stats from the container 1575 time.Sleep(4 * time.Second) 1576 cli.DockerCmd(c, "rm", "-f", name) 1577 1578 // collect the results from the stats stream or timeout and fail 1579 // if the stream was not disconnected. 1580 select { 1581 case <-time.After(2 * time.Second): 1582 c.Fatal("stream was not closed after container was removed") 1583 case sr := <-bc: 1584 assert.Assert(c, sr.err == nil) 1585 sr.stats.Body.Close() 1586 } 1587 } 1588 1589 func (s *DockerAPISuite) TestContainersAPICreateMountsValidation(c *testing.T) { 1590 type testCase struct { 1591 config container.Config 1592 hostConfig container.HostConfig 1593 msg string 1594 } 1595 1596 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1597 destPath := prefix + slash + "foo" 1598 notExistPath := prefix + slash + "notexist" 1599 1600 cases := []testCase{ 1601 { 1602 config: container.Config{ 1603 Image: "busybox", 1604 }, 1605 hostConfig: container.HostConfig{ 1606 Mounts: []mount.Mount{{ 1607 Type: "notreal", 1608 Target: destPath, 1609 }}, 1610 }, 1611 1612 msg: "mount type unknown", 1613 }, 1614 { 1615 config: container.Config{ 1616 Image: "busybox", 1617 }, 1618 hostConfig: container.HostConfig{ 1619 Mounts: []mount.Mount{{ 1620 Type: "bind", 1621 }}, 1622 }, 1623 msg: "Target must not be empty", 1624 }, 1625 { 1626 config: container.Config{ 1627 Image: "busybox", 1628 }, 1629 hostConfig: container.HostConfig{ 1630 Mounts: []mount.Mount{{ 1631 Type: "bind", 1632 Target: destPath, 1633 }}, 1634 }, 1635 msg: "Source must not be empty", 1636 }, 1637 { 1638 config: container.Config{ 1639 Image: "busybox", 1640 }, 1641 hostConfig: container.HostConfig{ 1642 Mounts: []mount.Mount{{ 1643 Type: "bind", 1644 Source: notExistPath, 1645 Target: destPath, 1646 }}, 1647 }, 1648 msg: "source path does not exist", 1649 // FIXME(vdemeester) fails into e2e, migrate to integration/container anyway 1650 // msg: "source path does not exist: " + notExistPath, 1651 }, 1652 { 1653 config: container.Config{ 1654 Image: "busybox", 1655 }, 1656 hostConfig: container.HostConfig{ 1657 Mounts: []mount.Mount{{ 1658 Type: "volume", 1659 }}, 1660 }, 1661 msg: "Target must not be empty", 1662 }, 1663 { 1664 config: container.Config{ 1665 Image: "busybox", 1666 }, 1667 hostConfig: container.HostConfig{ 1668 Mounts: []mount.Mount{{ 1669 Type: "volume", 1670 Source: "hello", 1671 Target: destPath, 1672 }}, 1673 }, 1674 msg: "", 1675 }, 1676 { 1677 config: container.Config{ 1678 Image: "busybox", 1679 }, 1680 hostConfig: container.HostConfig{ 1681 Mounts: []mount.Mount{{ 1682 Type: "volume", 1683 Source: "hello2", 1684 Target: destPath, 1685 VolumeOptions: &mount.VolumeOptions{ 1686 DriverConfig: &mount.Driver{ 1687 Name: "local", 1688 }, 1689 }, 1690 }}, 1691 }, 1692 msg: "", 1693 }, 1694 } 1695 1696 if testEnv.IsLocalDaemon() { 1697 tmpDir, err := os.MkdirTemp("", "test-mounts-api") 1698 assert.NilError(c, err) 1699 defer os.RemoveAll(tmpDir) 1700 cases = append(cases, []testCase{ 1701 { 1702 config: container.Config{ 1703 Image: "busybox", 1704 }, 1705 hostConfig: container.HostConfig{ 1706 Mounts: []mount.Mount{{ 1707 Type: "bind", 1708 Source: tmpDir, 1709 Target: destPath, 1710 }}, 1711 }, 1712 msg: "", 1713 }, 1714 { 1715 config: container.Config{ 1716 Image: "busybox", 1717 }, 1718 hostConfig: container.HostConfig{ 1719 Mounts: []mount.Mount{{ 1720 Type: "bind", 1721 Source: tmpDir, 1722 Target: destPath, 1723 VolumeOptions: &mount.VolumeOptions{}, 1724 }}, 1725 }, 1726 msg: "VolumeOptions must not be specified", 1727 }, 1728 }...) 1729 } 1730 1731 if DaemonIsWindows() { 1732 cases = append(cases, []testCase{ 1733 { 1734 config: container.Config{ 1735 Image: "busybox", 1736 }, 1737 hostConfig: container.HostConfig{ 1738 Mounts: []mount.Mount{ 1739 { 1740 Type: "volume", 1741 Source: "not-supported-on-windows", 1742 Target: destPath, 1743 VolumeOptions: &mount.VolumeOptions{ 1744 DriverConfig: &mount.Driver{ 1745 Name: "local", 1746 Options: map[string]string{"type": "tmpfs"}, 1747 }, 1748 }, 1749 }, 1750 }, 1751 }, 1752 msg: `options are not supported on this platform`, 1753 }, 1754 }...) 1755 } 1756 1757 if DaemonIsLinux() { 1758 cases = append(cases, []testCase{ 1759 { 1760 config: container.Config{ 1761 Image: "busybox", 1762 }, 1763 hostConfig: container.HostConfig{ 1764 Mounts: []mount.Mount{ 1765 { 1766 Type: "volume", 1767 Source: "missing-device-opt", 1768 Target: destPath, 1769 VolumeOptions: &mount.VolumeOptions{ 1770 DriverConfig: &mount.Driver{ 1771 Name: "local", 1772 Options: map[string]string{"foobar": "foobaz"}, 1773 }, 1774 }, 1775 }, 1776 }, 1777 }, 1778 msg: `invalid option: "foobar"`, 1779 }, 1780 { 1781 config: container.Config{ 1782 Image: "busybox", 1783 }, 1784 hostConfig: container.HostConfig{ 1785 Mounts: []mount.Mount{ 1786 { 1787 Type: "volume", 1788 Source: "missing-device-opt", 1789 Target: destPath, 1790 VolumeOptions: &mount.VolumeOptions{ 1791 DriverConfig: &mount.Driver{ 1792 Name: "local", 1793 Options: map[string]string{"type": "tmpfs"}, 1794 }, 1795 }, 1796 }, 1797 }, 1798 }, 1799 msg: `missing required option: "device"`, 1800 }, 1801 { 1802 config: container.Config{ 1803 Image: "busybox", 1804 }, 1805 hostConfig: container.HostConfig{ 1806 Mounts: []mount.Mount{ 1807 { 1808 Type: "volume", 1809 Source: "missing-type-opt", 1810 Target: destPath, 1811 VolumeOptions: &mount.VolumeOptions{ 1812 DriverConfig: &mount.Driver{ 1813 Name: "local", 1814 Options: map[string]string{"device": "tmpfs"}, 1815 }, 1816 }, 1817 }, 1818 }, 1819 }, 1820 msg: `missing required option: "type"`, 1821 }, 1822 { 1823 config: container.Config{ 1824 Image: "busybox", 1825 }, 1826 hostConfig: container.HostConfig{ 1827 Mounts: []mount.Mount{ 1828 { 1829 Type: "volume", 1830 Source: "hello4", 1831 Target: destPath, 1832 VolumeOptions: &mount.VolumeOptions{ 1833 DriverConfig: &mount.Driver{ 1834 Name: "local", 1835 Options: map[string]string{"o": "size=1", "type": "tmpfs", "device": "tmpfs"}, 1836 }, 1837 }, 1838 }, 1839 }, 1840 }, 1841 msg: "", 1842 }, 1843 { 1844 config: container.Config{ 1845 Image: "busybox", 1846 }, 1847 hostConfig: container.HostConfig{ 1848 Mounts: []mount.Mount{{ 1849 Type: "tmpfs", 1850 Target: destPath, 1851 }}, 1852 }, 1853 msg: "", 1854 }, 1855 { 1856 config: container.Config{ 1857 Image: "busybox", 1858 }, 1859 hostConfig: container.HostConfig{ 1860 Mounts: []mount.Mount{{ 1861 Type: "tmpfs", 1862 Target: destPath, 1863 TmpfsOptions: &mount.TmpfsOptions{ 1864 SizeBytes: 4096 * 1024, 1865 Mode: 0o700, 1866 }, 1867 }}, 1868 }, 1869 msg: "", 1870 }, 1871 { 1872 config: container.Config{ 1873 Image: "busybox", 1874 }, 1875 hostConfig: container.HostConfig{ 1876 Mounts: []mount.Mount{{ 1877 Type: "tmpfs", 1878 Source: "/shouldnotbespecified", 1879 Target: destPath, 1880 }}, 1881 }, 1882 msg: "Source must not be specified", 1883 }, 1884 }...) 1885 } 1886 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1887 assert.NilError(c, err) 1888 defer apiClient.Close() 1889 1890 // TODO add checks for statuscode returned by API 1891 for i, x := range cases { 1892 x := x 1893 c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) { 1894 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &x.config, &x.hostConfig, &network.NetworkingConfig{}, nil, "") 1895 if len(x.msg) > 0 { 1896 assert.ErrorContains(c, err, x.msg, "%v", cases[i].config) 1897 } else { 1898 assert.NilError(c, err) 1899 } 1900 }) 1901 } 1902 } 1903 1904 func (s *DockerAPISuite) TestContainerAPICreateMountsBindRead(c *testing.T) { 1905 testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon) 1906 // also with data in the host side 1907 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1908 destPath := prefix + slash + "foo" 1909 tmpDir, err := os.MkdirTemp("", "test-mounts-api-bind") 1910 assert.NilError(c, err) 1911 defer os.RemoveAll(tmpDir) 1912 err = os.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0o666) 1913 assert.NilError(c, err) 1914 config := container.Config{ 1915 Image: "busybox", 1916 Cmd: []string{"/bin/sh", "-c", "cat /foo/bar"}, 1917 } 1918 hostConfig := container.HostConfig{ 1919 Mounts: []mount.Mount{ 1920 {Type: "bind", Source: tmpDir, Target: destPath}, 1921 }, 1922 } 1923 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1924 assert.NilError(c, err) 1925 defer apiClient.Close() 1926 1927 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "test") 1928 assert.NilError(c, err) 1929 1930 out := cli.DockerCmd(c, "start", "-a", "test").Combined() 1931 assert.Equal(c, out, "hello") 1932 } 1933 1934 // Test Mounts comes out as expected for the MountPoint 1935 func (s *DockerAPISuite) TestContainersAPICreateMountsCreate(c *testing.T) { 1936 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1937 destPath := prefix + slash + "foo" 1938 1939 var testImg string 1940 if testEnv.DaemonInfo.OSType != "windows" { 1941 testImg = "test-mount-config" 1942 buildImageSuccessfully(c, testImg, build.WithDockerfile(` 1943 FROM busybox 1944 RUN mkdir `+destPath+` && touch `+destPath+slash+`bar 1945 CMD cat `+destPath+slash+`bar 1946 `)) 1947 } else { 1948 testImg = "busybox" 1949 } 1950 1951 type testCase struct { 1952 spec mount.Mount 1953 expected types.MountPoint 1954 } 1955 1956 var selinuxSharedLabel string 1957 // this test label was added after a bug fix in 1.32, thus add requirements min API >= 1.32 1958 // for the sake of making test pass in earlier versions 1959 // bug fixed in https://github.com/moby/moby/pull/34684 1960 if !versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 1961 if runtime.GOOS == "linux" { 1962 selinuxSharedLabel = "z" 1963 } 1964 } 1965 1966 cases := []testCase{ 1967 // use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest 1968 // Validation of the actual `Mount` struct is done in another test is not needed here 1969 { 1970 spec: mount.Mount{Type: "volume", Target: destPath}, 1971 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 1972 }, 1973 { 1974 spec: mount.Mount{Type: "volume", Target: destPath + slash}, 1975 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 1976 }, 1977 { 1978 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test1"}, 1979 expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 1980 }, 1981 { 1982 spec: mount.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"}, 1983 expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 1984 }, 1985 { 1986 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mount.VolumeOptions{DriverConfig: &mount.Driver{Name: volume.DefaultDriverName}}}, 1987 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 1988 }, 1989 } 1990 1991 if testEnv.IsLocalDaemon() { 1992 // setup temp dir for testing binds 1993 tmpDir1, err := os.MkdirTemp("", "test-mounts-api-1") 1994 assert.NilError(c, err) 1995 defer os.RemoveAll(tmpDir1) 1996 cases = append(cases, []testCase{ 1997 { 1998 spec: mount.Mount{ 1999 Type: "bind", 2000 Source: tmpDir1, 2001 Target: destPath, 2002 }, 2003 expected: types.MountPoint{ 2004 Type: "bind", 2005 RW: true, 2006 Destination: destPath, 2007 Source: tmpDir1, 2008 }, 2009 }, 2010 { 2011 spec: mount.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true}, 2012 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1}, 2013 }, 2014 }...) 2015 2016 // for modes only supported on Linux 2017 if DaemonIsLinux() { 2018 tmpDir3, err := os.MkdirTemp("", "test-mounts-api-3") 2019 assert.NilError(c, err) 2020 defer os.RemoveAll(tmpDir3) 2021 2022 assert.Assert(c, mountWrapper(tmpDir3, tmpDir3, "none", "bind,shared") == nil) 2023 2024 cases = append(cases, []testCase{ 2025 { 2026 spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath}, 2027 expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3}, 2028 }, 2029 { 2030 spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true}, 2031 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3}, 2032 }, 2033 { 2034 spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mount.BindOptions{Propagation: "shared"}}, 2035 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"}, 2036 }, 2037 }...) 2038 } 2039 } 2040 2041 if testEnv.DaemonInfo.OSType != "windows" { // Windows does not support volume populate 2042 cases = append(cases, []testCase{ 2043 { 2044 spec: mount.Mount{Type: "volume", Target: destPath, VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2045 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2046 }, 2047 { 2048 spec: mount.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2049 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2050 }, 2051 { 2052 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2053 expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2054 }, 2055 { 2056 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2057 expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 2058 }, 2059 }...) 2060 } 2061 2062 ctx := testutil.GetContext(c) 2063 apiclient := testEnv.APIClient() 2064 for i, x := range cases { 2065 x := x 2066 c.Run(fmt.Sprintf("%d config: %v", i, x.spec), func(c *testing.T) { 2067 ctr, err := apiclient.ContainerCreate( 2068 ctx, 2069 &container.Config{Image: testImg}, 2070 &container.HostConfig{Mounts: []mount.Mount{x.spec}}, 2071 &network.NetworkingConfig{}, 2072 nil, 2073 "") 2074 assert.NilError(c, err) 2075 2076 containerInspect, err := apiclient.ContainerInspect(ctx, ctr.ID) 2077 assert.NilError(c, err) 2078 mps := containerInspect.Mounts 2079 assert.Assert(c, is.Len(mps, 1)) 2080 mountPoint := mps[0] 2081 2082 if x.expected.Source != "" { 2083 assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source)) 2084 } 2085 if x.expected.Name != "" { 2086 assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name)) 2087 } 2088 if x.expected.Driver != "" { 2089 assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver)) 2090 } 2091 if x.expected.Propagation != "" { 2092 assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation)) 2093 } 2094 assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW)) 2095 assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type)) 2096 assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode)) 2097 assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination)) 2098 2099 err = apiclient.ContainerStart(ctx, ctr.ID, container.StartOptions{}) 2100 assert.NilError(c, err) 2101 poll.WaitOn(c, containerExit(ctx, apiclient, ctr.ID), poll.WithDelay(time.Second)) 2102 2103 err = apiclient.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{ 2104 RemoveVolumes: true, 2105 Force: true, 2106 }) 2107 assert.NilError(c, err) 2108 2109 switch { 2110 // Named volumes still exist after the container is removed 2111 case x.spec.Type == "volume" && len(x.spec.Source) > 0: 2112 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2113 assert.NilError(c, err) 2114 2115 // Bind mounts are never removed with the container 2116 case x.spec.Type == "bind": 2117 2118 // anonymous volumes are removed 2119 default: 2120 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2121 assert.Check(c, is.ErrorType(err, errdefs.IsNotFound)) 2122 } 2123 }) 2124 } 2125 } 2126 2127 func containerExit(ctx context.Context, apiclient client.APIClient, name string) func(poll.LogT) poll.Result { 2128 return func(logT poll.LogT) poll.Result { 2129 ctr, err := apiclient.ContainerInspect(ctx, name) 2130 if err != nil { 2131 return poll.Error(err) 2132 } 2133 switch ctr.State.Status { 2134 case "created", "running": 2135 return poll.Continue("container %s is %s, waiting for exit", name, ctr.State.Status) 2136 } 2137 return poll.Success() 2138 } 2139 } 2140 2141 func (s *DockerAPISuite) TestContainersAPICreateMountsTmpfs(c *testing.T) { 2142 testRequires(c, DaemonIsLinux) 2143 type testCase struct { 2144 cfg mount.Mount 2145 expectedOptions []string 2146 } 2147 target := "/foo" 2148 cases := []testCase{ 2149 { 2150 cfg: mount.Mount{ 2151 Type: "tmpfs", 2152 Target: target, 2153 }, 2154 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}, 2155 }, 2156 { 2157 cfg: mount.Mount{ 2158 Type: "tmpfs", 2159 Target: target, 2160 TmpfsOptions: &mount.TmpfsOptions{ 2161 SizeBytes: 4096 * 1024, Mode: 0o700, 2162 }, 2163 }, 2164 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"}, 2165 }, 2166 } 2167 2168 apiClient, err := client.NewClientWithOpts(client.FromEnv) 2169 assert.NilError(c, err) 2170 defer apiClient.Close() 2171 2172 config := container.Config{ 2173 Image: "busybox", 2174 Cmd: []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)}, 2175 } 2176 for i, x := range cases { 2177 cName := fmt.Sprintf("test-tmpfs-%d", i) 2178 hostConfig := container.HostConfig{ 2179 Mounts: []mount.Mount{x.cfg}, 2180 } 2181 2182 _, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, cName) 2183 assert.NilError(c, err) 2184 out := cli.DockerCmd(c, "start", "-a", cName).Combined() 2185 for _, option := range x.expectedOptions { 2186 assert.Assert(c, strings.Contains(out, option)) 2187 } 2188 } 2189 } 2190 2191 // Regression test for #33334 2192 // Makes sure that when a container which has a custom stop signal + restart=always 2193 // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled. 2194 func (s *DockerAPISuite) TestContainerKillCustomStopSignal(c *testing.T) { 2195 id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always")) 2196 res, _, err := request.Post(testutil.GetContext(c), "/containers/"+id+"/kill") 2197 assert.NilError(c, err) 2198 defer res.Body.Close() 2199 2200 b, err := io.ReadAll(res.Body) 2201 assert.NilError(c, err) 2202 assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b)) 2203 err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second) 2204 assert.NilError(c, err) 2205 }