github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration/container/create_test.go (about) 1 package container // import "github.com/Prakhar-Agarwal-byte/moby/integration/container" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "strconv" 8 "testing" 9 "time" 10 11 "github.com/Prakhar-Agarwal-byte/moby/api/types/container" 12 "github.com/Prakhar-Agarwal-byte/moby/api/types/network" 13 "github.com/Prakhar-Agarwal-byte/moby/api/types/versions" 14 "github.com/Prakhar-Agarwal-byte/moby/client" 15 "github.com/Prakhar-Agarwal-byte/moby/errdefs" 16 ctr "github.com/Prakhar-Agarwal-byte/moby/integration/internal/container" 17 "github.com/Prakhar-Agarwal-byte/moby/oci" 18 "github.com/Prakhar-Agarwal-byte/moby/testutil" 19 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 20 "gotest.tools/v3/assert" 21 is "gotest.tools/v3/assert/cmp" 22 "gotest.tools/v3/poll" 23 "gotest.tools/v3/skip" 24 ) 25 26 func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) { 27 ctx := setupTest(t) 28 client := testEnv.APIClient() 29 30 testCases := []struct { 31 doc string 32 image string 33 expectedError string 34 }{ 35 { 36 doc: "image and tag", 37 image: "test456:v1", 38 expectedError: "No such image: test456:v1", 39 }, 40 { 41 doc: "image no tag", 42 image: "test456", 43 expectedError: "No such image: test456", 44 }, 45 { 46 doc: "digest", 47 image: "sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa", 48 expectedError: "No such image: sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa", 49 }, 50 } 51 52 for _, tc := range testCases { 53 tc := tc 54 t.Run(tc.doc, func(t *testing.T) { 55 t.Parallel() 56 ctx := testutil.StartSpan(ctx, t) 57 _, err := client.ContainerCreate(ctx, 58 &container.Config{Image: tc.image}, 59 &container.HostConfig{}, 60 &network.NetworkingConfig{}, 61 nil, 62 "", 63 ) 64 assert.Check(t, is.ErrorContains(err, tc.expectedError)) 65 assert.Check(t, errdefs.IsNotFound(err)) 66 }) 67 } 68 } 69 70 // TestCreateLinkToNonExistingContainer verifies that linking to a non-existing 71 // container returns an "invalid parameter" (400) status, and not the underlying 72 // "non exists" (404). 73 func TestCreateLinkToNonExistingContainer(t *testing.T) { 74 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "legacy links are not supported on windows") 75 ctx := setupTest(t) 76 c := testEnv.APIClient() 77 78 _, err := c.ContainerCreate(ctx, 79 &container.Config{ 80 Image: "busybox", 81 }, 82 &container.HostConfig{ 83 Links: []string{"no-such-container"}, 84 }, 85 &network.NetworkingConfig{}, 86 nil, 87 "", 88 ) 89 assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container")) 90 assert.Check(t, errdefs.IsInvalidParameter(err)) 91 } 92 93 func TestCreateWithInvalidEnv(t *testing.T) { 94 ctx := setupTest(t) 95 client := testEnv.APIClient() 96 97 testCases := []struct { 98 env string 99 expectedError string 100 }{ 101 { 102 env: "", 103 expectedError: "invalid environment variable:", 104 }, 105 { 106 env: "=", 107 expectedError: "invalid environment variable: =", 108 }, 109 { 110 env: "=foo", 111 expectedError: "invalid environment variable: =foo", 112 }, 113 } 114 115 for index, tc := range testCases { 116 tc := tc 117 t.Run(strconv.Itoa(index), func(t *testing.T) { 118 t.Parallel() 119 ctx := testutil.StartSpan(ctx, t) 120 _, err := client.ContainerCreate(ctx, 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 ctx := setupTest(t) 140 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(ctx, 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 182 func TestCreateWithCustomMaskedPaths(t *testing.T) { 183 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 184 185 ctx := setupTest(t) 186 apiClient := testEnv.APIClient() 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 := apiClient.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 // TODO: This should be using subtests 229 230 for i, tc := range testCases { 231 name := fmt.Sprintf("create-masked-paths-%d", i) 232 config := container.Config{ 233 Image: "busybox", 234 Cmd: []string{"true"}, 235 } 236 hc := container.HostConfig{} 237 if tc.maskedPaths != nil { 238 hc.MaskedPaths = tc.maskedPaths 239 } 240 241 // Create the container. 242 c, err := apiClient.ContainerCreate(ctx, 243 &config, 244 &hc, 245 &network.NetworkingConfig{}, 246 nil, 247 name, 248 ) 249 assert.NilError(t, err) 250 251 checkInspect(t, ctx, name, tc.expected) 252 253 // Start the container. 254 err = apiClient.ContainerStart(ctx, c.ID, container.StartOptions{}) 255 assert.NilError(t, err) 256 257 poll.WaitOn(t, ctr.IsInState(ctx, apiClient, c.ID, "exited"), poll.WithDelay(100*time.Millisecond)) 258 259 checkInspect(t, ctx, name, tc.expected) 260 } 261 } 262 263 func TestCreateWithCustomReadonlyPaths(t *testing.T) { 264 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 265 266 ctx := setupTest(t) 267 apiClient := testEnv.APIClient() 268 269 testCases := []struct { 270 readonlyPaths []string 271 expected []string 272 }{ 273 { 274 readonlyPaths: []string{}, 275 expected: []string{}, 276 }, 277 { 278 readonlyPaths: nil, 279 expected: oci.DefaultSpec().Linux.ReadonlyPaths, 280 }, 281 { 282 readonlyPaths: []string{"/proc/asound", "/proc/bus"}, 283 expected: []string{"/proc/asound", "/proc/bus"}, 284 }, 285 } 286 287 checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) { 288 _, b, err := apiClient.ContainerInspectWithRaw(ctx, name, false) 289 assert.NilError(t, err) 290 291 var inspectJSON map[string]interface{} 292 err = json.Unmarshal(b, &inspectJSON) 293 assert.NilError(t, err) 294 295 cfg, ok := inspectJSON["HostConfig"].(map[string]interface{}) 296 assert.Check(t, is.Equal(true, ok), name) 297 298 readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{}) 299 assert.Check(t, is.Equal(true, ok), name) 300 301 rops := []string{} 302 for _, rop := range readonlyPaths { 303 rops = append(rops, rop.(string)) 304 } 305 assert.DeepEqual(t, expected, rops) 306 } 307 308 for i, tc := range testCases { 309 name := fmt.Sprintf("create-readonly-paths-%d", i) 310 config := container.Config{ 311 Image: "busybox", 312 Cmd: []string{"true"}, 313 } 314 hc := container.HostConfig{} 315 if tc.readonlyPaths != nil { 316 hc.ReadonlyPaths = tc.readonlyPaths 317 } 318 319 // Create the container. 320 c, err := apiClient.ContainerCreate(ctx, 321 &config, 322 &hc, 323 &network.NetworkingConfig{}, 324 nil, 325 name, 326 ) 327 assert.NilError(t, err) 328 329 checkInspect(t, ctx, name, tc.expected) 330 331 // Start the container. 332 err = apiClient.ContainerStart(ctx, c.ID, container.StartOptions{}) 333 assert.NilError(t, err) 334 335 poll.WaitOn(t, ctr.IsInState(ctx, apiClient, c.ID, "exited"), poll.WithDelay(100*time.Millisecond)) 336 337 checkInspect(t, ctx, name, tc.expected) 338 } 339 } 340 341 func TestCreateWithInvalidHealthcheckParams(t *testing.T) { 342 ctx := setupTest(t) 343 apiClient := testEnv.APIClient() 344 345 testCases := []struct { 346 doc string 347 interval time.Duration 348 timeout time.Duration 349 retries int 350 startPeriod time.Duration 351 startInterval time.Duration 352 expectedErr string 353 }{ 354 { 355 doc: "test invalid Interval in Healthcheck: less than 0s", 356 interval: -10 * time.Millisecond, 357 timeout: time.Second, 358 retries: 1000, 359 expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration), 360 }, 361 { 362 doc: "test invalid Interval in Healthcheck: larger than 0s but less than 1ms", 363 interval: 500 * time.Microsecond, 364 timeout: time.Second, 365 retries: 1000, 366 expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration), 367 }, 368 { 369 doc: "test invalid Timeout in Healthcheck: less than 1ms", 370 interval: time.Second, 371 timeout: -100 * time.Millisecond, 372 retries: 1000, 373 expectedErr: fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration), 374 }, 375 { 376 doc: "test invalid Retries in Healthcheck: less than 0", 377 interval: time.Second, 378 timeout: time.Second, 379 retries: -10, 380 expectedErr: "Retries in Healthcheck cannot be negative", 381 }, 382 { 383 doc: "test invalid StartPeriod in Healthcheck: not 0 and less than 1ms", 384 interval: time.Second, 385 timeout: time.Second, 386 retries: 1000, 387 startPeriod: 100 * time.Microsecond, 388 expectedErr: fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration), 389 }, 390 { 391 doc: "test invalid StartInterval in Healthcheck: not 0 and less than 1ms", 392 interval: time.Second, 393 timeout: time.Second, 394 retries: 1000, 395 startPeriod: time.Second, 396 startInterval: 100 * time.Microsecond, 397 expectedErr: fmt.Sprintf("StartInterval in Healthcheck cannot be less than %s", container.MinimumDuration), 398 }, 399 } 400 401 for _, tc := range testCases { 402 tc := tc 403 t.Run(tc.doc, func(t *testing.T) { 404 t.Parallel() 405 ctx := testutil.StartSpan(ctx, t) 406 cfg := container.Config{ 407 Image: "busybox", 408 Healthcheck: &container.HealthConfig{ 409 Interval: tc.interval, 410 Timeout: tc.timeout, 411 Retries: tc.retries, 412 StartInterval: tc.startInterval, 413 }, 414 } 415 if tc.startPeriod != 0 { 416 cfg.Healthcheck.StartPeriod = tc.startPeriod 417 } 418 419 resp, err := apiClient.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, nil, "") 420 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 421 422 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 423 assert.Check(t, errdefs.IsSystem(err)) 424 } else { 425 assert.Check(t, errdefs.IsInvalidParameter(err)) 426 } 427 assert.ErrorContains(t, err, tc.expectedErr) 428 }) 429 } 430 } 431 432 // Make sure that anonymous volumes can be overritten by tmpfs 433 // https://github.com/moby/moby/issues/40446 434 func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) { 435 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "windows does not support tmpfs") 436 ctx := setupTest(t) 437 apiClient := testEnv.APIClient() 438 439 id := ctr.Create(ctx, t, apiClient, 440 ctr.WithVolume("/foo"), 441 ctr.WithTmpfs("/foo"), 442 ctr.WithVolume("/bar"), 443 ctr.WithTmpfs("/bar:size=999"), 444 ctr.WithCmd("/bin/sh", "-c", "mount | grep '/foo' | grep tmpfs && mount | grep '/bar' | grep tmpfs"), 445 ) 446 447 defer func() { 448 err := apiClient.ContainerRemove(ctx, id, container.RemoveOptions{Force: true}) 449 assert.NilError(t, err) 450 }() 451 452 inspect, err := apiClient.ContainerInspect(ctx, id) 453 assert.NilError(t, err) 454 // tmpfs do not currently get added to inspect.Mounts 455 // Normally an anonymous volume would, except now tmpfs should prevent that. 456 assert.Assert(t, is.Len(inspect.Mounts, 0)) 457 458 chWait, chErr := apiClient.ContainerWait(ctx, id, container.WaitConditionNextExit) 459 assert.NilError(t, apiClient.ContainerStart(ctx, id, container.StartOptions{})) 460 461 timeout := time.NewTimer(30 * time.Second) 462 defer timeout.Stop() 463 464 select { 465 case <-timeout.C: 466 t.Fatal("timeout waiting for container to exit") 467 case status := <-chWait: 468 var errMsg string 469 if status.Error != nil { 470 errMsg = status.Error.Message 471 } 472 assert.Equal(t, int(status.StatusCode), 0, errMsg) 473 case err := <-chErr: 474 assert.NilError(t, err) 475 } 476 } 477 478 // Test that if the referenced image platform does not match the requested platform on container create that we get an 479 // error. 480 func TestCreateDifferentPlatform(t *testing.T) { 481 ctx := setupTest(t) 482 apiClient := testEnv.APIClient() 483 484 img, _, err := apiClient.ImageInspectWithRaw(ctx, "busybox:latest") 485 assert.NilError(t, err) 486 assert.Assert(t, img.Architecture != "") 487 488 t.Run("different os", func(t *testing.T) { 489 ctx := testutil.StartSpan(ctx, t) 490 p := ocispec.Platform{ 491 OS: img.Os + "DifferentOS", 492 Architecture: img.Architecture, 493 Variant: img.Variant, 494 } 495 _, err := apiClient.ContainerCreate(ctx, &container.Config{Image: "busybox:latest"}, &container.HostConfig{}, nil, &p, "") 496 assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) 497 }) 498 t.Run("different cpu arch", func(t *testing.T) { 499 ctx := testutil.StartSpan(ctx, t) 500 p := ocispec.Platform{ 501 OS: img.Os, 502 Architecture: img.Architecture + "DifferentArch", 503 Variant: img.Variant, 504 } 505 _, err := apiClient.ContainerCreate(ctx, &container.Config{Image: "busybox:latest"}, &container.HostConfig{}, nil, &p, "") 506 assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) 507 }) 508 } 509 510 func TestCreateVolumesFromNonExistingContainer(t *testing.T) { 511 ctx := setupTest(t) 512 cli := testEnv.APIClient() 513 514 _, err := cli.ContainerCreate( 515 ctx, 516 &container.Config{Image: "busybox"}, 517 &container.HostConfig{VolumesFrom: []string{"nosuchcontainer"}}, 518 nil, 519 nil, 520 "", 521 ) 522 assert.Check(t, errdefs.IsInvalidParameter(err)) 523 } 524 525 // Test that we can create a container from an image that is for a different platform even if a platform was not specified 526 // This is for the regression detailed here: https://github.com/moby/moby/issues/41552 527 func TestCreatePlatformSpecificImageNoPlatform(t *testing.T) { 528 ctx := setupTest(t) 529 530 skip.If(t, testEnv.DaemonInfo.Architecture == "arm", "test only makes sense to run on non-arm systems") 531 skip.If(t, testEnv.DaemonInfo.OSType != "linux", "test image is only available on linux") 532 cli := testEnv.APIClient() 533 534 _, err := cli.ContainerCreate( 535 ctx, 536 &container.Config{Image: "arm32v7/hello-world"}, 537 &container.HostConfig{}, 538 nil, 539 nil, 540 "", 541 ) 542 assert.NilError(t, err) 543 } 544 545 func TestCreateInvalidHostConfig(t *testing.T) { 546 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 547 548 ctx := setupTest(t) 549 apiClient := testEnv.APIClient() 550 551 testCases := []struct { 552 doc string 553 hc container.HostConfig 554 expectedErr string 555 }{ 556 { 557 doc: "invalid IpcMode", 558 hc: container.HostConfig{IpcMode: "invalid"}, 559 expectedErr: "Error response from daemon: invalid IPC mode: invalid", 560 }, 561 { 562 doc: "invalid PidMode", 563 hc: container.HostConfig{PidMode: "invalid"}, 564 expectedErr: "Error response from daemon: invalid PID mode: invalid", 565 }, 566 { 567 doc: "invalid PidMode without container ID", 568 hc: container.HostConfig{PidMode: "container"}, 569 expectedErr: "Error response from daemon: invalid PID mode: container", 570 }, 571 { 572 doc: "invalid UTSMode", 573 hc: container.HostConfig{UTSMode: "invalid"}, 574 expectedErr: "Error response from daemon: invalid UTS mode: invalid", 575 }, 576 { 577 doc: "invalid Annotations", 578 hc: container.HostConfig{Annotations: map[string]string{"": "a"}}, 579 expectedErr: "Error response from daemon: invalid Annotations: the empty string is not permitted as an annotation key", 580 }, 581 } 582 583 for _, tc := range testCases { 584 tc := tc 585 t.Run(tc.doc, func(t *testing.T) { 586 t.Parallel() 587 ctx := testutil.StartSpan(ctx, t) 588 cfg := container.Config{ 589 Image: "busybox", 590 } 591 resp, err := apiClient.ContainerCreate(ctx, &cfg, &tc.hc, nil, nil, "") 592 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 593 assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T", err) 594 assert.Error(t, err, tc.expectedErr) 595 }) 596 } 597 } 598 599 func TestCreateWithMultipleEndpointSettings(t *testing.T) { 600 ctx := setupTest(t) 601 602 testcases := []struct { 603 apiVersion string 604 expectedErr string 605 }{ 606 {apiVersion: "1.44"}, 607 {apiVersion: "1.43", expectedErr: "Container cannot be created with multiple network endpoints"}, 608 } 609 610 for _, tc := range testcases { 611 t.Run("with API v"+tc.apiVersion, func(t *testing.T) { 612 apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion(tc.apiVersion)) 613 assert.NilError(t, err) 614 615 config := container.Config{ 616 Image: "busybox", 617 } 618 networkingConfig := network.NetworkingConfig{ 619 EndpointsConfig: map[string]*network.EndpointSettings{ 620 "net1": {}, 621 "net2": {}, 622 "net3": {}, 623 }, 624 } 625 _, err = apiClient.ContainerCreate(ctx, &config, &container.HostConfig{}, &networkingConfig, nil, "") 626 if tc.expectedErr == "" { 627 assert.NilError(t, err) 628 } else { 629 assert.ErrorContains(t, err, tc.expectedErr) 630 } 631 }) 632 } 633 }