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