github.com/docker/compose-on-kubernetes@v0.5.0/internal/convert/pod_test.go (about) 1 package convert 2 3 import ( 4 "fmt" 5 "runtime" 6 "testing" 7 "time" 8 9 "github.com/docker/compose-on-kubernetes/api/compose/latest" 10 "github.com/docker/compose-on-kubernetes/internal/stackresources" 11 . "github.com/docker/compose-on-kubernetes/internal/test/builders" 12 "github.com/stretchr/testify/assert" 13 apiv1 "k8s.io/api/core/v1" 14 "k8s.io/apimachinery/pkg/api/resource" 15 ) 16 17 func podTemplate(t *testing.T, stack *latest.Stack) apiv1.PodTemplateSpec { 18 res, err := podTemplateWithError(stack) 19 assert.NoError(t, err) 20 return res 21 } 22 23 func podTemplateWithError(stack *latest.Stack) (apiv1.PodTemplateSpec, error) { 24 s, err := StackToStack(*stack, loadBalancerServiceStrategy{}, stackresources.EmptyStackState) 25 if err != nil { 26 return apiv1.PodTemplateSpec{}, err 27 } 28 for _, r := range s.Deployments { 29 return r.Spec.Template, nil 30 } 31 for _, r := range s.Daemonsets { 32 return r.Spec.Template, nil 33 } 34 for _, r := range s.Statefulsets { 35 return r.Spec.Template, nil 36 } 37 return apiv1.PodTemplateSpec{}, nil 38 } 39 40 func TestToPodWithDockerSocket(t *testing.T) { 41 if runtime.GOOS == "windows" { 42 t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet") 43 return 44 } 45 podTemplate := podTemplate(t, Stack("demo", 46 WithService("redis", 47 Image("redis:alpine"), 48 WithVolume( 49 Source("/var/run/docker.sock"), 50 Target("/var/run/docker.sock"), 51 Mount, 52 ), 53 ), 54 )) 55 56 expectedVolume := apiv1.Volume{ 57 Name: "mount-0", 58 VolumeSource: apiv1.VolumeSource{ 59 HostPath: &apiv1.HostPathVolumeSource{ 60 Path: "/var/run", 61 }, 62 }, 63 } 64 65 expectedMount := apiv1.VolumeMount{ 66 Name: "mount-0", 67 MountPath: "/var/run/docker.sock", 68 SubPath: "docker.sock", 69 } 70 71 assert.Len(t, podTemplate.Spec.Volumes, 1) 72 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 73 assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) 74 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 75 } 76 77 func TestToPodWithFunkyCommand(t *testing.T) { 78 podTemplate := podTemplate(t, Stack("demo", 79 WithService("redis", 80 Image("basi/node-exporter"), 81 Command("-collector.procfs", "/host/proc", "-collector.sysfs", "/host/sys"), 82 ), 83 )) 84 85 expectedArgs := []string{ 86 `-collector.procfs`, 87 `/host/proc`, // ? 88 `-collector.sysfs`, 89 `/host/sys`, // ? 90 } 91 assert.Equal(t, expectedArgs, podTemplate.Spec.Containers[0].Args) 92 } 93 94 func TestToPodWithGlobalVolume(t *testing.T) { 95 podTemplate := podTemplate(t, Stack("demo", 96 WithService("db", 97 Image("postgres:9.4"), 98 WithVolume( 99 Source("dbdata"), 100 Target("/var/lib/postgresql/data"), 101 Volume, 102 ), 103 ), 104 )) 105 106 expectedMount := apiv1.VolumeMount{ 107 Name: "dbdata", 108 MountPath: "/var/lib/postgresql/data", 109 } 110 assert.Len(t, podTemplate.Spec.Volumes, 0) 111 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 112 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 113 } 114 115 func TestToPodWithResources(t *testing.T) { 116 podTemplate := podTemplate(t, Stack("demo", 117 WithService("db", 118 Image("postgres:9.4"), 119 Deploy(Resources( 120 Limits(CPUs("0.001"), Memory(50*1024*1024)), 121 Reservations(CPUs("0.0001"), Memory(20*1024*1024)), 122 )), 123 ), 124 )) 125 126 expectedResourceRequirements := apiv1.ResourceRequirements{ 127 Limits: map[apiv1.ResourceName]resource.Quantity{ 128 apiv1.ResourceCPU: resource.MustParse("0.001"), 129 apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 50*1024*1024)), 130 }, 131 Requests: map[apiv1.ResourceName]resource.Quantity{ 132 apiv1.ResourceCPU: resource.MustParse("0.0001"), 133 apiv1.ResourceMemory: resource.MustParse(fmt.Sprintf("%d", 20*1024*1024)), 134 }, 135 } 136 assert.Equal(t, expectedResourceRequirements, podTemplate.Spec.Containers[0].Resources) 137 } 138 139 func TestToPodWithCapabilities(t *testing.T) { 140 podTemplate := podTemplate(t, Stack("demo", 141 WithService("redis", 142 Image("redis:alpine"), 143 WithCapAdd("ALL"), 144 WithCapDrop("NET_ADMIN", "SYS_ADMIN"), 145 ), 146 )) 147 148 expectedSecurityContext := &apiv1.SecurityContext{ 149 Capabilities: &apiv1.Capabilities{ 150 Add: []apiv1.Capability{"ALL"}, 151 Drop: []apiv1.Capability{"NET_ADMIN", "SYS_ADMIN"}, 152 }, 153 } 154 155 assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) 156 } 157 158 func TestToPodWithReadOnly(t *testing.T) { 159 podTemplate := podTemplate(t, Stack("demo", 160 WithService("redis", 161 Image("redis:alpine"), 162 ReadOnly, 163 ), 164 )) 165 166 yes := true 167 expectedSecurityContext := &apiv1.SecurityContext{ 168 ReadOnlyRootFilesystem: &yes, 169 } 170 assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) 171 } 172 173 func TestToPodWithPrivileged(t *testing.T) { 174 podTemplate := podTemplate(t, Stack("demo", 175 WithService("redis", 176 Image("redis:alpine"), 177 Privileged, 178 ), 179 )) 180 181 yes := true 182 expectedSecurityContext := &apiv1.SecurityContext{ 183 Privileged: &yes, 184 } 185 assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) 186 } 187 188 func strptr(s string) *string { 189 return &s 190 } 191 192 func TestToPodWithEnvNilShouldErrorOut(t *testing.T) { 193 stack := Stack("demo", 194 WithService("redis", 195 Image("redis:alpine"), 196 WithEnvironment("SESSION_SECRET", nil), 197 ), 198 ) 199 _, err := StackToStack(*stack, loadBalancerServiceStrategy{}, stackresources.EmptyStackState) 200 assert.Error(t, err) 201 } 202 203 func TestToPodWithEnv(t *testing.T) { 204 podTemplate := podTemplate(t, Stack("demo", 205 WithService("redis", 206 Image("redis:alpine"), 207 WithEnvironment("RACK_ENV", strptr("development")), 208 WithEnvironment("SHOW", strptr("true")), 209 ), 210 )) 211 212 expectedEnv := []apiv1.EnvVar{ 213 { 214 Name: "RACK_ENV", 215 Value: "development", 216 }, 217 { 218 Name: "SHOW", 219 Value: "true", 220 }, 221 } 222 223 assert.Equal(t, expectedEnv, podTemplate.Spec.Containers[0].Env) 224 } 225 226 func TestToPodWithVolume(t *testing.T) { 227 if runtime.GOOS == "windows" { 228 t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet") 229 return 230 } 231 podTemplate := podTemplate(t, Stack("demo", 232 WithService("nginx", 233 Image("nginx"), 234 WithVolume(Source("/ignore"), Target("/ignore"), Mount), 235 WithVolume(Source("/opt/data"), Target("/var/lib/mysql"), VolumeReadOnly, Mount), 236 ), 237 )) 238 239 assert.Len(t, podTemplate.Spec.Volumes, 2) 240 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 2) 241 } 242 243 func TestToPodWithRelativeVolumes(t *testing.T) { 244 if runtime.GOOS == "windows" { 245 t.Skip("on windows, source path validation is broken (and actually, source validation for windows workload is broken too). Skip it for now, as we don't support it yet") 246 return 247 } 248 stack := Stack("demo", 249 WithService("nginx", 250 Image("nginx"), 251 WithVolume(Source("./fail"), Target("/ignore"), Mount), 252 )) 253 _, err := StackToStack(*stack, loadBalancerServiceStrategy{}, stackresources.EmptyStackState) 254 assert.Error(t, err) 255 } 256 257 func TestToPodWithHealthCheck(t *testing.T) { 258 podTemplate := podTemplate(t, Stack("demo", 259 WithService("nginx", 260 Image("nginx"), 261 Healthcheck( 262 Test("CMD", "curl", "-f", "http://localhost"), 263 Interval(90*time.Second), 264 Timeout(10*time.Second), 265 Retries(3), 266 ), 267 ), 268 )) 269 expectedLivenessProbe := &apiv1.Probe{ 270 TimeoutSeconds: 10, 271 PeriodSeconds: 90, 272 FailureThreshold: 3, 273 Handler: apiv1.Handler{ 274 Exec: &apiv1.ExecAction{ 275 Command: []string{"curl", "-f", "http://localhost"}, 276 }, 277 }, 278 } 279 280 assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe) 281 } 282 283 func TestToPodWithShellHealthCheck(t *testing.T) { 284 podTemplate := podTemplate(t, Stack("demo", 285 WithService("nginx", 286 Image("nginx"), 287 Healthcheck( 288 Test("CMD-SHELL", "curl -f http://localhost"), 289 ), 290 ), 291 )) 292 293 expectedLivenessProbe := &apiv1.Probe{ 294 TimeoutSeconds: 1, 295 PeriodSeconds: 1, 296 FailureThreshold: 3, 297 Handler: apiv1.Handler{ 298 Exec: &apiv1.ExecAction{ 299 Command: []string{"sh", "-c", "curl -f http://localhost"}, 300 }, 301 }, 302 } 303 304 assert.Equal(t, expectedLivenessProbe, podTemplate.Spec.Containers[0].LivenessProbe) 305 } 306 307 func TestToPodWithHealthCheckEmptyCommand(t *testing.T) { 308 podTemplate := podTemplate(t, Stack("demo", 309 WithService("nginx", 310 Image("nginx"), 311 Healthcheck(Test()), 312 ), 313 )) 314 315 assert.Nil(t, podTemplate.Spec.Containers[0].LivenessProbe) 316 } 317 318 func TestToPodWithTargetlessExternalSecret(t *testing.T) { 319 podTemplate := podTemplate(t, Stack("demo", 320 WithService("nginx", 321 Image("nginx"), 322 WithSecret( 323 SecretSource("my_secret"), 324 ), 325 ), 326 )) 327 328 expectedVolume := apiv1.Volume{ 329 Name: "secret-0", 330 VolumeSource: apiv1.VolumeSource{ 331 Secret: &apiv1.SecretVolumeSource{ 332 SecretName: "my_secret", 333 Items: []apiv1.KeyToPath{ 334 { 335 Key: "file", // TODO: This is the key we assume external secrets use 336 Path: "secret-0", 337 }, 338 }, 339 }, 340 }, 341 } 342 343 expectedMount := apiv1.VolumeMount{ 344 Name: "secret-0", 345 ReadOnly: true, 346 MountPath: "/run/secrets/my_secret", 347 SubPath: "secret-0", 348 } 349 350 assert.Len(t, podTemplate.Spec.Volumes, 1) 351 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 352 assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) 353 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 354 } 355 356 func TestToPodWithExternalSecret(t *testing.T) { 357 podTemplate := podTemplate(t, Stack("demo", 358 WithService("nginx", 359 Image("nginx"), 360 WithSecret( 361 SecretSource("my_secret"), 362 SecretTarget("nginx_secret"), 363 ), 364 ), 365 )) 366 367 expectedVolume := apiv1.Volume{ 368 Name: "secret-0", 369 VolumeSource: apiv1.VolumeSource{ 370 Secret: &apiv1.SecretVolumeSource{ 371 SecretName: "my_secret", 372 Items: []apiv1.KeyToPath{ 373 { 374 Key: "file", // TODO: This is the key we assume external secrets use 375 Path: "secret-0", 376 }, 377 }, 378 }, 379 }, 380 } 381 382 expectedMount := apiv1.VolumeMount{ 383 Name: "secret-0", 384 ReadOnly: true, 385 MountPath: "/run/secrets/nginx_secret", 386 SubPath: "secret-0", 387 } 388 389 assert.Len(t, podTemplate.Spec.Volumes, 1) 390 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 391 assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) 392 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 393 } 394 395 func TestToPodWithFileBasedSecret(t *testing.T) { 396 podTemplate := podTemplate(t, Stack("demo", 397 WithService("nginx", 398 Image("nginx"), 399 WithSecret( 400 SecretSource("my_secret"), 401 ), 402 ), 403 WithSecretConfig("my_secret", SecretFile("./secret.txt")), 404 )) 405 406 expectedVolume := apiv1.Volume{ 407 Name: "secret-0", 408 VolumeSource: apiv1.VolumeSource{ 409 Secret: &apiv1.SecretVolumeSource{ 410 SecretName: "my_secret", 411 Items: []apiv1.KeyToPath{ 412 { 413 Key: "secret.txt", 414 Path: "secret-0", 415 }, 416 }, 417 }, 418 }, 419 } 420 421 expectedMount := apiv1.VolumeMount{ 422 Name: "secret-0", 423 ReadOnly: true, 424 MountPath: "/run/secrets/my_secret", 425 SubPath: "secret-0", 426 } 427 428 assert.Len(t, podTemplate.Spec.Volumes, 1) 429 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 430 assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) 431 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 432 } 433 434 func TestToPodWithTwoFileBasedSecrets(t *testing.T) { 435 podTemplate := podTemplate(t, Stack("demo", 436 WithService("nginx", 437 Image("nginx"), 438 WithSecret( 439 SecretSource("my_secret1"), 440 ), 441 WithSecret( 442 SecretSource("my_secret2"), 443 SecretTarget("secret2"), 444 ), 445 ), 446 WithSecretConfig("my_secret1", SecretFile("./secret1.txt")), 447 WithSecretConfig("my_secret2", SecretFile("./secret2.txt")), 448 )) 449 450 expectedVolumes := []apiv1.Volume{ 451 { 452 Name: "secret-0", 453 VolumeSource: apiv1.VolumeSource{ 454 Secret: &apiv1.SecretVolumeSource{ 455 SecretName: "my_secret1", 456 Items: []apiv1.KeyToPath{ 457 { 458 Key: "secret1.txt", 459 Path: "secret-0", 460 }, 461 }, 462 }, 463 }, 464 }, 465 { 466 Name: "secret-1", 467 VolumeSource: apiv1.VolumeSource{ 468 Secret: &apiv1.SecretVolumeSource{ 469 SecretName: "my_secret2", 470 Items: []apiv1.KeyToPath{ 471 { 472 Key: "secret2.txt", 473 Path: "secret-1", 474 }, 475 }, 476 }, 477 }, 478 }, 479 } 480 481 expectedMounts := []apiv1.VolumeMount{ 482 { 483 Name: "secret-0", 484 ReadOnly: true, 485 MountPath: "/run/secrets/my_secret1", 486 SubPath: "secret-0", 487 }, 488 { 489 Name: "secret-1", 490 ReadOnly: true, 491 MountPath: "/run/secrets/secret2", 492 SubPath: "secret-1", 493 }, 494 } 495 496 assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes) 497 assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts) 498 } 499 500 func TestToPodWithTerminationGracePeriod(t *testing.T) { 501 podTemplate := podTemplate(t, Stack("demo", 502 WithService("redis", 503 Image("redis:alpine"), 504 StopGracePeriod(100*time.Second), 505 ), 506 )) 507 508 expected := int64(100) 509 assert.Equal(t, &expected, podTemplate.Spec.TerminationGracePeriodSeconds) 510 } 511 512 func TestToPodWithTmpfs(t *testing.T) { 513 podTemplate := podTemplate(t, Stack("demo", 514 WithService("redis", 515 Image("redis:alpine"), 516 WithTmpFS("/tmp"), 517 ), 518 )) 519 520 expectedVolume := apiv1.Volume{ 521 Name: "tmp-0", 522 VolumeSource: apiv1.VolumeSource{ 523 EmptyDir: &apiv1.EmptyDirVolumeSource{ 524 Medium: "Memory", 525 }, 526 }, 527 } 528 529 expectedMount := apiv1.VolumeMount{ 530 Name: "tmp-0", 531 MountPath: "/tmp", 532 } 533 534 assert.Len(t, podTemplate.Spec.Volumes, 1) 535 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 536 assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) 537 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 538 } 539 540 func TestToPodWithNumericalUser(t *testing.T) { 541 podTemplate := podTemplate(t, Stack("demo", 542 WithService("redis", 543 Image("redis:alpine"), 544 User(1000), 545 ), 546 )) 547 548 userID := int64(1000) 549 550 expectedSecurityContext := &apiv1.SecurityContext{ 551 RunAsUser: &userID, 552 } 553 554 assert.Equal(t, expectedSecurityContext, podTemplate.Spec.Containers[0].SecurityContext) 555 } 556 557 func TestToPodWithGitVolume(t *testing.T) { 558 podTemplate := podTemplate(t, Stack("demo", 559 WithService("redis", 560 Image("redis:alpine"), 561 WithVolume( 562 Source("git@github.com:moby/moby.git"), 563 Target("/sources"), 564 Mount, 565 ), 566 ), 567 )) 568 569 expectedVolume := apiv1.Volume{ 570 Name: "mount-0", 571 VolumeSource: apiv1.VolumeSource{ 572 GitRepo: &apiv1.GitRepoVolumeSource{ 573 Repository: "git@github.com:moby/moby.git", 574 }, 575 }, 576 } 577 578 expectedMount := apiv1.VolumeMount{ 579 Name: "mount-0", 580 ReadOnly: false, 581 MountPath: "/sources", 582 } 583 584 assert.Len(t, podTemplate.Spec.Volumes, 1) 585 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 586 assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) 587 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 588 } 589 590 func TestToPodWithFileBasedConfig(t *testing.T) { 591 podTemplate := podTemplate(t, Stack("demo", 592 WithService("nginx", 593 Image("nginx"), 594 WithConfig( 595 ConfigSource("my_config"), 596 ConfigTarget("/usr/share/nginx/html/index.html"), 597 ConfigUID("103"), 598 ConfigGID("103"), 599 ConfigMode(0440), 600 ), 601 ), 602 WithConfigObjConfig("my_config", ConfigFile("./file.html")), 603 )) 604 605 mode := int32(0440) 606 607 expectedVolume := apiv1.Volume{ 608 Name: "config-0", 609 VolumeSource: apiv1.VolumeSource{ 610 ConfigMap: &apiv1.ConfigMapVolumeSource{ 611 LocalObjectReference: apiv1.LocalObjectReference{ 612 Name: "my_config", 613 }, 614 Items: []apiv1.KeyToPath{ 615 { 616 Key: "file.html", 617 Path: "config-0", 618 Mode: &mode, 619 }, 620 }, 621 }, 622 }, 623 } 624 625 expectedMount := apiv1.VolumeMount{ 626 Name: "config-0", 627 ReadOnly: true, 628 MountPath: "/usr/share/nginx/html/index.html", 629 SubPath: "config-0", 630 } 631 632 assert.Len(t, podTemplate.Spec.Volumes, 1) 633 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 634 assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) 635 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 636 } 637 638 func TestToPodWithTargetlessFileBasedConfig(t *testing.T) { 639 podTemplate := podTemplate(t, Stack("demo", 640 WithService("nginx", 641 Image("nginx"), 642 WithConfig( 643 ConfigSource("myconfig"), 644 ), 645 ), 646 WithConfigObjConfig("myconfig", ConfigFile("./file.html")), 647 )) 648 649 expectedVolume := apiv1.Volume{ 650 Name: "config-0", 651 VolumeSource: apiv1.VolumeSource{ 652 ConfigMap: &apiv1.ConfigMapVolumeSource{ 653 LocalObjectReference: apiv1.LocalObjectReference{ 654 Name: "myconfig", 655 }, 656 Items: []apiv1.KeyToPath{ 657 { 658 Key: "file.html", 659 Path: "config-0", 660 }, 661 }, 662 }, 663 }, 664 } 665 666 expectedMount := apiv1.VolumeMount{ 667 Name: "config-0", 668 ReadOnly: true, 669 MountPath: "/myconfig", 670 SubPath: "config-0", 671 } 672 673 assert.Len(t, podTemplate.Spec.Volumes, 1) 674 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 675 assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) 676 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 677 } 678 679 func TestToPodWithExternalConfig(t *testing.T) { 680 podTemplate := podTemplate(t, Stack("demo", 681 WithService("nginx", 682 Image("nginx"), 683 WithConfig( 684 ConfigSource("my_config"), 685 ConfigTarget("/usr/share/nginx/html/index.html"), 686 ConfigUID("103"), 687 ConfigGID("103"), 688 ConfigMode(0440), 689 ), 690 ), 691 WithConfigObjConfig("my_config", ConfigExternal), 692 )) 693 694 mode := int32(0440) 695 696 expectedVolume := apiv1.Volume{ 697 Name: "config-0", 698 VolumeSource: apiv1.VolumeSource{ 699 ConfigMap: &apiv1.ConfigMapVolumeSource{ 700 LocalObjectReference: apiv1.LocalObjectReference{ 701 Name: "my_config", 702 }, 703 Items: []apiv1.KeyToPath{ 704 { 705 Key: "file", // TODO: This is the key we assume external config use 706 Path: "config-0", 707 Mode: &mode, 708 }, 709 }, 710 }, 711 }, 712 } 713 714 expectedMount := apiv1.VolumeMount{ 715 Name: "config-0", 716 ReadOnly: true, 717 MountPath: "/usr/share/nginx/html/index.html", 718 SubPath: "config-0", 719 } 720 721 assert.Len(t, podTemplate.Spec.Volumes, 1) 722 assert.Len(t, podTemplate.Spec.Containers[0].VolumeMounts, 1) 723 assert.Equal(t, expectedVolume, podTemplate.Spec.Volumes[0]) 724 assert.Equal(t, expectedMount, podTemplate.Spec.Containers[0].VolumeMounts[0]) 725 } 726 727 func TestToPodWithTwoConfigsSameMountPoint(t *testing.T) { 728 podTemplate := podTemplate(t, Stack("demo", 729 WithService("nginx", 730 Image("nginx"), 731 WithConfig( 732 ConfigSource("first"), 733 ConfigTarget("/data/first.json"), 734 ConfigMode(0440), 735 ), 736 WithConfig( 737 ConfigSource("second"), 738 ConfigTarget("/data/second.json"), 739 ConfigMode(0550), 740 ), 741 ), 742 WithConfigObjConfig("first", ConfigFile("./file1")), 743 WithConfigObjConfig("second", ConfigFile("./file2")), 744 )) 745 746 mode0440 := int32(0440) 747 mode0550 := int32(0550) 748 749 expectedVolumes := []apiv1.Volume{ 750 { 751 Name: "config-0", 752 VolumeSource: apiv1.VolumeSource{ 753 ConfigMap: &apiv1.ConfigMapVolumeSource{ 754 LocalObjectReference: apiv1.LocalObjectReference{ 755 Name: "first", 756 }, 757 Items: []apiv1.KeyToPath{ 758 { 759 Key: "file1", 760 Path: "config-0", 761 Mode: &mode0440, 762 }, 763 }, 764 }, 765 }, 766 }, 767 { 768 Name: "config-1", 769 VolumeSource: apiv1.VolumeSource{ 770 ConfigMap: &apiv1.ConfigMapVolumeSource{ 771 LocalObjectReference: apiv1.LocalObjectReference{ 772 Name: "second", 773 }, 774 Items: []apiv1.KeyToPath{ 775 { 776 Key: "file2", 777 Path: "config-1", 778 Mode: &mode0550, 779 }, 780 }, 781 }, 782 }, 783 }, 784 } 785 786 expectedMounts := []apiv1.VolumeMount{ 787 { 788 Name: "config-0", 789 ReadOnly: true, 790 MountPath: "/data/first.json", 791 SubPath: "config-0", 792 }, 793 { 794 Name: "config-1", 795 ReadOnly: true, 796 MountPath: "/data/second.json", 797 SubPath: "config-1", 798 }, 799 } 800 801 assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes) 802 assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts) 803 } 804 805 func TestToPodWithTwoExternalConfigsSameMountPoint(t *testing.T) { 806 podTemplate := podTemplate(t, Stack("demo", 807 WithService("nginx", 808 Image("nginx"), 809 WithConfig( 810 ConfigSource("first"), 811 ConfigTarget("/data/first.json"), 812 ), 813 WithConfig( 814 ConfigSource("second"), 815 ConfigTarget("/data/second.json"), 816 ), 817 ), 818 WithConfigObjConfig("first", ConfigExternal), 819 WithConfigObjConfig("second", ConfigExternal), 820 )) 821 822 expectedVolumes := []apiv1.Volume{ 823 { 824 Name: "config-0", 825 VolumeSource: apiv1.VolumeSource{ 826 ConfigMap: &apiv1.ConfigMapVolumeSource{ 827 LocalObjectReference: apiv1.LocalObjectReference{ 828 Name: "first", 829 }, 830 Items: []apiv1.KeyToPath{ 831 { 832 Key: "file", 833 Path: "config-0", 834 }, 835 }, 836 }, 837 }, 838 }, 839 { 840 Name: "config-1", 841 VolumeSource: apiv1.VolumeSource{ 842 ConfigMap: &apiv1.ConfigMapVolumeSource{ 843 LocalObjectReference: apiv1.LocalObjectReference{ 844 Name: "second", 845 }, 846 Items: []apiv1.KeyToPath{ 847 { 848 Key: "file", 849 Path: "config-1", 850 }, 851 }, 852 }, 853 }, 854 }, 855 } 856 857 expectedMounts := []apiv1.VolumeMount{ 858 { 859 Name: "config-0", 860 ReadOnly: true, 861 MountPath: "/data/first.json", 862 SubPath: "config-0", 863 }, 864 { 865 Name: "config-1", 866 ReadOnly: true, 867 MountPath: "/data/second.json", 868 SubPath: "config-1", 869 }, 870 } 871 872 assert.Equal(t, expectedVolumes, podTemplate.Spec.Volumes) 873 assert.Equal(t, expectedMounts, podTemplate.Spec.Containers[0].VolumeMounts) 874 } 875 876 func TestToPodWithPullSecret(t *testing.T) { 877 podTemplateWithSecret := podTemplate(t, Stack("demo", 878 WithService("nginx", 879 Image("nginx"), 880 PullSecret("test-pull-secret"), 881 ))) 882 assert.Equal(t, 1, len(podTemplateWithSecret.Spec.ImagePullSecrets)) 883 assert.Equal(t, "test-pull-secret", podTemplateWithSecret.Spec.ImagePullSecrets[0].Name) 884 podTemplateNoSecret := podTemplate(t, Stack("demo", 885 WithService("nginx", 886 Image("nginx"), 887 ))) 888 assert.Nil(t, podTemplateNoSecret.Spec.ImagePullSecrets) 889 } 890 891 func TestToPodWithPullPolicy(t *testing.T) { 892 cases := []struct { 893 name string 894 stack *latest.Stack 895 expectedPolicy apiv1.PullPolicy 896 expectedError string 897 }{ 898 { 899 name: "specific tag", 900 stack: Stack("demo", 901 WithService("nginx", 902 Image("nginx:specific"), 903 )), 904 expectedPolicy: apiv1.PullIfNotPresent, 905 }, 906 { 907 name: "latest tag", 908 stack: Stack("demo", 909 WithService("nginx", 910 Image("nginx:latest"), 911 )), 912 expectedPolicy: apiv1.PullAlways, 913 }, 914 { 915 name: "explicit policy", 916 stack: Stack("demo", 917 WithService("nginx", 918 Image("nginx:latest"), 919 PullPolicy("Never"), 920 )), 921 expectedPolicy: apiv1.PullNever, 922 }, 923 { 924 name: "invalid policy", 925 stack: Stack("demo", 926 WithService("nginx", 927 Image("nginx:latest"), 928 PullPolicy("Invalid"), 929 )), 930 expectedError: `invalid pull policy "Invalid", must be "Always", "IfNotPresent" or "Never"`, 931 }, 932 } 933 934 for _, c := range cases { 935 t.Run(c.name, func(t *testing.T) { 936 pod, err := podTemplateWithError(c.stack) 937 if c.expectedError != "" { 938 assert.EqualError(t, err, c.expectedError) 939 } else { 940 assert.NoError(t, err) 941 assert.Equal(t, pod.Spec.Containers[0].ImagePullPolicy, c.expectedPolicy) 942 } 943 }) 944 } 945 }