github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/integration/container/create_test.go (about) 1 package container // import "github.com/docker/docker/integration/container" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "strconv" 8 "testing" 9 "time" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/container" 13 containertypes "github.com/docker/docker/api/types/container" 14 "github.com/docker/docker/api/types/network" 15 "github.com/docker/docker/api/types/versions" 16 "github.com/docker/docker/client" 17 "github.com/docker/docker/errdefs" 18 ctr "github.com/docker/docker/integration/internal/container" 19 "github.com/docker/docker/oci" 20 "github.com/docker/docker/testutil/request" 21 specs "github.com/opencontainers/image-spec/specs-go/v1" 22 "gotest.tools/v3/assert" 23 is "gotest.tools/v3/assert/cmp" 24 "gotest.tools/v3/poll" 25 "gotest.tools/v3/skip" 26 ) 27 28 func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) { 29 defer setupTest(t)() 30 client := testEnv.APIClient() 31 32 testCases := []struct { 33 doc string 34 image string 35 expectedError string 36 }{ 37 { 38 doc: "image and tag", 39 image: "test456:v1", 40 expectedError: "No such image: test456:v1", 41 }, 42 { 43 doc: "image no tag", 44 image: "test456", 45 expectedError: "No such image: test456", 46 }, 47 { 48 doc: "digest", 49 image: "sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa", 50 expectedError: "No such image: sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa", 51 }, 52 } 53 54 for _, tc := range testCases { 55 tc := tc 56 t.Run(tc.doc, func(t *testing.T) { 57 t.Parallel() 58 _, err := client.ContainerCreate(context.Background(), 59 &container.Config{Image: tc.image}, 60 &container.HostConfig{}, 61 &network.NetworkingConfig{}, 62 nil, 63 "", 64 ) 65 assert.Check(t, is.ErrorContains(err, tc.expectedError)) 66 assert.Check(t, errdefs.IsNotFound(err)) 67 }) 68 } 69 } 70 71 // TestCreateLinkToNonExistingContainer verifies that linking to a non-existing 72 // container returns an "invalid parameter" (400) status, and not the underlying 73 // "non exists" (404). 74 func TestCreateLinkToNonExistingContainer(t *testing.T) { 75 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "legacy links are not supported on windows") 76 defer setupTest(t)() 77 c := testEnv.APIClient() 78 79 _, err := c.ContainerCreate(context.Background(), 80 &container.Config{ 81 Image: "busybox", 82 }, 83 &container.HostConfig{ 84 Links: []string{"no-such-container"}, 85 }, 86 &network.NetworkingConfig{}, 87 nil, 88 "", 89 ) 90 assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container")) 91 assert.Check(t, errdefs.IsInvalidParameter(err)) 92 } 93 94 func TestCreateWithInvalidEnv(t *testing.T) { 95 defer setupTest(t)() 96 client := testEnv.APIClient() 97 98 testCases := []struct { 99 env string 100 expectedError string 101 }{ 102 { 103 env: "", 104 expectedError: "invalid environment variable:", 105 }, 106 { 107 env: "=", 108 expectedError: "invalid environment variable: =", 109 }, 110 { 111 env: "=foo", 112 expectedError: "invalid environment variable: =foo", 113 }, 114 } 115 116 for index, tc := range testCases { 117 tc := tc 118 t.Run(strconv.Itoa(index), func(t *testing.T) { 119 t.Parallel() 120 _, err := client.ContainerCreate(context.Background(), 121 &container.Config{ 122 Image: "busybox", 123 Env: []string{tc.env}, 124 }, 125 &container.HostConfig{}, 126 &network.NetworkingConfig{}, 127 nil, 128 "", 129 ) 130 assert.Check(t, is.ErrorContains(err, tc.expectedError)) 131 assert.Check(t, errdefs.IsInvalidParameter(err)) 132 }) 133 } 134 } 135 136 // Test case for #30166 (target was not validated) 137 func TestCreateTmpfsMountsTarget(t *testing.T) { 138 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 139 140 defer setupTest(t)() 141 client := testEnv.APIClient() 142 143 testCases := []struct { 144 target string 145 expectedError string 146 }{ 147 { 148 target: ".", 149 expectedError: "mount path must be absolute", 150 }, 151 { 152 target: "foo", 153 expectedError: "mount path must be absolute", 154 }, 155 { 156 target: "/", 157 expectedError: "destination can't be '/'", 158 }, 159 { 160 target: "//", 161 expectedError: "destination can't be '/'", 162 }, 163 } 164 165 for _, tc := range testCases { 166 _, err := client.ContainerCreate(context.Background(), 167 &container.Config{ 168 Image: "busybox", 169 }, 170 &container.HostConfig{ 171 Tmpfs: map[string]string{tc.target: ""}, 172 }, 173 &network.NetworkingConfig{}, 174 nil, 175 "", 176 ) 177 assert.Check(t, is.ErrorContains(err, tc.expectedError)) 178 assert.Check(t, errdefs.IsInvalidParameter(err)) 179 } 180 } 181 func TestCreateWithCustomMaskedPaths(t *testing.T) { 182 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 183 184 defer setupTest(t)() 185 client := testEnv.APIClient() 186 ctx := context.Background() 187 188 testCases := []struct { 189 maskedPaths []string 190 expected []string 191 }{ 192 { 193 maskedPaths: []string{}, 194 expected: []string{}, 195 }, 196 { 197 maskedPaths: nil, 198 expected: oci.DefaultSpec().Linux.MaskedPaths, 199 }, 200 { 201 maskedPaths: []string{"/proc/kcore", "/proc/keys"}, 202 expected: []string{"/proc/kcore", "/proc/keys"}, 203 }, 204 } 205 206 checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) { 207 _, b, err := client.ContainerInspectWithRaw(ctx, name, false) 208 assert.NilError(t, err) 209 210 var inspectJSON map[string]interface{} 211 err = json.Unmarshal(b, &inspectJSON) 212 assert.NilError(t, err) 213 214 cfg, ok := inspectJSON["HostConfig"].(map[string]interface{}) 215 assert.Check(t, is.Equal(true, ok), name) 216 217 maskedPaths, ok := cfg["MaskedPaths"].([]interface{}) 218 assert.Check(t, is.Equal(true, ok), name) 219 220 mps := []string{} 221 for _, mp := range maskedPaths { 222 mps = append(mps, mp.(string)) 223 } 224 225 assert.DeepEqual(t, expected, mps) 226 } 227 228 for i, tc := range testCases { 229 name := fmt.Sprintf("create-masked-paths-%d", i) 230 config := container.Config{ 231 Image: "busybox", 232 Cmd: []string{"true"}, 233 } 234 hc := container.HostConfig{} 235 if tc.maskedPaths != nil { 236 hc.MaskedPaths = tc.maskedPaths 237 } 238 239 // Create the container. 240 c, err := client.ContainerCreate(context.Background(), 241 &config, 242 &hc, 243 &network.NetworkingConfig{}, 244 nil, 245 name, 246 ) 247 assert.NilError(t, err) 248 249 checkInspect(t, ctx, name, tc.expected) 250 251 // Start the container. 252 err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}) 253 assert.NilError(t, err) 254 255 poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond)) 256 257 checkInspect(t, ctx, name, tc.expected) 258 } 259 } 260 261 func TestCreateWithCapabilities(t *testing.T) { 262 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME: test should be able to run on LCOW") 263 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "Capabilities was added in API v1.40") 264 265 defer setupTest(t)() 266 ctx := context.Background() 267 clientNew := request.NewAPIClient(t) 268 clientOld := request.NewAPIClient(t, client.WithVersion("1.39")) 269 270 testCases := []struct { 271 doc string 272 hostConfig container.HostConfig 273 expected []string 274 expectedError string 275 oldClient bool 276 }{ 277 { 278 doc: "no capabilities", 279 hostConfig: container.HostConfig{}, 280 }, 281 { 282 doc: "empty capabilities", 283 hostConfig: container.HostConfig{ 284 Capabilities: []string{}, 285 }, 286 expected: []string{}, 287 }, 288 { 289 doc: "valid capabilities", 290 hostConfig: container.HostConfig{ 291 Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}, 292 }, 293 expected: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}, 294 }, 295 { 296 doc: "invalid capabilities", 297 hostConfig: container.HostConfig{ 298 Capabilities: []string{"NET_RAW"}, 299 }, 300 expectedError: `invalid Capabilities: unknown capability: "NET_RAW"`, 301 }, 302 { 303 doc: "duplicate capabilities", 304 hostConfig: container.HostConfig{ 305 Capabilities: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"}, 306 }, 307 expected: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"}, 308 }, 309 { 310 doc: "capabilities API v1.39", 311 hostConfig: container.HostConfig{ 312 Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}, 313 }, 314 expected: nil, 315 oldClient: true, 316 }, 317 { 318 doc: "empty capadd", 319 hostConfig: container.HostConfig{ 320 Capabilities: []string{"CAP_NET_ADMIN"}, 321 CapAdd: []string{}, 322 }, 323 expected: []string{"CAP_NET_ADMIN"}, 324 }, 325 { 326 doc: "empty capdrop", 327 hostConfig: container.HostConfig{ 328 Capabilities: []string{"CAP_NET_ADMIN"}, 329 CapDrop: []string{}, 330 }, 331 expected: []string{"CAP_NET_ADMIN"}, 332 }, 333 { 334 doc: "capadd capdrop", 335 hostConfig: container.HostConfig{ 336 CapAdd: []string{"SYS_NICE", "CAP_SYS_NICE"}, 337 CapDrop: []string{"SYS_NICE", "CAP_SYS_NICE"}, 338 }, 339 }, 340 { 341 doc: "conflict with capadd", 342 hostConfig: container.HostConfig{ 343 Capabilities: []string{"CAP_NET_ADMIN"}, 344 CapAdd: []string{"SYS_NICE"}, 345 }, 346 expectedError: `conflicting options: Capabilities and CapAdd`, 347 }, 348 { 349 doc: "conflict with capdrop", 350 hostConfig: container.HostConfig{ 351 Capabilities: []string{"CAP_NET_ADMIN"}, 352 CapDrop: []string{"NET_RAW"}, 353 }, 354 expectedError: `conflicting options: Capabilities and CapDrop`, 355 }, 356 } 357 358 for _, tc := range testCases { 359 tc := tc 360 t.Run(tc.doc, func(t *testing.T) { 361 t.Parallel() 362 client := clientNew 363 if tc.oldClient { 364 client = clientOld 365 } 366 367 c, err := client.ContainerCreate(context.Background(), 368 &container.Config{Image: "busybox"}, 369 &tc.hostConfig, 370 &network.NetworkingConfig{}, 371 nil, 372 "", 373 ) 374 if tc.expectedError == "" { 375 assert.NilError(t, err) 376 ci, err := client.ContainerInspect(ctx, c.ID) 377 assert.NilError(t, err) 378 assert.Check(t, ci.HostConfig != nil) 379 assert.DeepEqual(t, tc.expected, ci.HostConfig.Capabilities) 380 } else { 381 assert.ErrorContains(t, err, tc.expectedError) 382 assert.Check(t, errdefs.IsInvalidParameter(err)) 383 } 384 }) 385 } 386 } 387 388 func TestCreateWithCustomReadonlyPaths(t *testing.T) { 389 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 390 391 defer setupTest(t)() 392 client := testEnv.APIClient() 393 ctx := context.Background() 394 395 testCases := []struct { 396 readonlyPaths []string 397 expected []string 398 }{ 399 { 400 readonlyPaths: []string{}, 401 expected: []string{}, 402 }, 403 { 404 readonlyPaths: nil, 405 expected: oci.DefaultSpec().Linux.ReadonlyPaths, 406 }, 407 { 408 readonlyPaths: []string{"/proc/asound", "/proc/bus"}, 409 expected: []string{"/proc/asound", "/proc/bus"}, 410 }, 411 } 412 413 checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) { 414 _, b, err := client.ContainerInspectWithRaw(ctx, name, false) 415 assert.NilError(t, err) 416 417 var inspectJSON map[string]interface{} 418 err = json.Unmarshal(b, &inspectJSON) 419 assert.NilError(t, err) 420 421 cfg, ok := inspectJSON["HostConfig"].(map[string]interface{}) 422 assert.Check(t, is.Equal(true, ok), name) 423 424 readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{}) 425 assert.Check(t, is.Equal(true, ok), name) 426 427 rops := []string{} 428 for _, rop := range readonlyPaths { 429 rops = append(rops, rop.(string)) 430 } 431 assert.DeepEqual(t, expected, rops) 432 } 433 434 for i, tc := range testCases { 435 name := fmt.Sprintf("create-readonly-paths-%d", i) 436 config := container.Config{ 437 Image: "busybox", 438 Cmd: []string{"true"}, 439 } 440 hc := container.HostConfig{} 441 if tc.readonlyPaths != nil { 442 hc.ReadonlyPaths = tc.readonlyPaths 443 } 444 445 // Create the container. 446 c, err := client.ContainerCreate(context.Background(), 447 &config, 448 &hc, 449 &network.NetworkingConfig{}, 450 nil, 451 name, 452 ) 453 assert.NilError(t, err) 454 455 checkInspect(t, ctx, name, tc.expected) 456 457 // Start the container. 458 err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}) 459 assert.NilError(t, err) 460 461 poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond)) 462 463 checkInspect(t, ctx, name, tc.expected) 464 } 465 } 466 467 func TestCreateWithInvalidHealthcheckParams(t *testing.T) { 468 defer setupTest(t)() 469 client := testEnv.APIClient() 470 ctx := context.Background() 471 472 testCases := []struct { 473 doc string 474 interval time.Duration 475 timeout time.Duration 476 retries int 477 startPeriod time.Duration 478 expectedErr string 479 }{ 480 { 481 doc: "test invalid Interval in Healthcheck: less than 0s", 482 interval: -10 * time.Millisecond, 483 timeout: time.Second, 484 retries: 1000, 485 expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration), 486 }, 487 { 488 doc: "test invalid Interval in Healthcheck: larger than 0s but less than 1ms", 489 interval: 500 * time.Microsecond, 490 timeout: time.Second, 491 retries: 1000, 492 expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration), 493 }, 494 { 495 doc: "test invalid Timeout in Healthcheck: less than 1ms", 496 interval: time.Second, 497 timeout: -100 * time.Millisecond, 498 retries: 1000, 499 expectedErr: fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration), 500 }, 501 { 502 doc: "test invalid Retries in Healthcheck: less than 0", 503 interval: time.Second, 504 timeout: time.Second, 505 retries: -10, 506 expectedErr: "Retries in Healthcheck cannot be negative", 507 }, 508 { 509 doc: "test invalid StartPeriod in Healthcheck: not 0 and less than 1ms", 510 interval: time.Second, 511 timeout: time.Second, 512 retries: 1000, 513 startPeriod: 100 * time.Microsecond, 514 expectedErr: fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration), 515 }, 516 } 517 518 for _, tc := range testCases { 519 tc := tc 520 t.Run(tc.doc, func(t *testing.T) { 521 t.Parallel() 522 cfg := container.Config{ 523 Image: "busybox", 524 Healthcheck: &container.HealthConfig{ 525 Interval: tc.interval, 526 Timeout: tc.timeout, 527 Retries: tc.retries, 528 }, 529 } 530 if tc.startPeriod != 0 { 531 cfg.Healthcheck.StartPeriod = tc.startPeriod 532 } 533 534 resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, nil, "") 535 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 536 537 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 538 assert.Check(t, errdefs.IsSystem(err)) 539 } else { 540 assert.Check(t, errdefs.IsInvalidParameter(err)) 541 } 542 assert.ErrorContains(t, err, tc.expectedErr) 543 }) 544 } 545 } 546 547 // Make sure that anonymous volumes can be overritten by tmpfs 548 // https://github.com/moby/moby/issues/40446 549 func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) { 550 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "windows does not support tmpfs") 551 defer setupTest(t)() 552 client := testEnv.APIClient() 553 ctx := context.Background() 554 555 id := ctr.Create(ctx, t, client, 556 ctr.WithVolume("/foo"), 557 ctr.WithTmpfs("/foo"), 558 ctr.WithVolume("/bar"), 559 ctr.WithTmpfs("/bar:size=999"), 560 ctr.WithCmd("/bin/sh", "-c", "mount | grep '/foo' | grep tmpfs && mount | grep '/bar' | grep tmpfs"), 561 ) 562 563 defer func() { 564 err := client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true}) 565 assert.NilError(t, err) 566 }() 567 568 inspect, err := client.ContainerInspect(ctx, id) 569 assert.NilError(t, err) 570 // tmpfs do not currently get added to inspect.Mounts 571 // Normally an anonymous volume would, except now tmpfs should prevent that. 572 assert.Assert(t, is.Len(inspect.Mounts, 0)) 573 574 chWait, chErr := client.ContainerWait(ctx, id, container.WaitConditionNextExit) 575 assert.NilError(t, client.ContainerStart(ctx, id, types.ContainerStartOptions{})) 576 577 timeout := time.NewTimer(30 * time.Second) 578 defer timeout.Stop() 579 580 select { 581 case <-timeout.C: 582 t.Fatal("timeout waiting for container to exit") 583 case status := <-chWait: 584 var errMsg string 585 if status.Error != nil { 586 errMsg = status.Error.Message 587 } 588 assert.Equal(t, int(status.StatusCode), 0, errMsg) 589 case err := <-chErr: 590 assert.NilError(t, err) 591 } 592 } 593 594 // Test that if the referenced image platform does not match the requested platform on container create that we get an 595 // error. 596 func TestCreateDifferentPlatform(t *testing.T) { 597 defer setupTest(t)() 598 c := testEnv.APIClient() 599 ctx := context.Background() 600 601 img, _, err := c.ImageInspectWithRaw(ctx, "busybox:latest") 602 assert.NilError(t, err) 603 assert.Assert(t, img.Architecture != "") 604 605 t.Run("different os", func(t *testing.T) { 606 p := specs.Platform{ 607 OS: img.Os + "DifferentOS", 608 Architecture: img.Architecture, 609 Variant: img.Variant, 610 } 611 _, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "") 612 assert.Assert(t, client.IsErrNotFound(err), err) 613 }) 614 t.Run("different cpu arch", func(t *testing.T) { 615 p := specs.Platform{ 616 OS: img.Os, 617 Architecture: img.Architecture + "DifferentArch", 618 Variant: img.Variant, 619 } 620 _, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "") 621 assert.Assert(t, client.IsErrNotFound(err), err) 622 }) 623 }