github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/moby/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 "gotest.tools/v3/assert" 34 is "gotest.tools/v3/assert/cmp" 35 "gotest.tools/v3/poll" 36 ) 37 38 func (s *DockerSuite) TestContainerAPIGetAll(c *testing.T) { 39 startCount := getContainerCount(c) 40 name := "getall" 41 dockerCmd(c, "run", "--name", name, "busybox", "true") 42 43 cli, err := client.NewClientWithOpts(client.FromEnv) 44 assert.NilError(c, err) 45 defer cli.Close() 46 47 options := types.ContainerListOptions{ 48 All: true, 49 } 50 containers, err := cli.ContainerList(context.Background(), options) 51 assert.NilError(c, err) 52 assert.Equal(c, len(containers), startCount+1) 53 actual := containers[0].Names[0] 54 assert.Equal(c, actual, "/"+name) 55 } 56 57 // regression test for empty json field being omitted #13691 58 func (s *DockerSuite) TestContainerAPIGetJSONNoFieldsOmitted(c *testing.T) { 59 startCount := getContainerCount(c) 60 dockerCmd(c, "run", "busybox", "true") 61 62 cli, err := client.NewClientWithOpts(client.FromEnv) 63 assert.NilError(c, err) 64 defer cli.Close() 65 66 options := types.ContainerListOptions{ 67 All: true, 68 } 69 containers, err := cli.ContainerList(context.Background(), options) 70 assert.NilError(c, err) 71 assert.Equal(c, len(containers), startCount+1) 72 actual := fmt.Sprintf("%+v", containers[0]) 73 74 // empty Labels field triggered this bug, make sense to check for everything 75 // cause even Ports for instance can trigger this bug 76 // better safe than sorry.. 77 fields := []string{ 78 "ID", 79 "Names", 80 "Image", 81 "Command", 82 "Created", 83 "Ports", 84 "Labels", 85 "Status", 86 "NetworkSettings", 87 } 88 89 // decoding into types.Container do not work since it eventually unmarshal 90 // and empty field to an empty go map, so we just check for a string 91 for _, f := range fields { 92 if !strings.Contains(actual, f) { 93 c.Fatalf("Field %s is missing and it shouldn't", f) 94 } 95 } 96 } 97 98 func (s *DockerSuite) TestContainerAPIGetExport(c *testing.T) { 99 // Not supported on Windows as Windows does not support docker export 100 testRequires(c, DaemonIsLinux) 101 name := "exportcontainer" 102 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test") 103 104 cli, err := client.NewClientWithOpts(client.FromEnv) 105 assert.NilError(c, err) 106 defer cli.Close() 107 108 body, err := cli.ContainerExport(context.Background(), name) 109 assert.NilError(c, err) 110 defer body.Close() 111 found := false 112 for tarReader := tar.NewReader(body); ; { 113 h, err := tarReader.Next() 114 if err != nil && err == io.EOF { 115 break 116 } 117 if h.Name == "test" { 118 found = true 119 break 120 } 121 } 122 assert.Assert(c, found, "The created test file has not been found in the exported image") 123 } 124 125 func (s *DockerSuite) TestContainerAPIGetChanges(c *testing.T) { 126 // Not supported on Windows as Windows does not support docker diff (/containers/name/changes) 127 testRequires(c, DaemonIsLinux) 128 name := "changescontainer" 129 dockerCmd(c, "run", "--name", name, "busybox", "rm", "/etc/passwd") 130 131 cli, err := client.NewClientWithOpts(client.FromEnv) 132 assert.NilError(c, err) 133 defer cli.Close() 134 135 changes, err := cli.ContainerDiff(context.Background(), name) 136 assert.NilError(c, err) 137 138 // Check the changelog for removal of /etc/passwd 139 success := false 140 for _, elem := range changes { 141 if elem.Path == "/etc/passwd" && elem.Kind == 2 { 142 success = true 143 } 144 } 145 assert.Assert(c, success, "/etc/passwd has been removed but is not present in the diff") 146 } 147 148 func (s *DockerSuite) TestGetContainerStats(c *testing.T) { 149 var ( 150 name = "statscontainer" 151 ) 152 runSleepingContainer(c, "--name", name) 153 154 type b struct { 155 stats types.ContainerStats 156 err error 157 } 158 159 bc := make(chan b, 1) 160 go func() { 161 cli, err := client.NewClientWithOpts(client.FromEnv) 162 assert.NilError(c, err) 163 defer cli.Close() 164 165 stats, err := cli.ContainerStats(context.Background(), name, true) 166 assert.NilError(c, err) 167 bc <- b{stats, err} 168 }() 169 170 // allow some time to stream the stats from the container 171 time.Sleep(4 * time.Second) 172 dockerCmd(c, "rm", "-f", name) 173 174 // collect the results from the stats stream or timeout and fail 175 // if the stream was not disconnected. 176 select { 177 case <-time.After(2 * time.Second): 178 c.Fatal("stream was not closed after container was removed") 179 case sr := <-bc: 180 dec := json.NewDecoder(sr.stats.Body) 181 defer sr.stats.Body.Close() 182 var s *types.Stats 183 // decode only one object from the stream 184 assert.NilError(c, dec.Decode(&s)) 185 } 186 } 187 188 func (s *DockerSuite) TestGetContainerStatsRmRunning(c *testing.T) { 189 out := runSleepingContainer(c) 190 id := strings.TrimSpace(out) 191 192 buf := &ChannelBuffer{C: make(chan []byte, 1)} 193 defer buf.Close() 194 195 cli, err := client.NewClientWithOpts(client.FromEnv) 196 assert.NilError(c, err) 197 defer cli.Close() 198 199 stats, err := cli.ContainerStats(context.Background(), id, true) 200 assert.NilError(c, err) 201 defer stats.Body.Close() 202 203 chErr := make(chan error, 1) 204 go func() { 205 _, err = io.Copy(buf, stats.Body) 206 chErr <- err 207 }() 208 209 b := make([]byte, 32) 210 // make sure we've got some stats 211 _, err = buf.ReadTimeout(b, 2*time.Second) 212 assert.NilError(c, err) 213 214 // Now remove without `-f` and make sure we are still pulling stats 215 _, _, err = dockerCmdWithError("rm", id) 216 assert.Assert(c, err != nil, "rm should have failed but didn't") 217 _, err = buf.ReadTimeout(b, 2*time.Second) 218 assert.NilError(c, err) 219 220 dockerCmd(c, "rm", "-f", id) 221 assert.Assert(c, <-chErr == nil) 222 } 223 224 // ChannelBuffer holds a chan of byte array that can be populate in a goroutine. 225 type ChannelBuffer struct { 226 C chan []byte 227 } 228 229 // Write implements Writer. 230 func (c *ChannelBuffer) Write(b []byte) (int, error) { 231 c.C <- b 232 return len(b), nil 233 } 234 235 // Close closes the go channel. 236 func (c *ChannelBuffer) Close() error { 237 close(c.C) 238 return nil 239 } 240 241 // ReadTimeout reads the content of the channel in the specified byte array with 242 // the specified duration as timeout. 243 func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) { 244 select { 245 case b := <-c.C: 246 return copy(p[0:], b), nil 247 case <-time.After(n): 248 return -1, fmt.Errorf("timeout reading from channel") 249 } 250 } 251 252 // regression test for gh13421 253 // previous test was just checking one stat entry so it didn't fail (stats with 254 // stream false always return one stat) 255 func (s *DockerSuite) TestGetContainerStatsStream(c *testing.T) { 256 name := "statscontainer" 257 runSleepingContainer(c, "--name", name) 258 259 type b struct { 260 stats types.ContainerStats 261 err error 262 } 263 264 bc := make(chan b, 1) 265 go func() { 266 cli, err := client.NewClientWithOpts(client.FromEnv) 267 assert.NilError(c, err) 268 defer cli.Close() 269 270 stats, err := cli.ContainerStats(context.Background(), name, true) 271 assert.NilError(c, err) 272 bc <- b{stats, err} 273 }() 274 275 // allow some time to stream the stats from the container 276 time.Sleep(4 * time.Second) 277 dockerCmd(c, "rm", "-f", name) 278 279 // collect the results from the stats stream or timeout and fail 280 // if the stream was not disconnected. 281 select { 282 case <-time.After(2 * time.Second): 283 c.Fatal("stream was not closed after container was removed") 284 case sr := <-bc: 285 b, err := ioutil.ReadAll(sr.stats.Body) 286 defer sr.stats.Body.Close() 287 assert.NilError(c, err) 288 s := string(b) 289 // count occurrences of "read" of types.Stats 290 if l := strings.Count(s, "read"); l < 2 { 291 c.Fatalf("Expected more than one stat streamed, got %d", l) 292 } 293 } 294 } 295 296 func (s *DockerSuite) TestGetContainerStatsNoStream(c *testing.T) { 297 name := "statscontainer" 298 runSleepingContainer(c, "--name", name) 299 300 type b struct { 301 stats types.ContainerStats 302 err error 303 } 304 305 bc := make(chan b, 1) 306 307 go func() { 308 cli, err := client.NewClientWithOpts(client.FromEnv) 309 assert.NilError(c, err) 310 defer cli.Close() 311 312 stats, err := cli.ContainerStats(context.Background(), name, false) 313 assert.NilError(c, err) 314 bc <- b{stats, err} 315 }() 316 317 // allow some time to stream the stats from the container 318 time.Sleep(4 * time.Second) 319 dockerCmd(c, "rm", "-f", name) 320 321 // collect the results from the stats stream or timeout and fail 322 // if the stream was not disconnected. 323 select { 324 case <-time.After(2 * time.Second): 325 c.Fatal("stream was not closed after container was removed") 326 case sr := <-bc: 327 b, err := ioutil.ReadAll(sr.stats.Body) 328 defer sr.stats.Body.Close() 329 assert.NilError(c, err) 330 s := string(b) 331 // count occurrences of `"read"` of types.Stats 332 assert.Assert(c, strings.Count(s, `"read"`) == 1, "Expected only one stat streamed, got %d", strings.Count(s, `"read"`)) 333 } 334 } 335 336 func (s *DockerSuite) TestGetStoppedContainerStats(c *testing.T) { 337 name := "statscontainer" 338 dockerCmd(c, "create", "--name", name, "busybox", "ps") 339 340 chResp := make(chan error, 1) 341 342 // We expect an immediate response, but if it's not immediate, the test would hang, so put it in a goroutine 343 // below we'll check this on a timeout. 344 go func() { 345 cli, err := client.NewClientWithOpts(client.FromEnv) 346 assert.NilError(c, err) 347 defer cli.Close() 348 349 resp, err := cli.ContainerStats(context.Background(), name, false) 350 assert.NilError(c, err) 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 && true") 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 && true") 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{}, nil, "") 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{}, nil, "") 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{}, nil, "") 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, nil, "") 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{}, nil, "") 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{}, nil, "") 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 6MB")) 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{}, nil, 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{}, nil, "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{}, nil, "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{}, nil, "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{}, nil, "") 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{}, nil, 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{}, nil, 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{}, nil, "") 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{}, nil, "") 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{}, nil, "") 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{}, nil, "") 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{}, nil, "") 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{}, nil, 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{}, nil, 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{}, nil, 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1921 assert.NilError(c, err) 1922 defer apiClient.Close() 1923 1924 // TODO add checks for statuscode returned by API 1925 for i, x := range cases { 1926 x := x 1927 c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) { 1928 _, err = apiClient.ContainerCreate(context.Background(), &x.config, &x.hostConfig, &networktypes.NetworkingConfig{}, nil, "") 1929 if len(x.msg) > 0 { 1930 assert.ErrorContains(c, err, x.msg, "%v", cases[i].config) 1931 } else { 1932 assert.NilError(c, err) 1933 } 1934 }) 1935 } 1936 } 1937 1938 func (s *DockerSuite) TestContainerAPICreateMountsBindRead(c *testing.T) { 1939 testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon) 1940 // also with data in the host side 1941 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1942 destPath := prefix + slash + "foo" 1943 tmpDir, err := ioutil.TempDir("", "test-mounts-api-bind") 1944 assert.NilError(c, err) 1945 defer os.RemoveAll(tmpDir) 1946 err = ioutil.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0666) 1947 assert.NilError(c, err) 1948 config := containertypes.Config{ 1949 Image: "busybox", 1950 Cmd: []string{"/bin/sh", "-c", "cat /foo/bar"}, 1951 } 1952 hostConfig := containertypes.HostConfig{ 1953 Mounts: []mounttypes.Mount{ 1954 {Type: "bind", Source: tmpDir, Target: destPath}, 1955 }, 1956 } 1957 cli, err := client.NewClientWithOpts(client.FromEnv) 1958 assert.NilError(c, err) 1959 defer cli.Close() 1960 1961 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "test") 1962 assert.NilError(c, err) 1963 1964 out, _ := dockerCmd(c, "start", "-a", "test") 1965 assert.Equal(c, out, "hello") 1966 } 1967 1968 // Test Mounts comes out as expected for the MountPoint 1969 func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *testing.T) { 1970 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1971 destPath := prefix + slash + "foo" 1972 1973 var ( 1974 testImg string 1975 ) 1976 if testEnv.OSType != "windows" { 1977 testImg = "test-mount-config" 1978 buildImageSuccessfully(c, testImg, build.WithDockerfile(` 1979 FROM busybox 1980 RUN mkdir `+destPath+` && touch `+destPath+slash+`bar 1981 CMD cat `+destPath+slash+`bar 1982 `)) 1983 } else { 1984 testImg = "busybox" 1985 } 1986 1987 type testCase struct { 1988 spec mounttypes.Mount 1989 expected types.MountPoint 1990 } 1991 1992 var selinuxSharedLabel string 1993 // this test label was added after a bug fix in 1.32, thus add requirements min API >= 1.32 1994 // for the sake of making test pass in earlier versions 1995 // bug fixed in https://github.com/moby/moby/pull/34684 1996 if !versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 1997 if "linux" == "linux" { 1998 selinuxSharedLabel = "z" 1999 } 2000 } 2001 2002 cases := []testCase{ 2003 // use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest 2004 // Validation of the actual `Mount` struct is done in another test is not needed here 2005 { 2006 spec: mounttypes.Mount{Type: "volume", Target: destPath}, 2007 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2008 }, 2009 { 2010 spec: mounttypes.Mount{Type: "volume", Target: destPath + slash}, 2011 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2012 }, 2013 { 2014 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test1"}, 2015 expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2016 }, 2017 { 2018 spec: mounttypes.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"}, 2019 expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 2020 }, 2021 { 2022 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: volume.DefaultDriverName}}}, 2023 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2024 }, 2025 } 2026 2027 if testEnv.IsLocalDaemon() { 2028 // setup temp dir for testing binds 2029 tmpDir1, err := ioutil.TempDir("", "test-mounts-api-1") 2030 assert.NilError(c, err) 2031 defer os.RemoveAll(tmpDir1) 2032 cases = append(cases, []testCase{ 2033 { 2034 spec: mounttypes.Mount{ 2035 Type: "bind", 2036 Source: tmpDir1, 2037 Target: destPath, 2038 }, 2039 expected: types.MountPoint{ 2040 Type: "bind", 2041 RW: true, 2042 Destination: destPath, 2043 Source: tmpDir1, 2044 }, 2045 }, 2046 { 2047 spec: mounttypes.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true}, 2048 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1}, 2049 }, 2050 }...) 2051 2052 // for modes only supported on Linux 2053 if DaemonIsLinux() { 2054 tmpDir3, err := ioutils.TempDir("", "test-mounts-api-3") 2055 assert.NilError(c, err) 2056 defer os.RemoveAll(tmpDir3) 2057 2058 assert.Assert(c, mountWrapper(tmpDir3, tmpDir3, "none", "bind,shared") == nil) 2059 2060 cases = append(cases, []testCase{ 2061 { 2062 spec: mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath}, 2063 expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3}, 2064 }, 2065 { 2066 spec: mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true}, 2067 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3}, 2068 }, 2069 { 2070 spec: mounttypes.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mounttypes.BindOptions{Propagation: "shared"}}, 2071 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"}, 2072 }, 2073 }...) 2074 } 2075 } 2076 2077 if testEnv.OSType != "windows" { // Windows does not support volume populate 2078 cases = append(cases, []testCase{ 2079 { 2080 spec: mounttypes.Mount{Type: "volume", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2081 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2082 }, 2083 { 2084 spec: mounttypes.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2085 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2086 }, 2087 { 2088 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2089 expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2090 }, 2091 { 2092 spec: mounttypes.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}}, 2093 expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 2094 }, 2095 }...) 2096 } 2097 2098 ctx := context.Background() 2099 apiclient := testEnv.APIClient() 2100 for i, x := range cases { 2101 x := x 2102 c.Run(fmt.Sprintf("%d config: %v", i, x.spec), func(c *testing.T) { 2103 container, err := apiclient.ContainerCreate( 2104 ctx, 2105 &containertypes.Config{Image: testImg}, 2106 &containertypes.HostConfig{Mounts: []mounttypes.Mount{x.spec}}, 2107 &networktypes.NetworkingConfig{}, 2108 nil, 2109 "") 2110 assert.NilError(c, err) 2111 2112 containerInspect, err := apiclient.ContainerInspect(ctx, container.ID) 2113 assert.NilError(c, err) 2114 mps := containerInspect.Mounts 2115 assert.Assert(c, is.Len(mps, 1)) 2116 mountPoint := mps[0] 2117 2118 if x.expected.Source != "" { 2119 assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source)) 2120 } 2121 if x.expected.Name != "" { 2122 assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name)) 2123 } 2124 if x.expected.Driver != "" { 2125 assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver)) 2126 } 2127 if x.expected.Propagation != "" { 2128 assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation)) 2129 } 2130 assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW)) 2131 assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type)) 2132 assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode)) 2133 assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination)) 2134 2135 err = apiclient.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}) 2136 assert.NilError(c, err) 2137 poll.WaitOn(c, containerExit(apiclient, container.ID), poll.WithDelay(time.Second)) 2138 2139 err = apiclient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{ 2140 RemoveVolumes: true, 2141 Force: true, 2142 }) 2143 assert.NilError(c, err) 2144 2145 switch { 2146 2147 // Named volumes still exist after the container is removed 2148 case x.spec.Type == "volume" && len(x.spec.Source) > 0: 2149 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2150 assert.NilError(c, err) 2151 2152 // Bind mounts are never removed with the container 2153 case x.spec.Type == "bind": 2154 2155 // anonymous volumes are removed 2156 default: 2157 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2158 assert.Check(c, client.IsErrNotFound(err)) 2159 } 2160 }) 2161 } 2162 } 2163 2164 func containerExit(apiclient client.APIClient, name string) func(poll.LogT) poll.Result { 2165 return func(logT poll.LogT) poll.Result { 2166 container, err := apiclient.ContainerInspect(context.Background(), name) 2167 if err != nil { 2168 return poll.Error(err) 2169 } 2170 switch container.State.Status { 2171 case "created", "running": 2172 return poll.Continue("container %s is %s, waiting for exit", name, container.State.Status) 2173 } 2174 return poll.Success() 2175 } 2176 } 2177 2178 func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *testing.T) { 2179 testRequires(c, DaemonIsLinux) 2180 type testCase struct { 2181 cfg mounttypes.Mount 2182 expectedOptions []string 2183 } 2184 target := "/foo" 2185 cases := []testCase{ 2186 { 2187 cfg: mounttypes.Mount{ 2188 Type: "tmpfs", 2189 Target: target}, 2190 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}, 2191 }, 2192 { 2193 cfg: mounttypes.Mount{ 2194 Type: "tmpfs", 2195 Target: target, 2196 TmpfsOptions: &mounttypes.TmpfsOptions{ 2197 SizeBytes: 4096 * 1024, Mode: 0700}}, 2198 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"}, 2199 }, 2200 } 2201 2202 cli, err := client.NewClientWithOpts(client.FromEnv) 2203 assert.NilError(c, err) 2204 defer cli.Close() 2205 2206 config := containertypes.Config{ 2207 Image: "busybox", 2208 Cmd: []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)}, 2209 } 2210 for i, x := range cases { 2211 cName := fmt.Sprintf("test-tmpfs-%d", i) 2212 hostConfig := containertypes.HostConfig{ 2213 Mounts: []mounttypes.Mount{x.cfg}, 2214 } 2215 2216 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, cName) 2217 assert.NilError(c, err) 2218 out, _ := dockerCmd(c, "start", "-a", cName) 2219 for _, option := range x.expectedOptions { 2220 assert.Assert(c, strings.Contains(out, option)) 2221 } 2222 } 2223 } 2224 2225 // Regression test for #33334 2226 // Makes sure that when a container which has a custom stop signal + restart=always 2227 // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled. 2228 func (s *DockerSuite) TestContainerKillCustomStopSignal(c *testing.T) { 2229 id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always")) 2230 res, _, err := request.Post("/containers/" + id + "/kill") 2231 assert.NilError(c, err) 2232 defer res.Body.Close() 2233 2234 b, err := ioutil.ReadAll(res.Body) 2235 assert.NilError(c, err) 2236 assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b)) 2237 err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second) 2238 assert.NilError(c, err) 2239 }