github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/integration-cli/docker_api_containers_test.go (about) 1 package main 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "encoding/json" 8 "fmt" 9 "io" 10 "net/http" 11 "os" 12 "path/filepath" 13 "regexp" 14 "runtime" 15 "strings" 16 "testing" 17 "time" 18 19 "github.com/docker/docker/api/types" 20 "github.com/docker/docker/api/types/container" 21 "github.com/docker/docker/api/types/mount" 22 "github.com/docker/docker/api/types/network" 23 "github.com/docker/docker/api/types/versions" 24 "github.com/docker/docker/client" 25 dconfig "github.com/docker/docker/daemon/config" 26 "github.com/docker/docker/errdefs" 27 "github.com/docker/docker/integration-cli/cli" 28 "github.com/docker/docker/integration-cli/cli/build" 29 "github.com/docker/docker/pkg/ioutils" 30 "github.com/docker/docker/pkg/stringid" 31 "github.com/docker/docker/testutil/request" 32 "github.com/docker/docker/volume" 33 "github.com/docker/go-connections/nat" 34 "gotest.tools/v3/assert" 35 is "gotest.tools/v3/assert/cmp" 36 "gotest.tools/v3/poll" 37 ) 38 39 func (s *DockerAPISuite) TestContainerAPIGetAll(c *testing.T) { 40 startCount := getContainerCount(c) 41 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 := io.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 *DockerAPISuite) 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 := io.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 *DockerAPISuite) TestGetStoppedContainerStats(c *testing.T) { 338 name := "statscontainer" 339 dockerCmd(c, "create", "--name", name, "busybox", "ps") 340 341 chResp := make(chan error, 1) 342 343 // We expect an immediate response, but if it's not immediate, the test would hang, so put it in a goroutine 344 // below we'll check this on a timeout. 345 go func() { 346 cli, err := client.NewClientWithOpts(client.FromEnv) 347 assert.NilError(c, err) 348 defer cli.Close() 349 350 resp, err := cli.ContainerStats(context.Background(), name, false) 351 assert.NilError(c, err) 352 defer resp.Body.Close() 353 chResp <- err 354 }() 355 356 select { 357 case err := <-chResp: 358 assert.NilError(c, err) 359 case <-time.After(10 * time.Second): 360 c.Fatal("timeout waiting for stats response for stopped container") 361 } 362 } 363 364 func (s *DockerAPISuite) TestContainerAPIPause(c *testing.T) { 365 // Problematic on Windows as Windows does not support pause 366 testRequires(c, DaemonIsLinux) 367 368 getPaused := func(c *testing.T) []string { 369 return strings.Fields(cli.DockerCmd(c, "ps", "-f", "status=paused", "-q", "-a").Combined()) 370 } 371 372 out := cli.DockerCmd(c, "run", "-d", "busybox", "sleep", "30").Combined() 373 ContainerID := strings.TrimSpace(out) 374 375 cli, err := client.NewClientWithOpts(client.FromEnv) 376 assert.NilError(c, err) 377 defer cli.Close() 378 379 err = cli.ContainerPause(context.Background(), ContainerID) 380 assert.NilError(c, err) 381 382 pausedContainers := getPaused(c) 383 384 if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] { 385 c.Fatalf("there should be one paused container and not %d", len(pausedContainers)) 386 } 387 388 err = cli.ContainerUnpause(context.Background(), ContainerID) 389 assert.NilError(c, err) 390 391 pausedContainers = getPaused(c) 392 assert.Equal(c, len(pausedContainers), 0, "There should be no paused container.") 393 } 394 395 func (s *DockerAPISuite) TestContainerAPITop(c *testing.T) { 396 testRequires(c, DaemonIsLinux) 397 out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "top && true") 398 id := strings.TrimSpace(out) 399 assert.NilError(c, waitRun(id)) 400 401 cli, err := client.NewClientWithOpts(client.FromEnv) 402 assert.NilError(c, err) 403 defer cli.Close() 404 405 // sort by comm[andline] to make sure order stays the same in case of PID rollover 406 top, err := cli.ContainerTop(context.Background(), id, []string{"aux", "--sort=comm"}) 407 assert.NilError(c, err) 408 assert.Equal(c, len(top.Titles), 11, fmt.Sprintf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles)) 409 410 if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" { 411 c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) 412 } 413 assert.Equal(c, len(top.Processes), 2, fmt.Sprintf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes)) 414 assert.Equal(c, top.Processes[0][10], "/bin/sh -c top && true") 415 assert.Equal(c, top.Processes[1][10], "top") 416 } 417 418 func (s *DockerAPISuite) TestContainerAPITopWindows(c *testing.T) { 419 testRequires(c, DaemonIsWindows) 420 out := runSleepingContainer(c, "-d") 421 id := strings.TrimSpace(out) 422 assert.NilError(c, waitRun(id)) 423 424 cli, err := client.NewClientWithOpts(client.FromEnv) 425 assert.NilError(c, err) 426 defer cli.Close() 427 428 top, err := cli.ContainerTop(context.Background(), id, nil) 429 assert.NilError(c, err) 430 assert.Equal(c, len(top.Titles), 4, "expected 4 titles, found %d: %v", len(top.Titles), top.Titles) 431 432 if top.Titles[0] != "Name" || top.Titles[3] != "Private Working Set" { 433 c.Fatalf("expected `Name` at `Titles[0]` and `Private Working Set` at Titles[3]: %v", top.Titles) 434 } 435 assert.Assert(c, len(top.Processes) >= 2, "expected at least 2 processes, found %d: %v", len(top.Processes), top.Processes) 436 437 foundProcess := false 438 expectedProcess := "busybox.exe" 439 for _, process := range top.Processes { 440 if process[0] == expectedProcess { 441 foundProcess = true 442 break 443 } 444 } 445 446 assert.Assert(c, foundProcess, "expected to find %s: %v", expectedProcess, top.Processes) 447 } 448 449 func (s *DockerAPISuite) TestContainerAPICommit(c *testing.T) { 450 cName := "testapicommit" 451 dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 452 453 cli, err := client.NewClientWithOpts(client.FromEnv) 454 assert.NilError(c, err) 455 defer cli.Close() 456 457 options := types.ContainerCommitOptions{ 458 Reference: "testcontainerapicommit:testtag", 459 } 460 461 img, err := cli.ContainerCommit(context.Background(), cName, options) 462 assert.NilError(c, err) 463 464 cmd := inspectField(c, img.ID, "Config.Cmd") 465 assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd)) 466 467 // sanity check, make sure the image is what we think it is 468 dockerCmd(c, "run", img.ID, "ls", "/test") 469 } 470 471 func (s *DockerAPISuite) TestContainerAPICommitWithLabelInConfig(c *testing.T) { 472 cName := "testapicommitwithconfig" 473 dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 474 475 cli, err := client.NewClientWithOpts(client.FromEnv) 476 assert.NilError(c, err) 477 defer cli.Close() 478 479 config := container.Config{ 480 Labels: map[string]string{"key1": "value1", "key2": "value2"}} 481 482 options := types.ContainerCommitOptions{ 483 Reference: "testcontainerapicommitwithconfig", 484 Config: &config, 485 } 486 487 img, err := cli.ContainerCommit(context.Background(), cName, options) 488 assert.NilError(c, err) 489 490 label1 := inspectFieldMap(c, img.ID, "Config.Labels", "key1") 491 assert.Equal(c, label1, "value1") 492 493 label2 := inspectFieldMap(c, img.ID, "Config.Labels", "key2") 494 assert.Equal(c, label2, "value2") 495 496 cmd := inspectField(c, img.ID, "Config.Cmd") 497 assert.Equal(c, cmd, "[/bin/sh -c touch /test]", fmt.Sprintf("got wrong Cmd from commit: %q", cmd)) 498 499 // sanity check, make sure the image is what we think it is 500 dockerCmd(c, "run", img.ID, "ls", "/test") 501 } 502 503 func (s *DockerAPISuite) TestContainerAPIBadPort(c *testing.T) { 504 // TODO Windows to Windows CI - Port this test 505 testRequires(c, DaemonIsLinux) 506 507 config := container.Config{ 508 Image: "busybox", 509 Cmd: []string{"/bin/sh", "-c", "echo test"}, 510 } 511 512 hostConfig := container.HostConfig{ 513 PortBindings: nat.PortMap{ 514 "8080/tcp": []nat.PortBinding{ 515 { 516 HostIP: "", 517 HostPort: "aa80"}, 518 }, 519 }, 520 } 521 522 cli, err := client.NewClientWithOpts(client.FromEnv) 523 assert.NilError(c, err) 524 defer cli.Close() 525 526 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 527 assert.ErrorContains(c, err, `invalid port specification: "aa80"`) 528 } 529 530 func (s *DockerAPISuite) TestContainerAPICreate(c *testing.T) { 531 config := container.Config{ 532 Image: "busybox", 533 Cmd: []string{"/bin/sh", "-c", "touch /test && ls /test"}, 534 } 535 536 cli, err := client.NewClientWithOpts(client.FromEnv) 537 assert.NilError(c, err) 538 defer cli.Close() 539 540 container, err := cli.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 541 assert.NilError(c, err) 542 543 out, _ := dockerCmd(c, "start", "-a", container.ID) 544 assert.Equal(c, strings.TrimSpace(out), "/test") 545 } 546 547 func (s *DockerAPISuite) TestContainerAPICreateEmptyConfig(c *testing.T) { 548 cli, err := client.NewClientWithOpts(client.FromEnv) 549 assert.NilError(c, err) 550 defer cli.Close() 551 552 _, err = cli.ContainerCreate(context.Background(), &container.Config{}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 553 554 expected := "No command specified" 555 assert.ErrorContains(c, err, expected) 556 } 557 558 func (s *DockerAPISuite) TestContainerAPICreateMultipleNetworksConfig(c *testing.T) { 559 // Container creation must fail if client specified configurations for more than one network 560 config := container.Config{ 561 Image: "busybox", 562 } 563 564 networkingConfig := network.NetworkingConfig{ 565 EndpointsConfig: map[string]*network.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, &container.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 *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) { 586 // Windows does not support bridge 587 testRequires(c, DaemonIsLinux) 588 UtilCreateNetworkMode(c, "bridge") 589 } 590 591 func (s *DockerAPISuite) 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 container.NetworkMode) { 599 config := container.Config{ 600 Image: "busybox", 601 } 602 603 hostConfig := container.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, &network.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 *DockerAPISuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) { 621 // TODO Windows to Windows CI. The CpuShares part could be ported. 622 testRequires(c, DaemonIsLinux) 623 config := container.Config{ 624 Image: "busybox", 625 } 626 627 hostConfig := container.HostConfig{ 628 Resources: container.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, &network.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 *DockerAPISuite) 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(io.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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 *DockerAPISuite) 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 916 err = cli.ContainerRestart(context.Background(), name, container.StopOptions{Timeout: &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 *DockerAPISuite) 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, container.StopOptions{}) 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 *DockerAPISuite) TestContainerAPIStart(c *testing.T) { 939 name := "testing-start" 940 config := container.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, &container.HostConfig{}, &network.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 *DockerAPISuite) TestContainerAPIStop(c *testing.T) { 965 name := "test-api-stop" 966 runSleepingContainer(c, "-i", "--name", name) 967 timeout := 30 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, container.StopOptions{ 974 Timeout: &timeout, 975 }) 976 assert.NilError(c, err) 977 assert.Assert(c, waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second) == nil) 978 979 // second call to start should give 304 980 // maybe add ContainerStartWithRaw to test it 981 err = cli.ContainerStop(context.Background(), name, container.StopOptions{ 982 Timeout: &timeout, 983 }) 984 assert.NilError(c, err) 985 } 986 987 func (s *DockerAPISuite) TestContainerAPIWait(c *testing.T) { 988 name := "test-api-wait" 989 990 sleepCmd := "/bin/sleep" 991 if testEnv.OSType == "windows" { 992 sleepCmd = "sleep" 993 } 994 dockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2") 995 996 cli, err := client.NewClientWithOpts(client.FromEnv) 997 assert.NilError(c, err) 998 defer cli.Close() 999 1000 waitResC, errC := cli.ContainerWait(context.Background(), name, "") 1001 1002 select { 1003 case err = <-errC: 1004 assert.NilError(c, err) 1005 case waitRes := <-waitResC: 1006 assert.Equal(c, waitRes.StatusCode, int64(0)) 1007 } 1008 } 1009 1010 func (s *DockerAPISuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) { 1011 name := "test-container-api-copy" 1012 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1013 1014 postData := types.CopyConfig{ 1015 Resource: "/test.txt", 1016 } 1017 // no copy in client/ 1018 res, _, err := request.Post("/containers/"+name+"/copy", request.JSONBody(postData)) 1019 assert.NilError(c, err) 1020 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1021 } 1022 1023 func (s *DockerAPISuite) TestContainerAPICopyPre124(c *testing.T) { 1024 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1025 name := "test-container-api-copy" 1026 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1027 1028 postData := types.CopyConfig{ 1029 Resource: "/test.txt", 1030 } 1031 1032 res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1033 assert.NilError(c, err) 1034 assert.Equal(c, res.StatusCode, http.StatusOK) 1035 1036 found := false 1037 for tarReader := tar.NewReader(body); ; { 1038 h, err := tarReader.Next() 1039 if err != nil { 1040 if err == io.EOF { 1041 break 1042 } 1043 c.Fatal(err) 1044 } 1045 if h.Name == "test.txt" { 1046 found = true 1047 break 1048 } 1049 } 1050 assert.Assert(c, found) 1051 } 1052 1053 func (s *DockerAPISuite) TestContainerAPICopyResourcePathEmptyPre124(c *testing.T) { 1054 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1055 name := "test-container-api-copy-resource-empty" 1056 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1057 1058 postData := types.CopyConfig{ 1059 Resource: "", 1060 } 1061 1062 res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1063 assert.NilError(c, err) 1064 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 1065 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 1066 } else { 1067 assert.Assert(c, res.StatusCode != http.StatusOK) 1068 } 1069 b, err := request.ReadBody(body) 1070 assert.NilError(c, err) 1071 assert.Assert(c, is.Regexp("^Path cannot be empty\n$", string(b))) 1072 } 1073 1074 func (s *DockerAPISuite) TestContainerAPICopyResourcePathNotFoundPre124(c *testing.T) { 1075 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1076 name := "test-container-api-copy-resource-not-found" 1077 dockerCmd(c, "run", "--name", name, "busybox") 1078 1079 postData := types.CopyConfig{ 1080 Resource: "/notexist", 1081 } 1082 1083 res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1084 assert.NilError(c, err) 1085 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 1086 assert.Equal(c, res.StatusCode, http.StatusInternalServerError) 1087 } else { 1088 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1089 } 1090 b, err := request.ReadBody(body) 1091 assert.NilError(c, err) 1092 assert.Assert(c, is.Regexp("^Could not find the file /notexist in container "+name+"\n$", string(b))) 1093 } 1094 1095 func (s *DockerAPISuite) TestContainerAPICopyContainerNotFoundPr124(c *testing.T) { 1096 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1097 postData := types.CopyConfig{ 1098 Resource: "/something", 1099 } 1100 1101 res, _, err := request.Post("/v1.23/containers/notexists/copy", request.JSONBody(postData)) 1102 assert.NilError(c, err) 1103 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1104 } 1105 1106 func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) { 1107 out := runSleepingContainer(c) 1108 1109 id := strings.TrimSpace(out) 1110 assert.NilError(c, waitRun(id)) 1111 1112 dockerCmd(c, "stop", id) 1113 1114 cli, err := client.NewClientWithOpts(client.FromEnv) 1115 assert.NilError(c, err) 1116 defer cli.Close() 1117 1118 err = cli.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{}) 1119 assert.NilError(c, err) 1120 } 1121 1122 func (s *DockerAPISuite) TestContainerAPIDeleteNotExist(c *testing.T) { 1123 cli, err := client.NewClientWithOpts(client.FromEnv) 1124 assert.NilError(c, err) 1125 defer cli.Close() 1126 1127 err = cli.ContainerRemove(context.Background(), "doesnotexist", types.ContainerRemoveOptions{}) 1128 assert.ErrorContains(c, err, "No such container: doesnotexist") 1129 } 1130 1131 func (s *DockerAPISuite) TestContainerAPIDeleteForce(c *testing.T) { 1132 out := runSleepingContainer(c) 1133 id := strings.TrimSpace(out) 1134 assert.NilError(c, waitRun(id)) 1135 1136 removeOptions := types.ContainerRemoveOptions{ 1137 Force: true, 1138 } 1139 1140 cli, err := client.NewClientWithOpts(client.FromEnv) 1141 assert.NilError(c, err) 1142 defer cli.Close() 1143 1144 err = cli.ContainerRemove(context.Background(), id, removeOptions) 1145 assert.NilError(c, err) 1146 } 1147 1148 func (s *DockerAPISuite) TestContainerAPIDeleteRemoveLinks(c *testing.T) { 1149 // Windows does not support links 1150 testRequires(c, DaemonIsLinux) 1151 out, _ := dockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top") 1152 1153 id := strings.TrimSpace(out) 1154 assert.NilError(c, waitRun(id)) 1155 1156 out, _ = dockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top") 1157 1158 id2 := strings.TrimSpace(out) 1159 assert.Assert(c, waitRun(id2) == nil) 1160 1161 links := inspectFieldJSON(c, id2, "HostConfig.Links") 1162 assert.Equal(c, links, "[\"/tlink1:/tlink2/tlink1\"]", "expected to have links between containers") 1163 1164 removeOptions := types.ContainerRemoveOptions{ 1165 RemoveLinks: true, 1166 } 1167 1168 cli, err := client.NewClientWithOpts(client.FromEnv) 1169 assert.NilError(c, err) 1170 defer cli.Close() 1171 1172 err = cli.ContainerRemove(context.Background(), "tlink2/tlink1", removeOptions) 1173 assert.NilError(c, err) 1174 1175 linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links") 1176 assert.Equal(c, linksPostRm, "null", "call to api deleteContainer links should have removed the specified links") 1177 } 1178 1179 func (s *DockerAPISuite) TestContainerAPIDeleteConflict(c *testing.T) { 1180 out := runSleepingContainer(c) 1181 1182 id := strings.TrimSpace(out) 1183 assert.NilError(c, waitRun(id)) 1184 1185 cli, err := client.NewClientWithOpts(client.FromEnv) 1186 assert.NilError(c, err) 1187 defer cli.Close() 1188 1189 err = cli.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{}) 1190 expected := "cannot remove a running container" 1191 assert.ErrorContains(c, err, expected) 1192 } 1193 1194 func (s *DockerAPISuite) TestContainerAPIDeleteRemoveVolume(c *testing.T) { 1195 testRequires(c, testEnv.IsLocalDaemon) 1196 1197 vol := "/testvolume" 1198 if testEnv.OSType == "windows" { 1199 vol = `c:\testvolume` 1200 } 1201 1202 out := runSleepingContainer(c, "-v", vol) 1203 1204 id := strings.TrimSpace(out) 1205 assert.NilError(c, waitRun(id)) 1206 1207 source, err := inspectMountSourceField(id, vol) 1208 assert.NilError(c, err) 1209 _, err = os.Stat(source) 1210 assert.NilError(c, err) 1211 1212 removeOptions := types.ContainerRemoveOptions{ 1213 Force: true, 1214 RemoveVolumes: true, 1215 } 1216 1217 cli, err := client.NewClientWithOpts(client.FromEnv) 1218 assert.NilError(c, err) 1219 defer cli.Close() 1220 1221 err = cli.ContainerRemove(context.Background(), id, removeOptions) 1222 assert.NilError(c, err) 1223 1224 _, err = os.Stat(source) 1225 assert.Assert(c, os.IsNotExist(err), "expected to get ErrNotExist error, got %v", err) 1226 } 1227 1228 // Regression test for https://github.com/docker/docker/issues/6231 1229 func (s *DockerAPISuite) TestContainerAPIChunkedEncoding(c *testing.T) { 1230 config := map[string]interface{}{ 1231 "Image": "busybox", 1232 "Cmd": append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 1233 "OpenStdin": true, 1234 } 1235 1236 resp, _, err := request.Post("/containers/create", request.JSONBody(config), request.With(func(req *http.Request) error { 1237 // This is a cheat to make the http request do chunked encoding 1238 // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite 1239 // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 1240 req.ContentLength = -1 1241 return nil 1242 })) 1243 assert.Assert(c, err == nil, "error creating container with chunked encoding") 1244 defer resp.Body.Close() 1245 assert.Equal(c, resp.StatusCode, http.StatusCreated) 1246 } 1247 1248 func (s *DockerAPISuite) TestContainerAPIPostContainerStop(c *testing.T) { 1249 out := runSleepingContainer(c) 1250 1251 containerID := strings.TrimSpace(out) 1252 assert.Assert(c, waitRun(containerID) == nil) 1253 1254 cli, err := client.NewClientWithOpts(client.FromEnv) 1255 assert.NilError(c, err) 1256 defer cli.Close() 1257 1258 err = cli.ContainerStop(context.Background(), containerID, container.StopOptions{}) 1259 assert.NilError(c, err) 1260 assert.Assert(c, waitInspect(containerID, "{{ .State.Running }}", "false", 60*time.Second) == nil) 1261 } 1262 1263 // #14170 1264 func (s *DockerAPISuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *testing.T) { 1265 config := container.Config{ 1266 Image: "busybox", 1267 Entrypoint: []string{"echo"}, 1268 Cmd: []string{"hello", "world"}, 1269 } 1270 1271 cli, err := client.NewClientWithOpts(client.FromEnv) 1272 assert.NilError(c, err) 1273 defer cli.Close() 1274 1275 _, err = cli.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest") 1276 assert.NilError(c, err) 1277 out, _ := dockerCmd(c, "start", "-a", "echotest") 1278 assert.Equal(c, strings.TrimSpace(out), "hello world") 1279 1280 config2 := struct { 1281 Image string 1282 Entrypoint string 1283 Cmd []string 1284 }{"busybox", "echo", []string{"hello", "world"}} 1285 _, _, err = request.Post("/containers/create?name=echotest2", request.JSONBody(config2)) 1286 assert.NilError(c, err) 1287 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1288 assert.Equal(c, strings.TrimSpace(out), "hello world") 1289 } 1290 1291 // #14170 1292 func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T) { 1293 config := container.Config{ 1294 Image: "busybox", 1295 Cmd: []string{"echo", "hello", "world"}, 1296 } 1297 1298 cli, err := client.NewClientWithOpts(client.FromEnv) 1299 assert.NilError(c, err) 1300 defer cli.Close() 1301 1302 _, err = cli.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "echotest") 1303 assert.NilError(c, err) 1304 out, _ := dockerCmd(c, "start", "-a", "echotest") 1305 assert.Equal(c, strings.TrimSpace(out), "hello world") 1306 1307 config2 := struct { 1308 Image string 1309 Entrypoint string 1310 Cmd string 1311 }{"busybox", "echo", "hello world"} 1312 _, _, err = request.Post("/containers/create?name=echotest2", request.JSONBody(config2)) 1313 assert.NilError(c, err) 1314 out, _ = dockerCmd(c, "start", "-a", "echotest2") 1315 assert.Equal(c, strings.TrimSpace(out), "hello world") 1316 } 1317 1318 // regression #14318 1319 // for backward compatibility testing with and without CAP_ prefix 1320 // and with upper and lowercase 1321 func (s *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *testing.T) { 1322 // Windows doesn't support CapAdd/CapDrop 1323 testRequires(c, DaemonIsLinux) 1324 config := struct { 1325 Image string 1326 CapAdd string 1327 CapDrop string 1328 }{"busybox", "NET_ADMIN", "cap_sys_admin"} 1329 res, _, err := request.Post("/containers/create?name=capaddtest0", request.JSONBody(config)) 1330 assert.NilError(c, err) 1331 assert.Equal(c, res.StatusCode, http.StatusCreated) 1332 1333 config2 := container.Config{ 1334 Image: "busybox", 1335 } 1336 hostConfig := container.HostConfig{ 1337 CapAdd: []string{"net_admin", "SYS_ADMIN"}, 1338 CapDrop: []string{"SETGID", "CAP_SETPCAP"}, 1339 } 1340 1341 cli, err := client.NewClientWithOpts(client.FromEnv) 1342 assert.NilError(c, err) 1343 defer cli.Close() 1344 1345 _, err = cli.ContainerCreate(context.Background(), &config2, &hostConfig, &network.NetworkingConfig{}, nil, "capaddtest1") 1346 assert.NilError(c, err) 1347 } 1348 1349 // #14915 1350 func (s *DockerAPISuite) TestContainerAPICreateNoHostConfig118(c *testing.T) { 1351 testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later 1352 config := container.Config{ 1353 Image: "busybox", 1354 } 1355 1356 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18")) 1357 assert.NilError(c, err) 1358 1359 _, err = cli.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1360 assert.NilError(c, err) 1361 } 1362 1363 // Ensure an error occurs when you have a container read-only rootfs but you 1364 // extract an archive to a symlink in a writable volume which points to a 1365 // directory outside of the volume. 1366 func (s *DockerAPISuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRootfs(c *testing.T) { 1367 // Windows does not support read-only rootfs 1368 // Requires local volume mount bind. 1369 // --read-only + userns has remount issues 1370 testRequires(c, testEnv.IsLocalDaemon, NotUserNamespace, DaemonIsLinux) 1371 1372 testVol := getTestDir(c, "test-put-container-archive-err-symlink-in-volume-to-read-only-rootfs-") 1373 defer os.RemoveAll(testVol) 1374 1375 makeTestContentInDir(c, testVol) 1376 1377 cID := makeTestContainer(c, testContainerOptions{ 1378 readOnly: true, 1379 volumes: defaultVolumes(testVol), // Our bind mount is at /vol2 1380 }) 1381 1382 // Attempt to extract to a symlink in the volume which points to a 1383 // directory outside the volume. This should cause an error because the 1384 // rootfs is read-only. 1385 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.20")) 1386 assert.NilError(c, err) 1387 1388 err = cli.CopyToContainer(context.Background(), cID, "/vol2/symlinkToAbsDir", nil, types.CopyToContainerOptions{}) 1389 assert.ErrorContains(c, err, "container rootfs is marked read-only") 1390 } 1391 1392 func (s *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T) { 1393 // Not supported on Windows 1394 testRequires(c, DaemonIsLinux) 1395 1396 cli, err := client.NewClientWithOpts(client.FromEnv) 1397 assert.NilError(c, err) 1398 defer cli.Close() 1399 1400 config := container.Config{ 1401 Image: "busybox", 1402 } 1403 hostConfig1 := container.HostConfig{ 1404 Resources: container.Resources{ 1405 CpusetCpus: "1-42,,", 1406 }, 1407 } 1408 name := "wrong-cpuset-cpus" 1409 1410 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig1, &network.NetworkingConfig{}, nil, name) 1411 expected := "Invalid value 1-42,, for cpuset cpus" 1412 assert.ErrorContains(c, err, expected) 1413 1414 hostConfig2 := container.HostConfig{ 1415 Resources: container.Resources{ 1416 CpusetMems: "42-3,1--", 1417 }, 1418 } 1419 name = "wrong-cpuset-mems" 1420 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig2, &network.NetworkingConfig{}, nil, name) 1421 expected = "Invalid value 42-3,1-- for cpuset mems" 1422 assert.ErrorContains(c, err, expected) 1423 } 1424 1425 func (s *DockerAPISuite) TestPostContainersCreateShmSizeNegative(c *testing.T) { 1426 // ShmSize is not supported on Windows 1427 testRequires(c, DaemonIsLinux) 1428 config := container.Config{ 1429 Image: "busybox", 1430 } 1431 hostConfig := container.HostConfig{ 1432 ShmSize: -1, 1433 } 1434 1435 cli, err := client.NewClientWithOpts(client.FromEnv) 1436 assert.NilError(c, err) 1437 defer cli.Close() 1438 1439 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 1440 assert.ErrorContains(c, err, "SHM size can not be less than 0") 1441 } 1442 1443 func (s *DockerAPISuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testing.T) { 1444 // ShmSize is not supported on Windows 1445 testRequires(c, DaemonIsLinux) 1446 1447 config := container.Config{ 1448 Image: "busybox", 1449 Cmd: []string{"mount"}, 1450 } 1451 1452 cli, err := client.NewClientWithOpts(client.FromEnv) 1453 assert.NilError(c, err) 1454 defer cli.Close() 1455 1456 container, err := cli.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1457 assert.NilError(c, err) 1458 1459 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1460 assert.NilError(c, err) 1461 1462 assert.Equal(c, containerJSON.HostConfig.ShmSize, dconfig.DefaultShmSize) 1463 1464 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1465 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1466 if !shmRegexp.MatchString(out) { 1467 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1468 } 1469 } 1470 1471 func (s *DockerAPISuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) { 1472 // ShmSize is not supported on Windows 1473 testRequires(c, DaemonIsLinux) 1474 config := container.Config{ 1475 Image: "busybox", 1476 Cmd: []string{"mount"}, 1477 } 1478 1479 cli, err := client.NewClientWithOpts(client.FromEnv) 1480 assert.NilError(c, err) 1481 defer cli.Close() 1482 1483 container, err := cli.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1484 assert.NilError(c, err) 1485 1486 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1487 assert.NilError(c, err) 1488 1489 assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(67108864)) 1490 1491 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1492 shmRegexp := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=65536k`) 1493 if !shmRegexp.MatchString(out) { 1494 c.Fatalf("Expected shm of 64MB in mount command, got %v", out) 1495 } 1496 } 1497 1498 func (s *DockerAPISuite) TestPostContainersCreateWithShmSize(c *testing.T) { 1499 // ShmSize is not supported on Windows 1500 testRequires(c, DaemonIsLinux) 1501 config := container.Config{ 1502 Image: "busybox", 1503 Cmd: []string{"mount"}, 1504 } 1505 1506 hostConfig := container.HostConfig{ 1507 ShmSize: 1073741824, 1508 } 1509 1510 cli, err := client.NewClientWithOpts(client.FromEnv) 1511 assert.NilError(c, err) 1512 defer cli.Close() 1513 1514 container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 1515 assert.NilError(c, err) 1516 1517 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1518 assert.NilError(c, err) 1519 1520 assert.Equal(c, containerJSON.HostConfig.ShmSize, int64(1073741824)) 1521 1522 out, _ := dockerCmd(c, "start", "-i", containerJSON.ID) 1523 shmRegex := regexp.MustCompile(`shm on /dev/shm type tmpfs(.*)size=1048576k`) 1524 if !shmRegex.MatchString(out) { 1525 c.Fatalf("Expected shm of 1GB in mount command, got %v", out) 1526 } 1527 } 1528 1529 func (s *DockerAPISuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *testing.T) { 1530 // Swappiness is not supported on Windows 1531 testRequires(c, DaemonIsLinux) 1532 config := container.Config{ 1533 Image: "busybox", 1534 } 1535 1536 cli, err := client.NewClientWithOpts(client.FromEnv) 1537 assert.NilError(c, err) 1538 defer cli.Close() 1539 1540 container, err := cli.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1541 assert.NilError(c, err) 1542 1543 containerJSON, err := cli.ContainerInspect(context.Background(), container.ID) 1544 assert.NilError(c, err) 1545 1546 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.31") { 1547 assert.Equal(c, *containerJSON.HostConfig.MemorySwappiness, int64(-1)) 1548 } else { 1549 assert.Assert(c, containerJSON.HostConfig.MemorySwappiness == nil) 1550 } 1551 } 1552 1553 // check validation is done daemon side and not only in cli 1554 func (s *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *testing.T) { 1555 // OomScoreAdj is not supported on Windows 1556 testRequires(c, DaemonIsLinux) 1557 1558 config := container.Config{ 1559 Image: "busybox", 1560 } 1561 1562 hostConfig := container.HostConfig{ 1563 OomScoreAdj: 1001, 1564 } 1565 1566 cli, err := client.NewClientWithOpts(client.FromEnv) 1567 assert.NilError(c, err) 1568 defer cli.Close() 1569 1570 name := "oomscoreadj-over" 1571 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, name) 1572 1573 expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]" 1574 assert.ErrorContains(c, err, expected) 1575 1576 hostConfig = container.HostConfig{ 1577 OomScoreAdj: -1001, 1578 } 1579 1580 name = "oomscoreadj-low" 1581 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, name) 1582 1583 expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]" 1584 assert.ErrorContains(c, err, expected) 1585 } 1586 1587 // test case for #22210 where an empty container name caused panic. 1588 func (s *DockerAPISuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) { 1589 cli, err := client.NewClientWithOpts(client.FromEnv) 1590 assert.NilError(c, err) 1591 defer cli.Close() 1592 1593 err = cli.ContainerRemove(context.Background(), "", types.ContainerRemoveOptions{}) 1594 assert.Check(c, errdefs.IsNotFound(err)) 1595 } 1596 1597 func (s *DockerAPISuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) { 1598 // Problematic on Windows as Windows does not support stats 1599 testRequires(c, DaemonIsLinux) 1600 1601 name := "testing-network-disabled" 1602 1603 config := container.Config{ 1604 Image: "busybox", 1605 Cmd: []string{"top"}, 1606 NetworkDisabled: true, 1607 } 1608 1609 cli, err := client.NewClientWithOpts(client.FromEnv) 1610 assert.NilError(c, err) 1611 defer cli.Close() 1612 1613 _, err = cli.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name) 1614 assert.NilError(c, err) 1615 1616 err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{}) 1617 assert.NilError(c, err) 1618 1619 assert.Assert(c, waitRun(name) == nil) 1620 1621 type b struct { 1622 stats types.ContainerStats 1623 err error 1624 } 1625 bc := make(chan b, 1) 1626 go func() { 1627 stats, err := cli.ContainerStats(context.Background(), name, false) 1628 bc <- b{stats, err} 1629 }() 1630 1631 // allow some time to stream the stats from the container 1632 time.Sleep(4 * time.Second) 1633 dockerCmd(c, "rm", "-f", name) 1634 1635 // collect the results from the stats stream or timeout and fail 1636 // if the stream was not disconnected. 1637 select { 1638 case <-time.After(2 * time.Second): 1639 c.Fatal("stream was not closed after container was removed") 1640 case sr := <-bc: 1641 assert.Assert(c, sr.err == nil) 1642 sr.stats.Body.Close() 1643 } 1644 } 1645 1646 func (s *DockerAPISuite) TestContainersAPICreateMountsValidation(c *testing.T) { 1647 type testCase struct { 1648 config container.Config 1649 hostConfig container.HostConfig 1650 msg string 1651 } 1652 1653 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1654 destPath := prefix + slash + "foo" 1655 notExistPath := prefix + slash + "notexist" 1656 1657 cases := []testCase{ 1658 { 1659 config: container.Config{ 1660 Image: "busybox", 1661 }, 1662 hostConfig: container.HostConfig{ 1663 Mounts: []mount.Mount{{ 1664 Type: "notreal", 1665 Target: destPath, 1666 }, 1667 }, 1668 }, 1669 1670 msg: "mount type unknown", 1671 }, 1672 { 1673 config: container.Config{ 1674 Image: "busybox", 1675 }, 1676 hostConfig: container.HostConfig{ 1677 Mounts: []mount.Mount{{ 1678 Type: "bind"}}}, 1679 msg: "Target must not be empty", 1680 }, 1681 { 1682 config: container.Config{ 1683 Image: "busybox", 1684 }, 1685 hostConfig: container.HostConfig{ 1686 Mounts: []mount.Mount{{ 1687 Type: "bind", 1688 Target: destPath}}}, 1689 msg: "Source must not be empty", 1690 }, 1691 { 1692 config: container.Config{ 1693 Image: "busybox", 1694 }, 1695 hostConfig: container.HostConfig{ 1696 Mounts: []mount.Mount{{ 1697 Type: "bind", 1698 Source: notExistPath, 1699 Target: destPath}}}, 1700 msg: "source path does not exist", 1701 // FIXME(vdemeester) fails into e2e, migrate to integration/container anyway 1702 // msg: "source path does not exist: " + notExistPath, 1703 }, 1704 { 1705 config: container.Config{ 1706 Image: "busybox", 1707 }, 1708 hostConfig: container.HostConfig{ 1709 Mounts: []mount.Mount{{ 1710 Type: "volume"}}}, 1711 msg: "Target must not be empty", 1712 }, 1713 { 1714 config: container.Config{ 1715 Image: "busybox", 1716 }, 1717 hostConfig: container.HostConfig{ 1718 Mounts: []mount.Mount{{ 1719 Type: "volume", 1720 Source: "hello", 1721 Target: destPath}}}, 1722 msg: "", 1723 }, 1724 { 1725 config: container.Config{ 1726 Image: "busybox", 1727 }, 1728 hostConfig: container.HostConfig{ 1729 Mounts: []mount.Mount{{ 1730 Type: "volume", 1731 Source: "hello2", 1732 Target: destPath, 1733 VolumeOptions: &mount.VolumeOptions{ 1734 DriverConfig: &mount.Driver{ 1735 Name: "local"}}}}}, 1736 msg: "", 1737 }, 1738 } 1739 1740 if testEnv.IsLocalDaemon() { 1741 tmpDir, err := ioutils.TempDir("", "test-mounts-api") 1742 assert.NilError(c, err) 1743 defer os.RemoveAll(tmpDir) 1744 cases = append(cases, []testCase{ 1745 { 1746 config: container.Config{ 1747 Image: "busybox", 1748 }, 1749 hostConfig: container.HostConfig{ 1750 Mounts: []mount.Mount{{ 1751 Type: "bind", 1752 Source: tmpDir, 1753 Target: destPath}}}, 1754 msg: "", 1755 }, 1756 { 1757 config: container.Config{ 1758 Image: "busybox", 1759 }, 1760 hostConfig: container.HostConfig{ 1761 Mounts: []mount.Mount{{ 1762 Type: "bind", 1763 Source: tmpDir, 1764 Target: destPath, 1765 VolumeOptions: &mount.VolumeOptions{}}}}, 1766 msg: "VolumeOptions must not be specified", 1767 }, 1768 }...) 1769 } 1770 1771 if DaemonIsWindows() { 1772 cases = append(cases, []testCase{ 1773 { 1774 config: container.Config{ 1775 Image: "busybox", 1776 }, 1777 hostConfig: container.HostConfig{ 1778 Mounts: []mount.Mount{ 1779 { 1780 Type: "volume", 1781 Source: "not-supported-on-windows", 1782 Target: destPath, 1783 VolumeOptions: &mount.VolumeOptions{ 1784 DriverConfig: &mount.Driver{ 1785 Name: "local", 1786 Options: map[string]string{"type": "tmpfs"}, 1787 }, 1788 }, 1789 }, 1790 }, 1791 }, 1792 msg: `options are not supported on this platform`, 1793 }, 1794 }...) 1795 } 1796 1797 if DaemonIsLinux() { 1798 cases = append(cases, []testCase{ 1799 { 1800 config: container.Config{ 1801 Image: "busybox", 1802 }, 1803 hostConfig: container.HostConfig{ 1804 Mounts: []mount.Mount{ 1805 { 1806 Type: "volume", 1807 Source: "missing-device-opt", 1808 Target: destPath, 1809 VolumeOptions: &mount.VolumeOptions{ 1810 DriverConfig: &mount.Driver{ 1811 Name: "local", 1812 Options: map[string]string{"foobar": "foobaz"}, 1813 }, 1814 }, 1815 }, 1816 }, 1817 }, 1818 msg: `invalid option: "foobar"`, 1819 }, 1820 { 1821 config: container.Config{ 1822 Image: "busybox", 1823 }, 1824 hostConfig: container.HostConfig{ 1825 Mounts: []mount.Mount{ 1826 { 1827 Type: "volume", 1828 Source: "missing-device-opt", 1829 Target: destPath, 1830 VolumeOptions: &mount.VolumeOptions{ 1831 DriverConfig: &mount.Driver{ 1832 Name: "local", 1833 Options: map[string]string{"type": "tmpfs"}, 1834 }, 1835 }, 1836 }, 1837 }, 1838 }, 1839 msg: `missing required option: "device"`, 1840 }, 1841 { 1842 config: container.Config{ 1843 Image: "busybox", 1844 }, 1845 hostConfig: container.HostConfig{ 1846 Mounts: []mount.Mount{ 1847 { 1848 Type: "volume", 1849 Source: "missing-type-opt", 1850 Target: destPath, 1851 VolumeOptions: &mount.VolumeOptions{ 1852 DriverConfig: &mount.Driver{ 1853 Name: "local", 1854 Options: map[string]string{"device": "tmpfs"}, 1855 }, 1856 }, 1857 }, 1858 }, 1859 }, 1860 msg: `missing required option: "type"`, 1861 }, 1862 { 1863 config: container.Config{ 1864 Image: "busybox", 1865 }, 1866 hostConfig: container.HostConfig{ 1867 Mounts: []mount.Mount{ 1868 { 1869 Type: "volume", 1870 Source: "hello4", 1871 Target: destPath, 1872 VolumeOptions: &mount.VolumeOptions{ 1873 DriverConfig: &mount.Driver{ 1874 Name: "local", 1875 Options: map[string]string{"o": "size=1", "type": "tmpfs", "device": "tmpfs"}, 1876 }, 1877 }, 1878 }, 1879 }, 1880 }, 1881 msg: "", 1882 }, 1883 { 1884 config: container.Config{ 1885 Image: "busybox", 1886 }, 1887 hostConfig: container.HostConfig{ 1888 Mounts: []mount.Mount{{ 1889 Type: "tmpfs", 1890 Target: destPath}}}, 1891 msg: "", 1892 }, 1893 { 1894 config: container.Config{ 1895 Image: "busybox", 1896 }, 1897 hostConfig: container.HostConfig{ 1898 Mounts: []mount.Mount{{ 1899 Type: "tmpfs", 1900 Target: destPath, 1901 TmpfsOptions: &mount.TmpfsOptions{ 1902 SizeBytes: 4096 * 1024, 1903 Mode: 0700, 1904 }}}}, 1905 msg: "", 1906 }, 1907 { 1908 config: container.Config{ 1909 Image: "busybox", 1910 }, 1911 hostConfig: container.HostConfig{ 1912 Mounts: []mount.Mount{{ 1913 Type: "tmpfs", 1914 Source: "/shouldnotbespecified", 1915 Target: destPath}}}, 1916 msg: "Source must not be specified", 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, &network.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 *DockerAPISuite) 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 := os.MkdirTemp("", "test-mounts-api-bind") 1944 assert.NilError(c, err) 1945 defer os.RemoveAll(tmpDir) 1946 err = os.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0666) 1947 assert.NilError(c, err) 1948 config := container.Config{ 1949 Image: "busybox", 1950 Cmd: []string{"/bin/sh", "-c", "cat /foo/bar"}, 1951 } 1952 hostConfig := container.HostConfig{ 1953 Mounts: []mount.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, &network.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 *DockerAPISuite) 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 mount.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 runtime.GOOS == "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: mount.Mount{Type: "volume", Target: destPath}, 2007 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2008 }, 2009 { 2010 spec: mount.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: mount.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: mount.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: mount.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mount.VolumeOptions{DriverConfig: &mount.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 := os.MkdirTemp("", "test-mounts-api-1") 2030 assert.NilError(c, err) 2031 defer os.RemoveAll(tmpDir1) 2032 cases = append(cases, []testCase{ 2033 { 2034 spec: mount.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: mount.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: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath}, 2063 expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3}, 2064 }, 2065 { 2066 spec: mount.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: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mount.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: mount.Mount{Type: "volume", Target: destPath, VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2081 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2082 }, 2083 { 2084 spec: mount.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2085 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2086 }, 2087 { 2088 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2089 expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2090 }, 2091 { 2092 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mount.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 &container.Config{Image: testImg}, 2106 &container.HostConfig{Mounts: []mount.Mount{x.spec}}, 2107 &network.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 // Named volumes still exist after the container is removed 2147 case x.spec.Type == "volume" && len(x.spec.Source) > 0: 2148 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2149 assert.NilError(c, err) 2150 2151 // Bind mounts are never removed with the container 2152 case x.spec.Type == "bind": 2153 2154 // anonymous volumes are removed 2155 default: 2156 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2157 assert.Check(c, client.IsErrNotFound(err)) 2158 } 2159 }) 2160 } 2161 } 2162 2163 func containerExit(apiclient client.APIClient, name string) func(poll.LogT) poll.Result { 2164 return func(logT poll.LogT) poll.Result { 2165 container, err := apiclient.ContainerInspect(context.Background(), name) 2166 if err != nil { 2167 return poll.Error(err) 2168 } 2169 switch container.State.Status { 2170 case "created", "running": 2171 return poll.Continue("container %s is %s, waiting for exit", name, container.State.Status) 2172 } 2173 return poll.Success() 2174 } 2175 } 2176 2177 func (s *DockerAPISuite) TestContainersAPICreateMountsTmpfs(c *testing.T) { 2178 testRequires(c, DaemonIsLinux) 2179 type testCase struct { 2180 cfg mount.Mount 2181 expectedOptions []string 2182 } 2183 target := "/foo" 2184 cases := []testCase{ 2185 { 2186 cfg: mount.Mount{ 2187 Type: "tmpfs", 2188 Target: target}, 2189 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}, 2190 }, 2191 { 2192 cfg: mount.Mount{ 2193 Type: "tmpfs", 2194 Target: target, 2195 TmpfsOptions: &mount.TmpfsOptions{ 2196 SizeBytes: 4096 * 1024, Mode: 0700}}, 2197 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"}, 2198 }, 2199 } 2200 2201 cli, err := client.NewClientWithOpts(client.FromEnv) 2202 assert.NilError(c, err) 2203 defer cli.Close() 2204 2205 config := container.Config{ 2206 Image: "busybox", 2207 Cmd: []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)}, 2208 } 2209 for i, x := range cases { 2210 cName := fmt.Sprintf("test-tmpfs-%d", i) 2211 hostConfig := container.HostConfig{ 2212 Mounts: []mount.Mount{x.cfg}, 2213 } 2214 2215 _, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, cName) 2216 assert.NilError(c, err) 2217 out, _ := dockerCmd(c, "start", "-a", cName) 2218 for _, option := range x.expectedOptions { 2219 assert.Assert(c, strings.Contains(out, option)) 2220 } 2221 } 2222 } 2223 2224 // Regression test for #33334 2225 // Makes sure that when a container which has a custom stop signal + restart=always 2226 // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled. 2227 func (s *DockerAPISuite) TestContainerKillCustomStopSignal(c *testing.T) { 2228 id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always")) 2229 res, _, err := request.Post("/containers/" + id + "/kill") 2230 assert.NilError(c, err) 2231 defer res.Body.Close() 2232 2233 b, err := io.ReadAll(res.Body) 2234 assert.NilError(c, err) 2235 assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b)) 2236 err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second) 2237 assert.NilError(c, err) 2238 }