github.com/rawahars/moby@v24.0.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/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 *DockerAPISuite) TestContainerAPIGetAll(c *testing.T) { 39 startCount := getContainerCount(c) 40 name := "getall" 41 dockerCmd(c, "run", "--name", name, "busybox", "true") 42 43 apiClient, err := client.NewClientWithOpts(client.FromEnv) 44 assert.NilError(c, err) 45 defer apiClient.Close() 46 47 options := types.ContainerListOptions{ 48 All: true, 49 } 50 containers, err := apiClient.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 *DockerAPISuite) TestContainerAPIGetJSONNoFieldsOmitted(c *testing.T) { 59 startCount := getContainerCount(c) 60 dockerCmd(c, "run", "busybox", "true") 61 62 apiClient, err := client.NewClientWithOpts(client.FromEnv) 63 assert.NilError(c, err) 64 defer apiClient.Close() 65 66 options := types.ContainerListOptions{ 67 All: true, 68 } 69 containers, err := apiClient.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 105 assert.NilError(c, err) 106 defer apiClient.Close() 107 108 body, err := apiClient.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 132 assert.NilError(c, err) 133 defer apiClient.Close() 134 135 changes, err := apiClient.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 162 assert.NilError(c, err) 163 defer apiClient.Close() 164 165 stats, err := apiClient.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 196 assert.NilError(c, err) 197 defer apiClient.Close() 198 199 stats, err := apiClient.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 267 assert.NilError(c, err) 268 defer apiClient.Close() 269 270 stats, err := apiClient.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 := io.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 309 assert.NilError(c, err) 310 defer apiClient.Close() 311 312 stats, err := apiClient.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 := io.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 346 assert.NilError(c, err) 347 defer apiClient.Close() 348 349 resp, err := apiClient.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 375 assert.NilError(c, err) 376 defer apiClient.Close() 377 378 err = apiClient.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 = apiClient.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 401 assert.NilError(c, err) 402 defer apiClient.Close() 403 404 // sort by comm[andline] to make sure order stays the same in case of PID rollover 405 top, err := apiClient.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv) 424 assert.NilError(c, err) 425 defer apiClient.Close() 426 427 top, err := apiClient.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 *DockerAPISuite) TestContainerAPICommit(c *testing.T) { 449 cName := "testapicommit" 450 dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 451 452 apiClient, err := client.NewClientWithOpts(client.FromEnv) 453 assert.NilError(c, err) 454 defer apiClient.Close() 455 456 options := types.ContainerCommitOptions{ 457 Reference: "testcontainerapicommit:testtag", 458 } 459 460 img, err := apiClient.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 *DockerAPISuite) TestContainerAPICommitWithLabelInConfig(c *testing.T) { 471 cName := "testapicommitwithconfig" 472 dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") 473 474 apiClient, err := client.NewClientWithOpts(client.FromEnv) 475 assert.NilError(c, err) 476 defer apiClient.Close() 477 478 config := container.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 := apiClient.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 *DockerAPISuite) TestContainerAPIBadPort(c *testing.T) { 503 // TODO Windows to Windows CI - Port this test 504 testRequires(c, DaemonIsLinux) 505 506 config := container.Config{ 507 Image: "busybox", 508 Cmd: []string{"/bin/sh", "-c", "echo test"}, 509 } 510 511 hostConfig := container.HostConfig{ 512 PortBindings: nat.PortMap{ 513 "8080/tcp": []nat.PortBinding{ 514 { 515 HostIP: "", 516 HostPort: "aa80"}, 517 }, 518 }, 519 } 520 521 apiClient, err := client.NewClientWithOpts(client.FromEnv) 522 assert.NilError(c, err) 523 defer apiClient.Close() 524 525 _, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 526 assert.ErrorContains(c, err, `invalid port specification: "aa80"`) 527 } 528 529 func (s *DockerAPISuite) TestContainerAPICreate(c *testing.T) { 530 config := container.Config{ 531 Image: "busybox", 532 Cmd: []string{"/bin/sh", "-c", "touch /test && ls /test"}, 533 } 534 535 apiClient, err := client.NewClientWithOpts(client.FromEnv) 536 assert.NilError(c, err) 537 defer apiClient.Close() 538 539 ctr, err := apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 540 assert.NilError(c, err) 541 542 out, _ := dockerCmd(c, "start", "-a", ctr.ID) 543 assert.Equal(c, strings.TrimSpace(out), "/test") 544 } 545 546 func (s *DockerAPISuite) TestContainerAPICreateEmptyConfig(c *testing.T) { 547 apiClient, err := client.NewClientWithOpts(client.FromEnv) 548 assert.NilError(c, err) 549 defer apiClient.Close() 550 551 _, err = apiClient.ContainerCreate(context.Background(), &container.Config{}, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 552 553 expected := "No command specified" 554 assert.ErrorContains(c, err, expected) 555 } 556 557 func (s *DockerAPISuite) TestContainerAPICreateMultipleNetworksConfig(c *testing.T) { 558 // Container creation must fail if client specified configurations for more than one network 559 config := container.Config{ 560 Image: "busybox", 561 } 562 563 networkingConfig := network.NetworkingConfig{ 564 EndpointsConfig: map[string]*network.EndpointSettings{ 565 "net1": {}, 566 "net2": {}, 567 "net3": {}, 568 }, 569 } 570 571 apiClient, err := client.NewClientWithOpts(client.FromEnv) 572 assert.NilError(c, err) 573 defer apiClient.Close() 574 575 _, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &networkingConfig, nil, "") 576 msg := err.Error() 577 // network name order in error message is not deterministic 578 assert.Assert(c, strings.Contains(msg, "Container cannot be connected to network endpoints")) 579 assert.Assert(c, strings.Contains(msg, "net1")) 580 assert.Assert(c, strings.Contains(msg, "net2")) 581 assert.Assert(c, strings.Contains(msg, "net3")) 582 } 583 584 func (s *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) { 585 // Windows does not support bridge 586 testRequires(c, DaemonIsLinux) 587 UtilCreateNetworkMode(c, "bridge") 588 } 589 590 func (s *DockerAPISuite) TestContainerAPICreateOtherNetworkModes(c *testing.T) { 591 // Windows does not support these network modes 592 testRequires(c, DaemonIsLinux, NotUserNamespace) 593 UtilCreateNetworkMode(c, "host") 594 UtilCreateNetworkMode(c, "container:web1") 595 } 596 597 func UtilCreateNetworkMode(c *testing.T, networkMode container.NetworkMode) { 598 config := container.Config{ 599 Image: "busybox", 600 } 601 602 hostConfig := container.HostConfig{ 603 NetworkMode: networkMode, 604 } 605 606 apiClient, err := client.NewClientWithOpts(client.FromEnv) 607 assert.NilError(c, err) 608 defer apiClient.Close() 609 610 ctr, err := apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 611 assert.NilError(c, err) 612 613 containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.ID) 614 assert.NilError(c, err) 615 616 assert.Equal(c, containerJSON.HostConfig.NetworkMode, networkMode, "Mismatched NetworkMode") 617 } 618 619 func (s *DockerAPISuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) { 620 // TODO Windows to Windows CI. The CpuShares part could be ported. 621 testRequires(c, DaemonIsLinux) 622 config := container.Config{ 623 Image: "busybox", 624 } 625 626 hostConfig := container.HostConfig{ 627 Resources: container.Resources{ 628 CPUShares: 512, 629 CpusetCpus: "0", 630 }, 631 } 632 633 apiClient, err := client.NewClientWithOpts(client.FromEnv) 634 assert.NilError(c, err) 635 defer apiClient.Close() 636 637 ctr, err := apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 638 assert.NilError(c, err) 639 640 containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.ID) 641 assert.NilError(c, err) 642 643 out := inspectField(c, containerJSON.ID, "HostConfig.CpuShares") 644 assert.Equal(c, out, "512") 645 646 outCpuset := inspectField(c, containerJSON.ID, "HostConfig.CpusetCpus") 647 assert.Equal(c, outCpuset, "0") 648 } 649 650 func (s *DockerAPISuite) TestContainerAPIVerifyHeader(c *testing.T) { 651 config := map[string]interface{}{ 652 "Image": "busybox", 653 } 654 655 create := func(ct string) (*http.Response, io.ReadCloser, error) { 656 jsonData := bytes.NewBuffer(nil) 657 assert.Assert(c, json.NewEncoder(jsonData).Encode(config) == nil) 658 return request.Post("/containers/create", request.RawContent(io.NopCloser(jsonData)), request.ContentType(ct)) 659 } 660 661 // Try with no content-type 662 res, body, err := create("") 663 assert.NilError(c, err) 664 // todo: we need to figure out a better way to compare between dockerd versions 665 // comparing between daemon API version is not precise. 666 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 667 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 668 } else { 669 assert.Assert(c, res.StatusCode != http.StatusOK) 670 } 671 body.Close() 672 673 // Try with wrong content-type 674 res, body, err = create("application/xml") 675 assert.NilError(c, err) 676 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 677 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 678 } else { 679 assert.Assert(c, res.StatusCode != http.StatusOK) 680 } 681 body.Close() 682 683 // now application/json 684 res, body, err = create("application/json") 685 assert.NilError(c, err) 686 assert.Equal(c, res.StatusCode, http.StatusCreated) 687 body.Close() 688 } 689 690 // Issue 14230. daemon should return 500 for invalid port syntax 691 func (s *DockerAPISuite) TestContainerAPIInvalidPortSyntax(c *testing.T) { 692 config := `{ 693 "Image": "busybox", 694 "HostConfig": { 695 "NetworkMode": "default", 696 "PortBindings": { 697 "19039;1230": [ 698 {} 699 ] 700 } 701 } 702 }` 703 704 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 705 assert.NilError(c, err) 706 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 707 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 708 } else { 709 assert.Assert(c, res.StatusCode != http.StatusOK) 710 } 711 712 b, err := request.ReadBody(body) 713 assert.NilError(c, err) 714 assert.Assert(c, strings.Contains(string(b[:]), "invalid port")) 715 } 716 717 func (s *DockerAPISuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *testing.T) { 718 config := `{ 719 "Image": "busybox", 720 "HostConfig": { 721 "RestartPolicy": { 722 "Name": "something", 723 "MaximumRetryCount": 0 724 } 725 } 726 }` 727 728 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 729 assert.NilError(c, err) 730 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 731 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 732 } else { 733 assert.Assert(c, res.StatusCode != http.StatusOK) 734 } 735 736 b, err := request.ReadBody(body) 737 assert.NilError(c, err) 738 assert.Assert(c, strings.Contains(string(b[:]), "invalid restart policy")) 739 } 740 741 func (s *DockerAPISuite) TestContainerAPIRestartPolicyRetryMismatch(c *testing.T) { 742 config := `{ 743 "Image": "busybox", 744 "HostConfig": { 745 "RestartPolicy": { 746 "Name": "always", 747 "MaximumRetryCount": 2 748 } 749 } 750 }` 751 752 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 753 assert.NilError(c, err) 754 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 755 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 756 } else { 757 assert.Assert(c, res.StatusCode != http.StatusOK) 758 } 759 760 b, err := request.ReadBody(body) 761 assert.NilError(c, err) 762 assert.Assert(c, strings.Contains(string(b[:]), "maximum retry count cannot be used with restart policy")) 763 } 764 765 func (s *DockerAPISuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *testing.T) { 766 config := `{ 767 "Image": "busybox", 768 "HostConfig": { 769 "RestartPolicy": { 770 "Name": "on-failure", 771 "MaximumRetryCount": -2 772 } 773 } 774 }` 775 776 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 777 assert.NilError(c, err) 778 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 779 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 780 } else { 781 assert.Assert(c, res.StatusCode != http.StatusOK) 782 } 783 784 b, err := request.ReadBody(body) 785 assert.NilError(c, err) 786 assert.Assert(c, strings.Contains(string(b[:]), "maximum retry count cannot be negative")) 787 } 788 789 func (s *DockerAPISuite) TestContainerAPIRestartPolicyDefaultRetryCount(c *testing.T) { 790 config := `{ 791 "Image": "busybox", 792 "HostConfig": { 793 "RestartPolicy": { 794 "Name": "on-failure", 795 "MaximumRetryCount": 0 796 } 797 } 798 }` 799 800 res, _, err := request.Post("/containers/create", request.RawString(config), request.JSON) 801 assert.NilError(c, err) 802 assert.Equal(c, res.StatusCode, http.StatusCreated) 803 } 804 805 // Issue 7941 - test to make sure a "null" in JSON is just ignored. 806 // W/o this fix a null in JSON would be parsed into a string var as "null" 807 func (s *DockerAPISuite) TestContainerAPIPostCreateNull(c *testing.T) { 808 config := `{ 809 "Hostname":"", 810 "Domainname":"", 811 "Memory":0, 812 "MemorySwap":0, 813 "CpuShares":0, 814 "Cpuset":null, 815 "AttachStdin":true, 816 "AttachStdout":true, 817 "AttachStderr":true, 818 "ExposedPorts":{}, 819 "Tty":true, 820 "OpenStdin":true, 821 "StdinOnce":true, 822 "Env":[], 823 "Cmd":"ls", 824 "Image":"busybox", 825 "Volumes":{}, 826 "WorkingDir":"", 827 "Entrypoint":null, 828 "NetworkDisabled":false, 829 "OnBuild":null}` 830 831 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 832 assert.NilError(c, err) 833 assert.Equal(c, res.StatusCode, http.StatusCreated) 834 835 b, err := request.ReadBody(body) 836 assert.NilError(c, err) 837 type createResp struct { 838 ID string 839 } 840 var ctr createResp 841 assert.Assert(c, json.Unmarshal(b, &ctr) == nil) 842 out := inspectField(c, ctr.ID, "HostConfig.CpusetCpus") 843 assert.Equal(c, out, "") 844 845 outMemory := inspectField(c, ctr.ID, "HostConfig.Memory") 846 assert.Equal(c, outMemory, "0") 847 outMemorySwap := inspectField(c, ctr.ID, "HostConfig.MemorySwap") 848 assert.Equal(c, outMemorySwap, "0") 849 } 850 851 func (s *DockerAPISuite) TestCreateWithTooLowMemoryLimit(c *testing.T) { 852 // TODO Windows: Port once memory is supported 853 testRequires(c, DaemonIsLinux) 854 config := `{ 855 "Image": "busybox", 856 "Cmd": "ls", 857 "OpenStdin": true, 858 "CpuShares": 100, 859 "Memory": 524287 860 }` 861 862 res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON) 863 assert.NilError(c, err) 864 b, err2 := request.ReadBody(body) 865 assert.Assert(c, err2 == nil) 866 867 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 868 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 869 } else { 870 assert.Assert(c, res.StatusCode != http.StatusOK) 871 } 872 assert.Assert(c, strings.Contains(string(b), "Minimum memory limit allowed is 6MB")) 873 } 874 875 func (s *DockerAPISuite) TestContainerAPIRename(c *testing.T) { 876 out, _ := dockerCmd(c, "run", "--name", "TestContainerAPIRename", "-d", "busybox", "sh") 877 878 containerID := strings.TrimSpace(out) 879 newName := "TestContainerAPIRenameNew" 880 881 apiClient, err := client.NewClientWithOpts(client.FromEnv) 882 assert.NilError(c, err) 883 defer apiClient.Close() 884 885 err = apiClient.ContainerRename(context.Background(), containerID, newName) 886 assert.NilError(c, err) 887 888 name := inspectField(c, containerID, "Name") 889 assert.Equal(c, name, "/"+newName, "Failed to rename container") 890 } 891 892 func (s *DockerAPISuite) TestContainerAPIKill(c *testing.T) { 893 name := "test-api-kill" 894 runSleepingContainer(c, "-i", "--name", name) 895 896 apiClient, err := client.NewClientWithOpts(client.FromEnv) 897 assert.NilError(c, err) 898 defer apiClient.Close() 899 900 err = apiClient.ContainerKill(context.Background(), name, "SIGKILL") 901 assert.NilError(c, err) 902 903 state := inspectField(c, name, "State.Running") 904 assert.Equal(c, state, "false", fmt.Sprintf("got wrong State from container %s: %q", name, state)) 905 } 906 907 func (s *DockerAPISuite) TestContainerAPIRestart(c *testing.T) { 908 name := "test-api-restart" 909 runSleepingContainer(c, "-di", "--name", name) 910 apiClient, err := client.NewClientWithOpts(client.FromEnv) 911 assert.NilError(c, err) 912 defer apiClient.Close() 913 914 timeout := 1 915 err = apiClient.ContainerRestart(context.Background(), name, container.StopOptions{Timeout: &timeout}) 916 assert.NilError(c, err) 917 918 assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil) 919 } 920 921 func (s *DockerAPISuite) TestContainerAPIRestartNotimeoutParam(c *testing.T) { 922 name := "test-api-restart-no-timeout-param" 923 out := runSleepingContainer(c, "-di", "--name", name) 924 id := strings.TrimSpace(out) 925 assert.NilError(c, waitRun(id)) 926 927 apiClient, err := client.NewClientWithOpts(client.FromEnv) 928 assert.NilError(c, err) 929 defer apiClient.Close() 930 931 err = apiClient.ContainerRestart(context.Background(), name, container.StopOptions{}) 932 assert.NilError(c, err) 933 934 assert.Assert(c, waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 15*time.Second) == nil) 935 } 936 937 func (s *DockerAPISuite) TestContainerAPIStart(c *testing.T) { 938 name := "testing-start" 939 config := container.Config{ 940 Image: "busybox", 941 Cmd: append([]string{"/bin/sh", "-c"}, sleepCommandForDaemonPlatform()...), 942 OpenStdin: true, 943 } 944 945 apiClient, err := client.NewClientWithOpts(client.FromEnv) 946 assert.NilError(c, err) 947 defer apiClient.Close() 948 949 _, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name) 950 assert.NilError(c, err) 951 952 err = apiClient.ContainerStart(context.Background(), name, types.ContainerStartOptions{}) 953 assert.NilError(c, err) 954 955 // second call to start should give 304 956 // maybe add ContainerStartWithRaw to test it 957 err = apiClient.ContainerStart(context.Background(), name, types.ContainerStartOptions{}) 958 assert.NilError(c, err) 959 960 // TODO(tibor): figure out why this doesn't work on windows 961 } 962 963 func (s *DockerAPISuite) TestContainerAPIStop(c *testing.T) { 964 name := "test-api-stop" 965 runSleepingContainer(c, "-i", "--name", name) 966 timeout := 30 967 968 apiClient, err := client.NewClientWithOpts(client.FromEnv) 969 assert.NilError(c, err) 970 defer apiClient.Close() 971 972 err = apiClient.ContainerStop(context.Background(), name, container.StopOptions{ 973 Timeout: &timeout, 974 }) 975 assert.NilError(c, err) 976 assert.Assert(c, waitInspect(name, "{{ .State.Running }}", "false", 60*time.Second) == nil) 977 978 // second call to start should give 304 979 // maybe add ContainerStartWithRaw to test it 980 err = apiClient.ContainerStop(context.Background(), name, container.StopOptions{ 981 Timeout: &timeout, 982 }) 983 assert.NilError(c, err) 984 } 985 986 func (s *DockerAPISuite) TestContainerAPIWait(c *testing.T) { 987 name := "test-api-wait" 988 989 sleepCmd := "/bin/sleep" 990 if testEnv.OSType == "windows" { 991 sleepCmd = "sleep" 992 } 993 dockerCmd(c, "run", "--name", name, "busybox", sleepCmd, "2") 994 995 apiClient, err := client.NewClientWithOpts(client.FromEnv) 996 assert.NilError(c, err) 997 defer apiClient.Close() 998 999 waitResC, errC := apiClient.ContainerWait(context.Background(), name, "") 1000 1001 select { 1002 case err = <-errC: 1003 assert.NilError(c, err) 1004 case waitRes := <-waitResC: 1005 assert.Equal(c, waitRes.StatusCode, int64(0)) 1006 } 1007 } 1008 1009 func (s *DockerAPISuite) TestContainerAPICopyNotExistsAnyMore(c *testing.T) { 1010 name := "test-container-api-copy" 1011 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1012 1013 postData := types.CopyConfig{ 1014 Resource: "/test.txt", 1015 } 1016 // no copy in client/ 1017 res, _, err := request.Post("/containers/"+name+"/copy", request.JSONBody(postData)) 1018 assert.NilError(c, err) 1019 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1020 } 1021 1022 func (s *DockerAPISuite) TestContainerAPICopyPre124(c *testing.T) { 1023 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1024 name := "test-container-api-copy" 1025 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1026 1027 postData := types.CopyConfig{ 1028 Resource: "/test.txt", 1029 } 1030 1031 res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1032 assert.NilError(c, err) 1033 assert.Equal(c, res.StatusCode, http.StatusOK) 1034 1035 found := false 1036 for tarReader := tar.NewReader(body); ; { 1037 h, err := tarReader.Next() 1038 if err != nil { 1039 if err == io.EOF { 1040 break 1041 } 1042 c.Fatal(err) 1043 } 1044 if h.Name == "test.txt" { 1045 found = true 1046 break 1047 } 1048 } 1049 assert.Assert(c, found) 1050 } 1051 1052 func (s *DockerAPISuite) TestContainerAPICopyResourcePathEmptyPre124(c *testing.T) { 1053 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1054 name := "test-container-api-copy-resource-empty" 1055 dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt") 1056 1057 postData := types.CopyConfig{ 1058 Resource: "", 1059 } 1060 1061 res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1062 assert.NilError(c, err) 1063 if versions.GreaterThanOrEqualTo(testEnv.DaemonAPIVersion(), "1.32") { 1064 assert.Equal(c, res.StatusCode, http.StatusBadRequest) 1065 } else { 1066 assert.Assert(c, res.StatusCode != http.StatusOK) 1067 } 1068 b, err := request.ReadBody(body) 1069 assert.NilError(c, err) 1070 assert.Assert(c, is.Regexp("^Path cannot be empty\n$", string(b))) 1071 } 1072 1073 func (s *DockerAPISuite) TestContainerAPICopyResourcePathNotFoundPre124(c *testing.T) { 1074 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1075 name := "test-container-api-copy-resource-not-found" 1076 dockerCmd(c, "run", "--name", name, "busybox") 1077 1078 postData := types.CopyConfig{ 1079 Resource: "/notexist", 1080 } 1081 1082 res, body, err := request.Post("/v1.23/containers/"+name+"/copy", request.JSONBody(postData)) 1083 assert.NilError(c, err) 1084 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 1085 assert.Equal(c, res.StatusCode, http.StatusInternalServerError) 1086 } else { 1087 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1088 } 1089 b, err := request.ReadBody(body) 1090 assert.NilError(c, err) 1091 assert.Assert(c, is.Regexp("^Could not find the file /notexist in container "+name+"\n$", string(b))) 1092 } 1093 1094 func (s *DockerAPISuite) TestContainerAPICopyContainerNotFoundPr124(c *testing.T) { 1095 testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later 1096 postData := types.CopyConfig{ 1097 Resource: "/something", 1098 } 1099 1100 res, _, err := request.Post("/v1.23/containers/notexists/copy", request.JSONBody(postData)) 1101 assert.NilError(c, err) 1102 assert.Equal(c, res.StatusCode, http.StatusNotFound) 1103 } 1104 1105 func (s *DockerAPISuite) TestContainerAPIDelete(c *testing.T) { 1106 out := runSleepingContainer(c) 1107 1108 id := strings.TrimSpace(out) 1109 assert.NilError(c, waitRun(id)) 1110 1111 dockerCmd(c, "stop", id) 1112 1113 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1114 assert.NilError(c, err) 1115 defer apiClient.Close() 1116 1117 err = apiClient.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{}) 1118 assert.NilError(c, err) 1119 } 1120 1121 func (s *DockerAPISuite) TestContainerAPIDeleteNotExist(c *testing.T) { 1122 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1123 assert.NilError(c, err) 1124 defer apiClient.Close() 1125 1126 err = apiClient.ContainerRemove(context.Background(), "doesnotexist", types.ContainerRemoveOptions{}) 1127 assert.ErrorContains(c, err, "No such container: doesnotexist") 1128 } 1129 1130 func (s *DockerAPISuite) TestContainerAPIDeleteForce(c *testing.T) { 1131 out := runSleepingContainer(c) 1132 id := strings.TrimSpace(out) 1133 assert.NilError(c, waitRun(id)) 1134 1135 removeOptions := types.ContainerRemoveOptions{ 1136 Force: true, 1137 } 1138 1139 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1140 assert.NilError(c, err) 1141 defer apiClient.Close() 1142 1143 err = apiClient.ContainerRemove(context.Background(), id, removeOptions) 1144 assert.NilError(c, err) 1145 } 1146 1147 func (s *DockerAPISuite) TestContainerAPIDeleteRemoveLinks(c *testing.T) { 1148 // Windows does not support links 1149 testRequires(c, DaemonIsLinux) 1150 out, _ := dockerCmd(c, "run", "-d", "--name", "tlink1", "busybox", "top") 1151 1152 id := strings.TrimSpace(out) 1153 assert.NilError(c, waitRun(id)) 1154 1155 out, _ = dockerCmd(c, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top") 1156 1157 id2 := strings.TrimSpace(out) 1158 assert.Assert(c, waitRun(id2) == nil) 1159 1160 links := inspectFieldJSON(c, id2, "HostConfig.Links") 1161 assert.Equal(c, links, "[\"/tlink1:/tlink2/tlink1\"]", "expected to have links between containers") 1162 1163 removeOptions := types.ContainerRemoveOptions{ 1164 RemoveLinks: true, 1165 } 1166 1167 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1168 assert.NilError(c, err) 1169 defer apiClient.Close() 1170 1171 err = apiClient.ContainerRemove(context.Background(), "tlink2/tlink1", removeOptions) 1172 assert.NilError(c, err) 1173 1174 linksPostRm := inspectFieldJSON(c, id2, "HostConfig.Links") 1175 assert.Equal(c, linksPostRm, "null", "call to api deleteContainer links should have removed the specified links") 1176 } 1177 1178 func (s *DockerAPISuite) TestContainerAPIDeleteConflict(c *testing.T) { 1179 out := runSleepingContainer(c) 1180 1181 id := strings.TrimSpace(out) 1182 assert.NilError(c, waitRun(id)) 1183 1184 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1185 assert.NilError(c, err) 1186 defer apiClient.Close() 1187 1188 err = apiClient.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{}) 1189 expected := "cannot remove a running container" 1190 assert.ErrorContains(c, err, expected) 1191 } 1192 1193 func (s *DockerAPISuite) TestContainerAPIDeleteRemoveVolume(c *testing.T) { 1194 testRequires(c, testEnv.IsLocalDaemon) 1195 1196 vol := "/testvolume" 1197 if testEnv.OSType == "windows" { 1198 vol = `c:\testvolume` 1199 } 1200 1201 out := runSleepingContainer(c, "-v", vol) 1202 1203 id := strings.TrimSpace(out) 1204 assert.NilError(c, waitRun(id)) 1205 1206 source, err := inspectMountSourceField(id, vol) 1207 assert.NilError(c, err) 1208 _, err = os.Stat(source) 1209 assert.NilError(c, err) 1210 1211 removeOptions := types.ContainerRemoveOptions{ 1212 Force: true, 1213 RemoveVolumes: true, 1214 } 1215 1216 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1217 assert.NilError(c, err) 1218 defer apiClient.Close() 1219 1220 err = apiClient.ContainerRemove(context.Background(), id, removeOptions) 1221 assert.NilError(c, err) 1222 1223 _, err = os.Stat(source) 1224 assert.Assert(c, os.IsNotExist(err), "expected to get ErrNotExist error, got %v", err) 1225 } 1226 1227 // Regression test for https://github.com/docker/docker/issues/6231 1228 func (s *DockerAPISuite) TestContainerAPIChunkedEncoding(c *testing.T) { 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 *DockerAPISuite) TestContainerAPIPostContainerStop(c *testing.T) { 1248 out := runSleepingContainer(c) 1249 1250 containerID := strings.TrimSpace(out) 1251 assert.Assert(c, waitRun(containerID) == nil) 1252 1253 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1254 assert.NilError(c, err) 1255 defer apiClient.Close() 1256 1257 err = apiClient.ContainerStop(context.Background(), containerID, container.StopOptions{}) 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 *DockerAPISuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *testing.T) { 1264 config := container.Config{ 1265 Image: "busybox", 1266 Entrypoint: []string{"echo"}, 1267 Cmd: []string{"hello", "world"}, 1268 } 1269 1270 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1271 assert.NilError(c, err) 1272 defer apiClient.Close() 1273 1274 _, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.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 *DockerAPISuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T) { 1292 config := container.Config{ 1293 Image: "busybox", 1294 Cmd: []string{"echo", "hello", "world"}, 1295 } 1296 1297 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1298 assert.NilError(c, err) 1299 defer apiClient.Close() 1300 1301 _, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.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 *DockerAPISuite) 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 := container.Config{ 1333 Image: "busybox", 1334 } 1335 hostConfig := container.HostConfig{ 1336 CapAdd: []string{"net_admin", "SYS_ADMIN"}, 1337 CapDrop: []string{"SETGID", "CAP_SETPCAP"}, 1338 } 1339 1340 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1341 assert.NilError(c, err) 1342 defer apiClient.Close() 1343 1344 _, err = apiClient.ContainerCreate(context.Background(), &config2, &hostConfig, &network.NetworkingConfig{}, nil, "capaddtest1") 1345 assert.NilError(c, err) 1346 } 1347 1348 // #14915 1349 func (s *DockerAPISuite) TestContainerAPICreateNoHostConfig118(c *testing.T) { 1350 testRequires(c, DaemonIsLinux) // Windows only support 1.25 or later 1351 config := container.Config{ 1352 Image: "busybox", 1353 } 1354 1355 apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18")) 1356 assert.NilError(c, err) 1357 1358 _, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.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 *DockerAPISuite) 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 apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.20")) 1385 assert.NilError(c, err) 1386 1387 err = apiClient.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 *DockerAPISuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T) { 1392 // Not supported on Windows 1393 testRequires(c, DaemonIsLinux) 1394 1395 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1396 assert.NilError(c, err) 1397 defer apiClient.Close() 1398 1399 config := container.Config{ 1400 Image: "busybox", 1401 } 1402 hostConfig1 := container.HostConfig{ 1403 Resources: container.Resources{ 1404 CpusetCpus: "1-42,,", 1405 }, 1406 } 1407 name := "wrong-cpuset-cpus" 1408 1409 _, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig1, &network.NetworkingConfig{}, nil, name) 1410 expected := "Invalid value 1-42,, for cpuset cpus" 1411 assert.ErrorContains(c, err, expected) 1412 1413 hostConfig2 := container.HostConfig{ 1414 Resources: container.Resources{ 1415 CpusetMems: "42-3,1--", 1416 }, 1417 } 1418 name = "wrong-cpuset-mems" 1419 _, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig2, &network.NetworkingConfig{}, nil, name) 1420 expected = "Invalid value 42-3,1-- for cpuset mems" 1421 assert.ErrorContains(c, err, expected) 1422 } 1423 1424 func (s *DockerAPISuite) TestPostContainersCreateShmSizeNegative(c *testing.T) { 1425 // ShmSize is not supported on Windows 1426 testRequires(c, DaemonIsLinux) 1427 config := container.Config{ 1428 Image: "busybox", 1429 } 1430 hostConfig := container.HostConfig{ 1431 ShmSize: -1, 1432 } 1433 1434 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1435 assert.NilError(c, err) 1436 defer apiClient.Close() 1437 1438 _, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 1439 assert.ErrorContains(c, err, "SHM size can not be less than 0") 1440 } 1441 1442 func (s *DockerAPISuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testing.T) { 1443 // ShmSize is not supported on Windows 1444 testRequires(c, DaemonIsLinux) 1445 1446 config := container.Config{ 1447 Image: "busybox", 1448 Cmd: []string{"mount"}, 1449 } 1450 1451 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1452 assert.NilError(c, err) 1453 defer apiClient.Close() 1454 1455 ctr, err := apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1456 assert.NilError(c, err) 1457 1458 containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.ID) 1459 assert.NilError(c, err) 1460 1461 assert.Equal(c, containerJSON.HostConfig.ShmSize, dconfig.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 *DockerAPISuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) { 1471 // ShmSize is not supported on Windows 1472 testRequires(c, DaemonIsLinux) 1473 config := container.Config{ 1474 Image: "busybox", 1475 Cmd: []string{"mount"}, 1476 } 1477 1478 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1479 assert.NilError(c, err) 1480 defer apiClient.Close() 1481 1482 ctr, err := apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1483 assert.NilError(c, err) 1484 1485 containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.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 *DockerAPISuite) TestPostContainersCreateWithShmSize(c *testing.T) { 1498 // ShmSize is not supported on Windows 1499 testRequires(c, DaemonIsLinux) 1500 config := container.Config{ 1501 Image: "busybox", 1502 Cmd: []string{"mount"}, 1503 } 1504 1505 hostConfig := container.HostConfig{ 1506 ShmSize: 1073741824, 1507 } 1508 1509 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1510 assert.NilError(c, err) 1511 defer apiClient.Close() 1512 1513 ctr, err := apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "") 1514 assert.NilError(c, err) 1515 1516 containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.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 *DockerAPISuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(c *testing.T) { 1529 // Swappiness is not supported on Windows 1530 testRequires(c, DaemonIsLinux) 1531 config := container.Config{ 1532 Image: "busybox", 1533 } 1534 1535 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1536 assert.NilError(c, err) 1537 defer apiClient.Close() 1538 1539 ctr, err := apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, "") 1540 assert.NilError(c, err) 1541 1542 containerJSON, err := apiClient.ContainerInspect(context.Background(), ctr.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 *DockerAPISuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *testing.T) { 1554 // OomScoreAdj is not supported on Windows 1555 testRequires(c, DaemonIsLinux) 1556 1557 config := container.Config{ 1558 Image: "busybox", 1559 } 1560 1561 hostConfig := container.HostConfig{ 1562 OomScoreAdj: 1001, 1563 } 1564 1565 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1566 assert.NilError(c, err) 1567 defer apiClient.Close() 1568 1569 name := "oomscoreadj-over" 1570 _, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.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 = container.HostConfig{ 1576 OomScoreAdj: -1001, 1577 } 1578 1579 name = "oomscoreadj-low" 1580 _, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.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 *DockerAPISuite) TestContainerAPIDeleteWithEmptyName(c *testing.T) { 1588 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1589 assert.NilError(c, err) 1590 defer apiClient.Close() 1591 1592 err = apiClient.ContainerRemove(context.Background(), "", types.ContainerRemoveOptions{}) 1593 assert.Check(c, errdefs.IsNotFound(err)) 1594 } 1595 1596 func (s *DockerAPISuite) 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 := container.Config{ 1603 Image: "busybox", 1604 Cmd: []string{"top"}, 1605 NetworkDisabled: true, 1606 } 1607 1608 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1609 assert.NilError(c, err) 1610 defer apiClient.Close() 1611 1612 _, err = apiClient.ContainerCreate(context.Background(), &config, &container.HostConfig{}, &network.NetworkingConfig{}, nil, name) 1613 assert.NilError(c, err) 1614 1615 err = apiClient.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 := apiClient.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 *DockerAPISuite) TestContainersAPICreateMountsValidation(c *testing.T) { 1646 type testCase struct { 1647 config container.Config 1648 hostConfig container.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: container.Config{ 1659 Image: "busybox", 1660 }, 1661 hostConfig: container.HostConfig{ 1662 Mounts: []mount.Mount{{ 1663 Type: "notreal", 1664 Target: destPath, 1665 }, 1666 }, 1667 }, 1668 1669 msg: "mount type unknown", 1670 }, 1671 { 1672 config: container.Config{ 1673 Image: "busybox", 1674 }, 1675 hostConfig: container.HostConfig{ 1676 Mounts: []mount.Mount{{ 1677 Type: "bind"}}}, 1678 msg: "Target must not be empty", 1679 }, 1680 { 1681 config: container.Config{ 1682 Image: "busybox", 1683 }, 1684 hostConfig: container.HostConfig{ 1685 Mounts: []mount.Mount{{ 1686 Type: "bind", 1687 Target: destPath}}}, 1688 msg: "Source must not be empty", 1689 }, 1690 { 1691 config: container.Config{ 1692 Image: "busybox", 1693 }, 1694 hostConfig: container.HostConfig{ 1695 Mounts: []mount.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: container.Config{ 1705 Image: "busybox", 1706 }, 1707 hostConfig: container.HostConfig{ 1708 Mounts: []mount.Mount{{ 1709 Type: "volume"}}}, 1710 msg: "Target must not be empty", 1711 }, 1712 { 1713 config: container.Config{ 1714 Image: "busybox", 1715 }, 1716 hostConfig: container.HostConfig{ 1717 Mounts: []mount.Mount{{ 1718 Type: "volume", 1719 Source: "hello", 1720 Target: destPath}}}, 1721 msg: "", 1722 }, 1723 { 1724 config: container.Config{ 1725 Image: "busybox", 1726 }, 1727 hostConfig: container.HostConfig{ 1728 Mounts: []mount.Mount{{ 1729 Type: "volume", 1730 Source: "hello2", 1731 Target: destPath, 1732 VolumeOptions: &mount.VolumeOptions{ 1733 DriverConfig: &mount.Driver{ 1734 Name: "local"}}}}}, 1735 msg: "", 1736 }, 1737 } 1738 1739 if testEnv.IsLocalDaemon() { 1740 tmpDir, err := os.MkdirTemp("", "test-mounts-api") 1741 assert.NilError(c, err) 1742 defer os.RemoveAll(tmpDir) 1743 cases = append(cases, []testCase{ 1744 { 1745 config: container.Config{ 1746 Image: "busybox", 1747 }, 1748 hostConfig: container.HostConfig{ 1749 Mounts: []mount.Mount{{ 1750 Type: "bind", 1751 Source: tmpDir, 1752 Target: destPath}}}, 1753 msg: "", 1754 }, 1755 { 1756 config: container.Config{ 1757 Image: "busybox", 1758 }, 1759 hostConfig: container.HostConfig{ 1760 Mounts: []mount.Mount{{ 1761 Type: "bind", 1762 Source: tmpDir, 1763 Target: destPath, 1764 VolumeOptions: &mount.VolumeOptions{}}}}, 1765 msg: "VolumeOptions must not be specified", 1766 }, 1767 }...) 1768 } 1769 1770 if DaemonIsWindows() { 1771 cases = append(cases, []testCase{ 1772 { 1773 config: container.Config{ 1774 Image: "busybox", 1775 }, 1776 hostConfig: container.HostConfig{ 1777 Mounts: []mount.Mount{ 1778 { 1779 Type: "volume", 1780 Source: "not-supported-on-windows", 1781 Target: destPath, 1782 VolumeOptions: &mount.VolumeOptions{ 1783 DriverConfig: &mount.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: container.Config{ 1800 Image: "busybox", 1801 }, 1802 hostConfig: container.HostConfig{ 1803 Mounts: []mount.Mount{ 1804 { 1805 Type: "volume", 1806 Source: "missing-device-opt", 1807 Target: destPath, 1808 VolumeOptions: &mount.VolumeOptions{ 1809 DriverConfig: &mount.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: container.Config{ 1821 Image: "busybox", 1822 }, 1823 hostConfig: container.HostConfig{ 1824 Mounts: []mount.Mount{ 1825 { 1826 Type: "volume", 1827 Source: "missing-device-opt", 1828 Target: destPath, 1829 VolumeOptions: &mount.VolumeOptions{ 1830 DriverConfig: &mount.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: container.Config{ 1842 Image: "busybox", 1843 }, 1844 hostConfig: container.HostConfig{ 1845 Mounts: []mount.Mount{ 1846 { 1847 Type: "volume", 1848 Source: "missing-type-opt", 1849 Target: destPath, 1850 VolumeOptions: &mount.VolumeOptions{ 1851 DriverConfig: &mount.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: container.Config{ 1863 Image: "busybox", 1864 }, 1865 hostConfig: container.HostConfig{ 1866 Mounts: []mount.Mount{ 1867 { 1868 Type: "volume", 1869 Source: "hello4", 1870 Target: destPath, 1871 VolumeOptions: &mount.VolumeOptions{ 1872 DriverConfig: &mount.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: container.Config{ 1884 Image: "busybox", 1885 }, 1886 hostConfig: container.HostConfig{ 1887 Mounts: []mount.Mount{{ 1888 Type: "tmpfs", 1889 Target: destPath}}}, 1890 msg: "", 1891 }, 1892 { 1893 config: container.Config{ 1894 Image: "busybox", 1895 }, 1896 hostConfig: container.HostConfig{ 1897 Mounts: []mount.Mount{{ 1898 Type: "tmpfs", 1899 Target: destPath, 1900 TmpfsOptions: &mount.TmpfsOptions{ 1901 SizeBytes: 4096 * 1024, 1902 Mode: 0700, 1903 }}}}, 1904 msg: "", 1905 }, 1906 { 1907 config: container.Config{ 1908 Image: "busybox", 1909 }, 1910 hostConfig: container.HostConfig{ 1911 Mounts: []mount.Mount{{ 1912 Type: "tmpfs", 1913 Source: "/shouldnotbespecified", 1914 Target: destPath}}}, 1915 msg: "Source must not be specified", 1916 }, 1917 }...) 1918 } 1919 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1920 assert.NilError(c, err) 1921 defer apiClient.Close() 1922 1923 // TODO add checks for statuscode returned by API 1924 for i, x := range cases { 1925 x := x 1926 c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) { 1927 _, err = apiClient.ContainerCreate(context.Background(), &x.config, &x.hostConfig, &network.NetworkingConfig{}, nil, "") 1928 if len(x.msg) > 0 { 1929 assert.ErrorContains(c, err, x.msg, "%v", cases[i].config) 1930 } else { 1931 assert.NilError(c, err) 1932 } 1933 }) 1934 } 1935 } 1936 1937 func (s *DockerAPISuite) TestContainerAPICreateMountsBindRead(c *testing.T) { 1938 testRequires(c, NotUserNamespace, testEnv.IsLocalDaemon) 1939 // also with data in the host side 1940 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1941 destPath := prefix + slash + "foo" 1942 tmpDir, err := os.MkdirTemp("", "test-mounts-api-bind") 1943 assert.NilError(c, err) 1944 defer os.RemoveAll(tmpDir) 1945 err = os.WriteFile(filepath.Join(tmpDir, "bar"), []byte("hello"), 0666) 1946 assert.NilError(c, err) 1947 config := container.Config{ 1948 Image: "busybox", 1949 Cmd: []string{"/bin/sh", "-c", "cat /foo/bar"}, 1950 } 1951 hostConfig := container.HostConfig{ 1952 Mounts: []mount.Mount{ 1953 {Type: "bind", Source: tmpDir, Target: destPath}, 1954 }, 1955 } 1956 apiClient, err := client.NewClientWithOpts(client.FromEnv) 1957 assert.NilError(c, err) 1958 defer apiClient.Close() 1959 1960 _, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "test") 1961 assert.NilError(c, err) 1962 1963 out, _ := dockerCmd(c, "start", "-a", "test") 1964 assert.Equal(c, out, "hello") 1965 } 1966 1967 // Test Mounts comes out as expected for the MountPoint 1968 func (s *DockerAPISuite) TestContainersAPICreateMountsCreate(c *testing.T) { 1969 prefix, slash := getPrefixAndSlashFromDaemonPlatform() 1970 destPath := prefix + slash + "foo" 1971 1972 var ( 1973 testImg string 1974 ) 1975 if testEnv.OSType != "windows" { 1976 testImg = "test-mount-config" 1977 buildImageSuccessfully(c, testImg, build.WithDockerfile(` 1978 FROM busybox 1979 RUN mkdir `+destPath+` && touch `+destPath+slash+`bar 1980 CMD cat `+destPath+slash+`bar 1981 `)) 1982 } else { 1983 testImg = "busybox" 1984 } 1985 1986 type testCase struct { 1987 spec mount.Mount 1988 expected types.MountPoint 1989 } 1990 1991 var selinuxSharedLabel string 1992 // this test label was added after a bug fix in 1.32, thus add requirements min API >= 1.32 1993 // for the sake of making test pass in earlier versions 1994 // bug fixed in https://github.com/moby/moby/pull/34684 1995 if !versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 1996 if runtime.GOOS == "linux" { 1997 selinuxSharedLabel = "z" 1998 } 1999 } 2000 2001 cases := []testCase{ 2002 // use literal strings here for `Type` instead of the defined constants in the volume package to keep this honest 2003 // Validation of the actual `Mount` struct is done in another test is not needed here 2004 { 2005 spec: mount.Mount{Type: "volume", Target: destPath}, 2006 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2007 }, 2008 { 2009 spec: mount.Mount{Type: "volume", Target: destPath + slash}, 2010 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2011 }, 2012 { 2013 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test1"}, 2014 expected: types.MountPoint{Type: "volume", Name: "test1", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2015 }, 2016 { 2017 spec: mount.Mount{Type: "volume", Target: destPath, ReadOnly: true, Source: "test2"}, 2018 expected: types.MountPoint{Type: "volume", Name: "test2", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 2019 }, 2020 { 2021 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test3", VolumeOptions: &mount.VolumeOptions{DriverConfig: &mount.Driver{Name: volume.DefaultDriverName}}}, 2022 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", Name: "test3", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2023 }, 2024 } 2025 2026 if testEnv.IsLocalDaemon() { 2027 // setup temp dir for testing binds 2028 tmpDir1, err := os.MkdirTemp("", "test-mounts-api-1") 2029 assert.NilError(c, err) 2030 defer os.RemoveAll(tmpDir1) 2031 cases = append(cases, []testCase{ 2032 { 2033 spec: mount.Mount{ 2034 Type: "bind", 2035 Source: tmpDir1, 2036 Target: destPath, 2037 }, 2038 expected: types.MountPoint{ 2039 Type: "bind", 2040 RW: true, 2041 Destination: destPath, 2042 Source: tmpDir1, 2043 }, 2044 }, 2045 { 2046 spec: mount.Mount{Type: "bind", Source: tmpDir1, Target: destPath, ReadOnly: true}, 2047 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir1}, 2048 }, 2049 }...) 2050 2051 // for modes only supported on Linux 2052 if DaemonIsLinux() { 2053 tmpDir3, err := os.MkdirTemp("", "test-mounts-api-3") 2054 assert.NilError(c, err) 2055 defer os.RemoveAll(tmpDir3) 2056 2057 assert.Assert(c, mountWrapper(tmpDir3, tmpDir3, "none", "bind,shared") == nil) 2058 2059 cases = append(cases, []testCase{ 2060 { 2061 spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath}, 2062 expected: types.MountPoint{Type: "bind", RW: true, Destination: destPath, Source: tmpDir3}, 2063 }, 2064 { 2065 spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true}, 2066 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3}, 2067 }, 2068 { 2069 spec: mount.Mount{Type: "bind", Source: tmpDir3, Target: destPath, ReadOnly: true, BindOptions: &mount.BindOptions{Propagation: "shared"}}, 2070 expected: types.MountPoint{Type: "bind", RW: false, Destination: destPath, Source: tmpDir3, Propagation: "shared"}, 2071 }, 2072 }...) 2073 } 2074 } 2075 2076 if testEnv.OSType != "windows" { // Windows does not support volume populate 2077 cases = append(cases, []testCase{ 2078 { 2079 spec: mount.Mount{Type: "volume", Target: destPath, VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2080 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2081 }, 2082 { 2083 spec: mount.Mount{Type: "volume", Target: destPath + slash, VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2084 expected: types.MountPoint{Driver: volume.DefaultDriverName, Type: "volume", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2085 }, 2086 { 2087 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test4", VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2088 expected: types.MountPoint{Type: "volume", Name: "test4", RW: true, Destination: destPath, Mode: selinuxSharedLabel}, 2089 }, 2090 { 2091 spec: mount.Mount{Type: "volume", Target: destPath, Source: "test5", ReadOnly: true, VolumeOptions: &mount.VolumeOptions{NoCopy: true}}, 2092 expected: types.MountPoint{Type: "volume", Name: "test5", RW: false, Destination: destPath, Mode: selinuxSharedLabel}, 2093 }, 2094 }...) 2095 } 2096 2097 ctx := context.Background() 2098 apiclient := testEnv.APIClient() 2099 for i, x := range cases { 2100 x := x 2101 c.Run(fmt.Sprintf("%d config: %v", i, x.spec), func(c *testing.T) { 2102 ctr, err := apiclient.ContainerCreate( 2103 ctx, 2104 &container.Config{Image: testImg}, 2105 &container.HostConfig{Mounts: []mount.Mount{x.spec}}, 2106 &network.NetworkingConfig{}, 2107 nil, 2108 "") 2109 assert.NilError(c, err) 2110 2111 containerInspect, err := apiclient.ContainerInspect(ctx, ctr.ID) 2112 assert.NilError(c, err) 2113 mps := containerInspect.Mounts 2114 assert.Assert(c, is.Len(mps, 1)) 2115 mountPoint := mps[0] 2116 2117 if x.expected.Source != "" { 2118 assert.Check(c, is.Equal(x.expected.Source, mountPoint.Source)) 2119 } 2120 if x.expected.Name != "" { 2121 assert.Check(c, is.Equal(x.expected.Name, mountPoint.Name)) 2122 } 2123 if x.expected.Driver != "" { 2124 assert.Check(c, is.Equal(x.expected.Driver, mountPoint.Driver)) 2125 } 2126 if x.expected.Propagation != "" { 2127 assert.Check(c, is.Equal(x.expected.Propagation, mountPoint.Propagation)) 2128 } 2129 assert.Check(c, is.Equal(x.expected.RW, mountPoint.RW)) 2130 assert.Check(c, is.Equal(x.expected.Type, mountPoint.Type)) 2131 assert.Check(c, is.Equal(x.expected.Mode, mountPoint.Mode)) 2132 assert.Check(c, is.Equal(x.expected.Destination, mountPoint.Destination)) 2133 2134 err = apiclient.ContainerStart(ctx, ctr.ID, types.ContainerStartOptions{}) 2135 assert.NilError(c, err) 2136 poll.WaitOn(c, containerExit(apiclient, ctr.ID), poll.WithDelay(time.Second)) 2137 2138 err = apiclient.ContainerRemove(ctx, ctr.ID, types.ContainerRemoveOptions{ 2139 RemoveVolumes: true, 2140 Force: true, 2141 }) 2142 assert.NilError(c, err) 2143 2144 switch { 2145 // Named volumes still exist after the container is removed 2146 case x.spec.Type == "volume" && len(x.spec.Source) > 0: 2147 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2148 assert.NilError(c, err) 2149 2150 // Bind mounts are never removed with the container 2151 case x.spec.Type == "bind": 2152 2153 // anonymous volumes are removed 2154 default: 2155 _, err := apiclient.VolumeInspect(ctx, mountPoint.Name) 2156 assert.Check(c, client.IsErrNotFound(err)) 2157 } 2158 }) 2159 } 2160 } 2161 2162 func containerExit(apiclient client.APIClient, name string) func(poll.LogT) poll.Result { 2163 return func(logT poll.LogT) poll.Result { 2164 ctr, err := apiclient.ContainerInspect(context.Background(), name) 2165 if err != nil { 2166 return poll.Error(err) 2167 } 2168 switch ctr.State.Status { 2169 case "created", "running": 2170 return poll.Continue("container %s is %s, waiting for exit", name, ctr.State.Status) 2171 } 2172 return poll.Success() 2173 } 2174 } 2175 2176 func (s *DockerAPISuite) TestContainersAPICreateMountsTmpfs(c *testing.T) { 2177 testRequires(c, DaemonIsLinux) 2178 type testCase struct { 2179 cfg mount.Mount 2180 expectedOptions []string 2181 } 2182 target := "/foo" 2183 cases := []testCase{ 2184 { 2185 cfg: mount.Mount{ 2186 Type: "tmpfs", 2187 Target: target}, 2188 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"}, 2189 }, 2190 { 2191 cfg: mount.Mount{ 2192 Type: "tmpfs", 2193 Target: target, 2194 TmpfsOptions: &mount.TmpfsOptions{ 2195 SizeBytes: 4096 * 1024, Mode: 0700}}, 2196 expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"}, 2197 }, 2198 } 2199 2200 apiClient, err := client.NewClientWithOpts(client.FromEnv) 2201 assert.NilError(c, err) 2202 defer apiClient.Close() 2203 2204 config := container.Config{ 2205 Image: "busybox", 2206 Cmd: []string{"/bin/sh", "-c", fmt.Sprintf("mount | grep 'tmpfs on %s'", target)}, 2207 } 2208 for i, x := range cases { 2209 cName := fmt.Sprintf("test-tmpfs-%d", i) 2210 hostConfig := container.HostConfig{ 2211 Mounts: []mount.Mount{x.cfg}, 2212 } 2213 2214 _, err = apiClient.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, cName) 2215 assert.NilError(c, err) 2216 out, _ := dockerCmd(c, "start", "-a", cName) 2217 for _, option := range x.expectedOptions { 2218 assert.Assert(c, strings.Contains(out, option)) 2219 } 2220 } 2221 } 2222 2223 // Regression test for #33334 2224 // Makes sure that when a container which has a custom stop signal + restart=always 2225 // gets killed (with SIGKILL) by the kill API, that the restart policy is cancelled. 2226 func (s *DockerAPISuite) TestContainerKillCustomStopSignal(c *testing.T) { 2227 id := strings.TrimSpace(runSleepingContainer(c, "--stop-signal=SIGTERM", "--restart=always")) 2228 res, _, err := request.Post("/containers/" + id + "/kill") 2229 assert.NilError(c, err) 2230 defer res.Body.Close() 2231 2232 b, err := io.ReadAll(res.Body) 2233 assert.NilError(c, err) 2234 assert.Equal(c, res.StatusCode, http.StatusNoContent, string(b)) 2235 err = waitInspect(id, "{{.State.Running}} {{.State.Restarting}}", "false false", 30*time.Second) 2236 assert.NilError(c, err) 2237 }