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