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