github.com/rumpl/bof@v23.0.0-rc.2+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 specs "github.com/opencontainers/image-spec/specs-go/v1" 21 "gotest.tools/v3/assert" 22 is "gotest.tools/v3/assert/cmp" 23 "gotest.tools/v3/poll" 24 "gotest.tools/v3/skip" 25 ) 26 27 func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) { 28 defer setupTest(t)() 29 client := testEnv.APIClient() 30 31 testCases := []struct { 32 doc string 33 image string 34 expectedError string 35 }{ 36 { 37 doc: "image and tag", 38 image: "test456:v1", 39 expectedError: "No such image: test456:v1", 40 }, 41 { 42 doc: "image no tag", 43 image: "test456", 44 expectedError: "No such image: test456", 45 }, 46 { 47 doc: "digest", 48 image: "sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa", 49 expectedError: "No such image: sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa", 50 }, 51 } 52 53 for _, tc := range testCases { 54 tc := tc 55 t.Run(tc.doc, func(t *testing.T) { 56 t.Parallel() 57 _, err := client.ContainerCreate(context.Background(), 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 defer setupTest(t)() 76 c := testEnv.APIClient() 77 78 _, err := c.ContainerCreate(context.Background(), 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 defer 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 _, err := client.ContainerCreate(context.Background(), 120 &container.Config{ 121 Image: "busybox", 122 Env: []string{tc.env}, 123 }, 124 &container.HostConfig{}, 125 &network.NetworkingConfig{}, 126 nil, 127 "", 128 ) 129 assert.Check(t, is.ErrorContains(err, tc.expectedError)) 130 assert.Check(t, errdefs.IsInvalidParameter(err)) 131 }) 132 } 133 } 134 135 // Test case for #30166 (target was not validated) 136 func TestCreateTmpfsMountsTarget(t *testing.T) { 137 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 138 139 defer setupTest(t)() 140 client := testEnv.APIClient() 141 142 testCases := []struct { 143 target string 144 expectedError string 145 }{ 146 { 147 target: ".", 148 expectedError: "mount path must be absolute", 149 }, 150 { 151 target: "foo", 152 expectedError: "mount path must be absolute", 153 }, 154 { 155 target: "/", 156 expectedError: "destination can't be '/'", 157 }, 158 { 159 target: "//", 160 expectedError: "destination can't be '/'", 161 }, 162 } 163 164 for _, tc := range testCases { 165 _, err := client.ContainerCreate(context.Background(), 166 &container.Config{ 167 Image: "busybox", 168 }, 169 &container.HostConfig{ 170 Tmpfs: map[string]string{tc.target: ""}, 171 }, 172 &network.NetworkingConfig{}, 173 nil, 174 "", 175 ) 176 assert.Check(t, is.ErrorContains(err, tc.expectedError)) 177 assert.Check(t, errdefs.IsInvalidParameter(err)) 178 } 179 } 180 func TestCreateWithCustomMaskedPaths(t *testing.T) { 181 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 182 183 defer setupTest(t)() 184 client := testEnv.APIClient() 185 ctx := context.Background() 186 187 testCases := []struct { 188 maskedPaths []string 189 expected []string 190 }{ 191 { 192 maskedPaths: []string{}, 193 expected: []string{}, 194 }, 195 { 196 maskedPaths: nil, 197 expected: oci.DefaultSpec().Linux.MaskedPaths, 198 }, 199 { 200 maskedPaths: []string{"/proc/kcore", "/proc/keys"}, 201 expected: []string{"/proc/kcore", "/proc/keys"}, 202 }, 203 } 204 205 checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) { 206 _, b, err := client.ContainerInspectWithRaw(ctx, name, false) 207 assert.NilError(t, err) 208 209 var inspectJSON map[string]interface{} 210 err = json.Unmarshal(b, &inspectJSON) 211 assert.NilError(t, err) 212 213 cfg, ok := inspectJSON["HostConfig"].(map[string]interface{}) 214 assert.Check(t, is.Equal(true, ok), name) 215 216 maskedPaths, ok := cfg["MaskedPaths"].([]interface{}) 217 assert.Check(t, is.Equal(true, ok), name) 218 219 mps := []string{} 220 for _, mp := range maskedPaths { 221 mps = append(mps, mp.(string)) 222 } 223 224 assert.DeepEqual(t, expected, mps) 225 } 226 227 for i, tc := range testCases { 228 name := fmt.Sprintf("create-masked-paths-%d", i) 229 config := container.Config{ 230 Image: "busybox", 231 Cmd: []string{"true"}, 232 } 233 hc := container.HostConfig{} 234 if tc.maskedPaths != nil { 235 hc.MaskedPaths = tc.maskedPaths 236 } 237 238 // Create the container. 239 c, err := client.ContainerCreate(context.Background(), 240 &config, 241 &hc, 242 &network.NetworkingConfig{}, 243 nil, 244 name, 245 ) 246 assert.NilError(t, err) 247 248 checkInspect(t, ctx, name, tc.expected) 249 250 // Start the container. 251 err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}) 252 assert.NilError(t, err) 253 254 poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond)) 255 256 checkInspect(t, ctx, name, tc.expected) 257 } 258 } 259 260 func TestCreateWithCustomReadonlyPaths(t *testing.T) { 261 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 262 263 defer setupTest(t)() 264 client := testEnv.APIClient() 265 ctx := context.Background() 266 267 testCases := []struct { 268 readonlyPaths []string 269 expected []string 270 }{ 271 { 272 readonlyPaths: []string{}, 273 expected: []string{}, 274 }, 275 { 276 readonlyPaths: nil, 277 expected: oci.DefaultSpec().Linux.ReadonlyPaths, 278 }, 279 { 280 readonlyPaths: []string{"/proc/asound", "/proc/bus"}, 281 expected: []string{"/proc/asound", "/proc/bus"}, 282 }, 283 } 284 285 checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) { 286 _, b, err := client.ContainerInspectWithRaw(ctx, name, false) 287 assert.NilError(t, err) 288 289 var inspectJSON map[string]interface{} 290 err = json.Unmarshal(b, &inspectJSON) 291 assert.NilError(t, err) 292 293 cfg, ok := inspectJSON["HostConfig"].(map[string]interface{}) 294 assert.Check(t, is.Equal(true, ok), name) 295 296 readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{}) 297 assert.Check(t, is.Equal(true, ok), name) 298 299 rops := []string{} 300 for _, rop := range readonlyPaths { 301 rops = append(rops, rop.(string)) 302 } 303 assert.DeepEqual(t, expected, rops) 304 } 305 306 for i, tc := range testCases { 307 name := fmt.Sprintf("create-readonly-paths-%d", i) 308 config := container.Config{ 309 Image: "busybox", 310 Cmd: []string{"true"}, 311 } 312 hc := container.HostConfig{} 313 if tc.readonlyPaths != nil { 314 hc.ReadonlyPaths = tc.readonlyPaths 315 } 316 317 // Create the container. 318 c, err := client.ContainerCreate(context.Background(), 319 &config, 320 &hc, 321 &network.NetworkingConfig{}, 322 nil, 323 name, 324 ) 325 assert.NilError(t, err) 326 327 checkInspect(t, ctx, name, tc.expected) 328 329 // Start the container. 330 err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}) 331 assert.NilError(t, err) 332 333 poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond)) 334 335 checkInspect(t, ctx, name, tc.expected) 336 } 337 } 338 339 func TestCreateWithInvalidHealthcheckParams(t *testing.T) { 340 defer setupTest(t)() 341 client := testEnv.APIClient() 342 ctx := context.Background() 343 344 testCases := []struct { 345 doc string 346 interval time.Duration 347 timeout time.Duration 348 retries int 349 startPeriod time.Duration 350 expectedErr string 351 }{ 352 { 353 doc: "test invalid Interval in Healthcheck: less than 0s", 354 interval: -10 * time.Millisecond, 355 timeout: time.Second, 356 retries: 1000, 357 expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration), 358 }, 359 { 360 doc: "test invalid Interval in Healthcheck: larger than 0s but less than 1ms", 361 interval: 500 * time.Microsecond, 362 timeout: time.Second, 363 retries: 1000, 364 expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration), 365 }, 366 { 367 doc: "test invalid Timeout in Healthcheck: less than 1ms", 368 interval: time.Second, 369 timeout: -100 * time.Millisecond, 370 retries: 1000, 371 expectedErr: fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration), 372 }, 373 { 374 doc: "test invalid Retries in Healthcheck: less than 0", 375 interval: time.Second, 376 timeout: time.Second, 377 retries: -10, 378 expectedErr: "Retries in Healthcheck cannot be negative", 379 }, 380 { 381 doc: "test invalid StartPeriod in Healthcheck: not 0 and less than 1ms", 382 interval: time.Second, 383 timeout: time.Second, 384 retries: 1000, 385 startPeriod: 100 * time.Microsecond, 386 expectedErr: fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration), 387 }, 388 } 389 390 for _, tc := range testCases { 391 tc := tc 392 t.Run(tc.doc, func(t *testing.T) { 393 t.Parallel() 394 cfg := container.Config{ 395 Image: "busybox", 396 Healthcheck: &container.HealthConfig{ 397 Interval: tc.interval, 398 Timeout: tc.timeout, 399 Retries: tc.retries, 400 }, 401 } 402 if tc.startPeriod != 0 { 403 cfg.Healthcheck.StartPeriod = tc.startPeriod 404 } 405 406 resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, nil, "") 407 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 408 409 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 410 assert.Check(t, errdefs.IsSystem(err)) 411 } else { 412 assert.Check(t, errdefs.IsInvalidParameter(err)) 413 } 414 assert.ErrorContains(t, err, tc.expectedErr) 415 }) 416 } 417 } 418 419 // Make sure that anonymous volumes can be overritten by tmpfs 420 // https://github.com/moby/moby/issues/40446 421 func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) { 422 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "windows does not support tmpfs") 423 defer setupTest(t)() 424 client := testEnv.APIClient() 425 ctx := context.Background() 426 427 id := ctr.Create(ctx, t, client, 428 ctr.WithVolume("/foo"), 429 ctr.WithTmpfs("/foo"), 430 ctr.WithVolume("/bar"), 431 ctr.WithTmpfs("/bar:size=999"), 432 ctr.WithCmd("/bin/sh", "-c", "mount | grep '/foo' | grep tmpfs && mount | grep '/bar' | grep tmpfs"), 433 ) 434 435 defer func() { 436 err := client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true}) 437 assert.NilError(t, err) 438 }() 439 440 inspect, err := client.ContainerInspect(ctx, id) 441 assert.NilError(t, err) 442 // tmpfs do not currently get added to inspect.Mounts 443 // Normally an anonymous volume would, except now tmpfs should prevent that. 444 assert.Assert(t, is.Len(inspect.Mounts, 0)) 445 446 chWait, chErr := client.ContainerWait(ctx, id, container.WaitConditionNextExit) 447 assert.NilError(t, client.ContainerStart(ctx, id, types.ContainerStartOptions{})) 448 449 timeout := time.NewTimer(30 * time.Second) 450 defer timeout.Stop() 451 452 select { 453 case <-timeout.C: 454 t.Fatal("timeout waiting for container to exit") 455 case status := <-chWait: 456 var errMsg string 457 if status.Error != nil { 458 errMsg = status.Error.Message 459 } 460 assert.Equal(t, int(status.StatusCode), 0, errMsg) 461 case err := <-chErr: 462 assert.NilError(t, err) 463 } 464 } 465 466 // Test that if the referenced image platform does not match the requested platform on container create that we get an 467 // error. 468 func TestCreateDifferentPlatform(t *testing.T) { 469 defer setupTest(t)() 470 c := testEnv.APIClient() 471 ctx := context.Background() 472 473 img, _, err := c.ImageInspectWithRaw(ctx, "busybox:latest") 474 assert.NilError(t, err) 475 assert.Assert(t, img.Architecture != "") 476 477 t.Run("different os", func(t *testing.T) { 478 p := specs.Platform{ 479 OS: img.Os + "DifferentOS", 480 Architecture: img.Architecture, 481 Variant: img.Variant, 482 } 483 _, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "") 484 assert.Assert(t, client.IsErrNotFound(err), err) 485 }) 486 t.Run("different cpu arch", func(t *testing.T) { 487 p := specs.Platform{ 488 OS: img.Os, 489 Architecture: img.Architecture + "DifferentArch", 490 Variant: img.Variant, 491 } 492 _, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "") 493 assert.Assert(t, client.IsErrNotFound(err), err) 494 }) 495 } 496 497 func TestCreateVolumesFromNonExistingContainer(t *testing.T) { 498 defer setupTest(t)() 499 cli := testEnv.APIClient() 500 501 _, err := cli.ContainerCreate( 502 context.Background(), 503 &container.Config{Image: "busybox"}, 504 &container.HostConfig{VolumesFrom: []string{"nosuchcontainer"}}, 505 nil, 506 nil, 507 "", 508 ) 509 assert.Check(t, errdefs.IsInvalidParameter(err)) 510 } 511 512 // Test that we can create a container from an image that is for a different platform even if a platform was not specified 513 // This is for the regression detailed here: https://github.com/moby/moby/issues/41552 514 func TestCreatePlatformSpecificImageNoPlatform(t *testing.T) { 515 defer setupTest(t)() 516 517 skip.If(t, testEnv.DaemonInfo.Architecture == "arm", "test only makes sense to run on non-arm systems") 518 skip.If(t, testEnv.OSType != "linux", "test image is only available on linux") 519 cli := testEnv.APIClient() 520 521 _, err := cli.ContainerCreate( 522 context.Background(), 523 &container.Config{Image: "arm32v7/hello-world"}, 524 &container.HostConfig{}, 525 nil, 526 nil, 527 "", 528 ) 529 assert.NilError(t, err) 530 } 531 532 func TestCreateInvalidHostConfig(t *testing.T) { 533 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 534 535 defer setupTest(t)() 536 apiClient := testEnv.APIClient() 537 ctx := context.Background() 538 539 testCases := []struct { 540 doc string 541 hc containertypes.HostConfig 542 expectedErr string 543 }{ 544 { 545 doc: "invalid IpcMode", 546 hc: containertypes.HostConfig{IpcMode: "invalid"}, 547 expectedErr: "Error response from daemon: invalid IPC mode: invalid", 548 }, 549 { 550 doc: "invalid PidMode", 551 hc: containertypes.HostConfig{PidMode: "invalid"}, 552 expectedErr: "Error response from daemon: invalid PID mode: invalid", 553 }, 554 { 555 doc: "invalid PidMode without container ID", 556 hc: containertypes.HostConfig{PidMode: "container"}, 557 expectedErr: "Error response from daemon: invalid PID mode: container", 558 }, 559 { 560 doc: "invalid UTSMode", 561 hc: containertypes.HostConfig{UTSMode: "invalid"}, 562 expectedErr: "Error response from daemon: invalid UTS mode: invalid", 563 }, 564 } 565 566 for _, tc := range testCases { 567 tc := tc 568 t.Run(tc.doc, func(t *testing.T) { 569 t.Parallel() 570 cfg := container.Config{ 571 Image: "busybox", 572 } 573 resp, err := apiClient.ContainerCreate(ctx, &cfg, &tc.hc, nil, nil, "") 574 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 575 assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T", err) 576 assert.Error(t, err, tc.expectedErr) 577 }) 578 } 579 }