sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/pod-utils/decorate/podspec_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package decorate 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "strconv" 24 "strings" 25 "testing" 26 "time" 27 28 coreapi "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/api/equality" 30 "k8s.io/apimachinery/pkg/api/resource" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/util/diff" 33 utilpointer "k8s.io/utils/pointer" 34 "sigs.k8s.io/yaml" 35 36 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 37 "sigs.k8s.io/prow/pkg/clonerefs" 38 "sigs.k8s.io/prow/pkg/entrypoint" 39 "sigs.k8s.io/prow/pkg/gcsupload" 40 "sigs.k8s.io/prow/pkg/github" 41 "sigs.k8s.io/prow/pkg/initupload" 42 "sigs.k8s.io/prow/pkg/pod-utils/wrapper" 43 "sigs.k8s.io/prow/pkg/sidecar" 44 "sigs.k8s.io/prow/pkg/testutil" 45 ) 46 47 func pStr(str string) *string { 48 return &str 49 } 50 func pInt64(i int64) *int64 { 51 return &i 52 } 53 54 func cookieVolumeOnly(secret string) coreapi.Volume { 55 v, _, _ := cookiefileVolume(secret) 56 return v 57 } 58 59 func cookieMountOnly(secret string) coreapi.VolumeMount { 60 _, vm, _ := cookiefileVolume(secret) 61 return vm 62 } 63 func cookiePathOnly(secret string) string { 64 _, _, vp := cookiefileVolume(secret) 65 return vp 66 } 67 68 func TestCloneRefs(t *testing.T) { 69 truth := true 70 logMount := coreapi.VolumeMount{ 71 Name: "log", 72 MountPath: "/log-mount", 73 } 74 codeMount := coreapi.VolumeMount{ 75 Name: "code", 76 MountPath: "/code-mount", 77 } 78 tmpMount := coreapi.VolumeMount{ 79 Name: "clonerefs-tmp", 80 MountPath: "/tmp", 81 } 82 tmpVolume := coreapi.Volume{ 83 Name: "clonerefs-tmp", 84 VolumeSource: coreapi.VolumeSource{ 85 EmptyDir: &coreapi.EmptyDirVolumeSource{}, 86 }, 87 } 88 envOrDie := func(opt clonerefs.Options) []coreapi.EnvVar { 89 e, err := cloneEnv(opt) 90 if err != nil { 91 t.Fatal(err) 92 } 93 return e 94 } 95 sshVolumeOnly := func(secret string) coreapi.Volume { 96 v, _ := sshVolume(secret) 97 return v 98 } 99 100 sshMountOnly := func(secret string) coreapi.VolumeMount { 101 _, vm := sshVolume(secret) 102 return vm 103 } 104 105 cases := []struct { 106 name string 107 pj prowapi.ProwJob 108 codeMountOverride *coreapi.VolumeMount 109 logMountOverride *coreapi.VolumeMount 110 expected *coreapi.Container 111 volumes []coreapi.Volume 112 err bool 113 }{ 114 { 115 name: "empty returns nil", 116 }, 117 { 118 name: "nil refs and extrarefs returns nil", 119 pj: prowapi.ProwJob{ 120 Spec: prowapi.ProwJobSpec{ 121 DecorationConfig: &prowapi.DecorationConfig{}, 122 }, 123 }, 124 }, 125 { 126 name: "nil DecorationConfig returns nil", 127 pj: prowapi.ProwJob{ 128 Spec: prowapi.ProwJobSpec{ 129 Refs: &prowapi.Refs{}, 130 }, 131 }, 132 }, 133 { 134 name: "SkipCloning returns nil", 135 pj: prowapi.ProwJob{ 136 Spec: prowapi.ProwJobSpec{ 137 Refs: &prowapi.Refs{}, 138 DecorationConfig: &prowapi.DecorationConfig{ 139 SkipCloning: &truth, 140 }, 141 }, 142 }, 143 }, 144 { 145 name: "reject empty code mount name", 146 pj: prowapi.ProwJob{ 147 Spec: prowapi.ProwJobSpec{ 148 DecorationConfig: &prowapi.DecorationConfig{}, 149 Refs: &prowapi.Refs{}, 150 }, 151 }, 152 codeMountOverride: &coreapi.VolumeMount{ 153 MountPath: "/whatever", 154 }, 155 err: true, 156 }, 157 { 158 name: "reject empty code mountpath", 159 pj: prowapi.ProwJob{ 160 Spec: prowapi.ProwJobSpec{ 161 DecorationConfig: &prowapi.DecorationConfig{}, 162 Refs: &prowapi.Refs{}, 163 }, 164 }, 165 codeMountOverride: &coreapi.VolumeMount{ 166 Name: "wee", 167 }, 168 err: true, 169 }, 170 { 171 name: "reject empty log mount name", 172 pj: prowapi.ProwJob{ 173 Spec: prowapi.ProwJobSpec{ 174 DecorationConfig: &prowapi.DecorationConfig{}, 175 Refs: &prowapi.Refs{}, 176 }, 177 }, 178 logMountOverride: &coreapi.VolumeMount{ 179 MountPath: "/whatever", 180 }, 181 err: true, 182 }, 183 { 184 name: "reject empty log mountpath", 185 pj: prowapi.ProwJob{ 186 Spec: prowapi.ProwJobSpec{ 187 DecorationConfig: &prowapi.DecorationConfig{}, 188 Refs: &prowapi.Refs{}, 189 }, 190 }, 191 logMountOverride: &coreapi.VolumeMount{ 192 Name: "wee", 193 }, 194 err: true, 195 }, 196 { 197 name: "create clonerefs container when refs are set", 198 pj: prowapi.ProwJob{ 199 Spec: prowapi.ProwJobSpec{ 200 Refs: &prowapi.Refs{}, 201 DecorationConfig: &prowapi.DecorationConfig{ 202 UtilityImages: &prowapi.UtilityImages{}, 203 }, 204 }, 205 }, 206 expected: &coreapi.Container{ 207 Name: cloneRefsName, 208 Env: envOrDie(clonerefs.Options{ 209 GitRefs: []prowapi.Refs{{}}, 210 GitUserEmail: clonerefs.DefaultGitUserEmail, 211 GitUserName: clonerefs.DefaultGitUserName, 212 SrcRoot: codeMount.MountPath, 213 Log: CloneLogPath(logMount), 214 GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint}, 215 }), 216 VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount}, 217 }, 218 volumes: []coreapi.Volume{tmpVolume}, 219 }, 220 { 221 name: "create clonerefs containers when extrarefs are set", 222 pj: prowapi.ProwJob{ 223 Spec: prowapi.ProwJobSpec{ 224 ExtraRefs: []prowapi.Refs{{}}, 225 DecorationConfig: &prowapi.DecorationConfig{ 226 UtilityImages: &prowapi.UtilityImages{}, 227 }, 228 }, 229 }, 230 expected: &coreapi.Container{ 231 Name: cloneRefsName, 232 Env: envOrDie(clonerefs.Options{ 233 GitRefs: []prowapi.Refs{{}}, 234 GitUserEmail: clonerefs.DefaultGitUserEmail, 235 GitUserName: clonerefs.DefaultGitUserName, 236 SrcRoot: codeMount.MountPath, 237 Log: CloneLogPath(logMount), 238 GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint}, 239 }), 240 VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount}, 241 }, 242 volumes: []coreapi.Volume{tmpVolume}, 243 }, 244 { 245 name: "append extrarefs after refs", 246 pj: prowapi.ProwJob{ 247 Spec: prowapi.ProwJobSpec{ 248 Refs: &prowapi.Refs{Org: "first"}, 249 ExtraRefs: []prowapi.Refs{{Org: "second"}, {Org: "third"}}, 250 DecorationConfig: &prowapi.DecorationConfig{ 251 UtilityImages: &prowapi.UtilityImages{}, 252 }, 253 }, 254 }, 255 expected: &coreapi.Container{ 256 Name: cloneRefsName, 257 Env: envOrDie(clonerefs.Options{ 258 GitRefs: []prowapi.Refs{{Org: "first"}, {Org: "second"}, {Org: "third"}}, 259 GitUserEmail: clonerefs.DefaultGitUserEmail, 260 GitUserName: clonerefs.DefaultGitUserName, 261 SrcRoot: codeMount.MountPath, 262 Log: CloneLogPath(logMount), 263 GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint}, 264 }), 265 VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount}, 266 }, 267 volumes: []coreapi.Volume{tmpVolume}, 268 }, 269 { 270 name: "append ssh secrets when set", 271 pj: prowapi.ProwJob{ 272 Spec: prowapi.ProwJobSpec{ 273 Refs: &prowapi.Refs{}, 274 DecorationConfig: &prowapi.DecorationConfig{ 275 UtilityImages: &prowapi.UtilityImages{}, 276 SSHKeySecrets: []string{"super", "secret"}, 277 }, 278 }, 279 }, 280 expected: &coreapi.Container{ 281 Name: cloneRefsName, 282 Env: envOrDie(clonerefs.Options{ 283 GitRefs: []prowapi.Refs{{}}, 284 GitUserEmail: clonerefs.DefaultGitUserEmail, 285 GitUserName: clonerefs.DefaultGitUserName, 286 KeyFiles: []string{sshMountOnly("super").MountPath, sshMountOnly("secret").MountPath}, 287 SrcRoot: codeMount.MountPath, 288 Log: CloneLogPath(logMount), 289 GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint}, 290 }), 291 VolumeMounts: []coreapi.VolumeMount{ 292 logMount, 293 codeMount, 294 sshMountOnly("super"), 295 sshMountOnly("secret"), 296 tmpMount, 297 }, 298 }, 299 volumes: []coreapi.Volume{sshVolumeOnly("super"), sshVolumeOnly("secret"), tmpVolume}, 300 }, 301 { 302 name: "include ssh host fingerprints when set", 303 pj: prowapi.ProwJob{ 304 Spec: prowapi.ProwJobSpec{ 305 ExtraRefs: []prowapi.Refs{{}}, 306 DecorationConfig: &prowapi.DecorationConfig{ 307 UtilityImages: &prowapi.UtilityImages{}, 308 SSHHostFingerprints: []string{"thumb", "pinky"}, 309 }, 310 }, 311 }, 312 expected: &coreapi.Container{ 313 Name: cloneRefsName, 314 Env: envOrDie(clonerefs.Options{ 315 GitRefs: []prowapi.Refs{{}}, 316 GitUserEmail: clonerefs.DefaultGitUserEmail, 317 GitUserName: clonerefs.DefaultGitUserName, 318 SrcRoot: codeMount.MountPath, 319 HostFingerprints: []string{"thumb", "pinky"}, 320 Log: CloneLogPath(logMount), 321 GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint}, 322 }), 323 VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount}, 324 }, 325 volumes: []coreapi.Volume{tmpVolume}, 326 }, 327 { 328 name: "include cookiefile secrets when set", 329 pj: prowapi.ProwJob{ 330 Spec: prowapi.ProwJobSpec{ 331 ExtraRefs: []prowapi.Refs{{}}, 332 DecorationConfig: &prowapi.DecorationConfig{ 333 UtilityImages: &prowapi.UtilityImages{}, 334 CookiefileSecret: pStr("oatmeal"), 335 }, 336 }, 337 }, 338 expected: &coreapi.Container{ 339 Name: cloneRefsName, 340 Args: []string{"--cookiefile=" + cookiePathOnly("oatmeal")}, 341 Env: envOrDie(clonerefs.Options{ 342 CookiePath: cookiePathOnly("oatmeal"), 343 GitRefs: []prowapi.Refs{{}}, 344 GitUserEmail: clonerefs.DefaultGitUserEmail, 345 GitUserName: clonerefs.DefaultGitUserName, 346 SrcRoot: codeMount.MountPath, 347 Log: CloneLogPath(logMount), 348 GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint}, 349 }), 350 VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount, cookieMountOnly("oatmeal")}, 351 }, 352 volumes: []coreapi.Volume{tmpVolume, cookieVolumeOnly("oatmeal")}, 353 }, 354 { 355 name: "intentional empty string cookiefile secrets is valid", 356 pj: prowapi.ProwJob{ 357 Spec: prowapi.ProwJobSpec{ 358 ExtraRefs: []prowapi.Refs{{}}, 359 DecorationConfig: &prowapi.DecorationConfig{ 360 UtilityImages: &prowapi.UtilityImages{}, 361 CookiefileSecret: pStr(""), 362 }, 363 }, 364 }, 365 expected: &coreapi.Container{ 366 Name: cloneRefsName, 367 Env: envOrDie(clonerefs.Options{ 368 GitRefs: []prowapi.Refs{{}}, 369 GitUserEmail: clonerefs.DefaultGitUserEmail, 370 GitUserName: clonerefs.DefaultGitUserName, 371 SrcRoot: codeMount.MountPath, 372 Log: CloneLogPath(logMount), 373 GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint}, 374 }), 375 VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount}, 376 }, 377 volumes: []coreapi.Volume{tmpVolume}, 378 }, 379 { 380 name: "include oauth token secret when set", 381 pj: prowapi.ProwJob{ 382 Spec: prowapi.ProwJobSpec{ 383 ExtraRefs: []prowapi.Refs{{}}, 384 DecorationConfig: &prowapi.DecorationConfig{ 385 UtilityImages: &prowapi.UtilityImages{}, 386 OauthTokenSecret: &prowapi.OauthTokenSecret{ 387 Name: "oauth-secret", 388 Key: "oauth-file", 389 }, 390 }, 391 }, 392 }, 393 expected: &coreapi.Container{ 394 Name: cloneRefsName, 395 Env: envOrDie(clonerefs.Options{ 396 GitRefs: []prowapi.Refs{{}}, 397 GitUserEmail: clonerefs.DefaultGitUserEmail, 398 GitUserName: clonerefs.DefaultGitUserName, 399 SrcRoot: codeMount.MountPath, 400 Log: CloneLogPath(logMount), 401 OauthTokenFile: "/secrets/oauth/oauth-file", 402 GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint}, 403 }), 404 VolumeMounts: []coreapi.VolumeMount{ 405 logMount, codeMount, 406 {Name: "oauth-secret", ReadOnly: true, MountPath: "/secrets/oauth"}, 407 tmpMount, 408 }, 409 }, 410 volumes: []coreapi.Volume{ 411 { 412 Name: "oauth-secret", 413 VolumeSource: coreapi.VolumeSource{ 414 Secret: &coreapi.SecretVolumeSource{ 415 SecretName: "oauth-secret", 416 Items: []coreapi.KeyToPath{{ 417 Key: "oauth-file", 418 Path: "./oauth-file", 419 }}, 420 }, 421 }, 422 }, 423 tmpVolume, 424 }, 425 }, 426 { 427 name: "include GitHub App ID and private key when set", 428 pj: prowapi.ProwJob{ 429 Spec: prowapi.ProwJobSpec{ 430 ExtraRefs: []prowapi.Refs{{}}, 431 DecorationConfig: &prowapi.DecorationConfig{ 432 UtilityImages: &prowapi.UtilityImages{}, 433 GitHubAppID: "123456", 434 GitHubAppPrivateKeySecret: &prowapi.GitHubAppPrivateKeySecret{ 435 Name: "github-app-secret", 436 Key: "private-key", 437 }, 438 }, 439 }, 440 }, 441 expected: &coreapi.Container{ 442 Name: cloneRefsName, 443 Env: envOrDie(clonerefs.Options{ 444 GitRefs: []prowapi.Refs{{}}, 445 GitUserEmail: clonerefs.DefaultGitUserEmail, 446 GitUserName: clonerefs.DefaultGitUserName, 447 SrcRoot: codeMount.MountPath, 448 Log: CloneLogPath(logMount), 449 GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint}, 450 GitHubAppID: "123456", 451 GitHubAppPrivateKeyFile: "/secrets/github-app/private-key", 452 }), 453 VolumeMounts: []coreapi.VolumeMount{ 454 logMount, codeMount, 455 { 456 Name: "github-app-secret", 457 ReadOnly: true, 458 MountPath: "/secrets/github-app", 459 }, 460 tmpMount, 461 }, 462 }, 463 volumes: []coreapi.Volume{ 464 { 465 Name: "github-app-secret", 466 VolumeSource: coreapi.VolumeSource{ 467 Secret: &coreapi.SecretVolumeSource{ 468 SecretName: "github-app-secret", 469 Items: []coreapi.KeyToPath{{ 470 Key: "private-key", 471 Path: "./private-key", 472 }}, 473 }, 474 }, 475 }, 476 tmpVolume, 477 }, 478 }, 479 { 480 name: "include custom GitHub API endpoints when set", 481 pj: prowapi.ProwJob{ 482 Spec: prowapi.ProwJobSpec{ 483 ExtraRefs: []prowapi.Refs{{}}, 484 DecorationConfig: &prowapi.DecorationConfig{ 485 UtilityImages: &prowapi.UtilityImages{}, 486 GitHubAPIEndpoints: []string{"http://example.com"}, 487 GitHubAppID: "123456", 488 GitHubAppPrivateKeySecret: &prowapi.GitHubAppPrivateKeySecret{ 489 Name: "github-app-secret", 490 Key: "private-key", 491 }, 492 }, 493 }, 494 }, 495 expected: &coreapi.Container{ 496 Name: cloneRefsName, 497 Env: envOrDie(clonerefs.Options{ 498 GitRefs: []prowapi.Refs{{}}, 499 GitUserEmail: clonerefs.DefaultGitUserEmail, 500 GitUserName: clonerefs.DefaultGitUserName, 501 SrcRoot: codeMount.MountPath, 502 Log: CloneLogPath(logMount), 503 GitHubAPIEndpoints: []string{"http://example.com"}, 504 GitHubAppID: "123456", 505 GitHubAppPrivateKeyFile: "/secrets/github-app/private-key", 506 }), 507 VolumeMounts: []coreapi.VolumeMount{ 508 logMount, codeMount, 509 { 510 Name: "github-app-secret", 511 ReadOnly: true, 512 MountPath: "/secrets/github-app", 513 }, 514 tmpMount, 515 }, 516 }, 517 volumes: []coreapi.Volume{ 518 { 519 Name: "github-app-secret", 520 VolumeSource: coreapi.VolumeSource{ 521 Secret: &coreapi.SecretVolumeSource{ 522 SecretName: "github-app-secret", 523 Items: []coreapi.KeyToPath{{ 524 Key: "private-key", 525 Path: "./private-key", 526 }}, 527 }, 528 }, 529 }, 530 tmpVolume, 531 }, 532 }, 533 } 534 535 for _, tc := range cases { 536 t.Run(tc.name, func(t *testing.T) { 537 lm := logMount 538 if tc.logMountOverride != nil { 539 lm = *tc.logMountOverride 540 } 541 cm := codeMount 542 if tc.codeMountOverride != nil { 543 cm = *tc.codeMountOverride 544 } 545 actual, refs, volumes, err := CloneRefs(tc.pj, cm, lm) 546 switch { 547 case err != nil: 548 if !tc.err { 549 t.Errorf("unexpected error: %v", err) 550 } 551 case tc.err: 552 t.Error("failed to receive expected exception") 553 case !equality.Semantic.DeepEqual(tc.expected, actual): 554 t.Errorf("unexpected container:\n%s", diff.ObjectReflectDiff(tc.expected, actual)) 555 case !equality.Semantic.DeepEqual(tc.volumes, volumes): 556 t.Errorf("unexpected volume:\n%s", diff.ObjectReflectDiff(tc.volumes, volumes)) 557 case actual != nil: 558 var er []prowapi.Refs 559 if tc.pj.Spec.Refs != nil { 560 er = append(er, *tc.pj.Spec.Refs) 561 } 562 er = append(er, tc.pj.Spec.ExtraRefs...) 563 if !equality.Semantic.DeepEqual(refs, er) { 564 t.Errorf("unexpected refs:\n%s", diff.ObjectReflectDiff(er, refs)) 565 } 566 } 567 }) 568 } 569 } 570 571 func TestProwJobToPod(t *testing.T) { 572 truth := true 573 tests := []struct { 574 podName string 575 buildID string 576 labels map[string]string 577 pjSpec prowapi.ProwJobSpec 578 pjStatus prowapi.ProwJobStatus 579 }{ 580 { 581 podName: "pod", 582 buildID: "blabla", 583 labels: map[string]string{"needstobe": "inherited"}, 584 pjSpec: prowapi.ProwJobSpec{ 585 Type: prowapi.PresubmitJob, 586 Job: "job-name", 587 Context: "job-context", 588 Agent: prowapi.KubernetesAgent, 589 Refs: &prowapi.Refs{ 590 Org: "org-name", 591 Repo: "repo-name", 592 BaseRef: "base-ref", 593 BaseSHA: "base-sha", 594 Pulls: []prowapi.Pull{{ 595 Number: 1, 596 Author: "author-name", 597 SHA: "pull-sha", 598 HeadRef: "pull-branch-name", 599 Title: "pull-title", 600 }}, 601 }, 602 PodSpec: &coreapi.PodSpec{ 603 Containers: []coreapi.Container{ 604 { 605 Image: "tester", 606 Env: []coreapi.EnvVar{ 607 {Name: "MY_ENV", Value: "rocks"}, 608 }, 609 }, 610 }, 611 }, 612 }, 613 pjStatus: prowapi.ProwJobStatus{ 614 BuildID: "blabla", 615 }, 616 }, 617 { 618 podName: "pod", 619 buildID: "blabla", 620 labels: map[string]string{"needstobe": "inherited"}, 621 pjSpec: prowapi.ProwJobSpec{ 622 Type: prowapi.PresubmitJob, 623 Job: "job-name", 624 Context: "job-context", 625 DecorationConfig: &prowapi.DecorationConfig{ 626 Timeout: &prowapi.Duration{Duration: 120 * time.Minute}, 627 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 628 UtilityImages: &prowapi.UtilityImages{ 629 CloneRefs: "clonerefs:tag", 630 InitUpload: "initupload:tag", 631 Entrypoint: "entrypoint:tag", 632 Sidecar: "sidecar:tag", 633 }, 634 GCSConfiguration: &prowapi.GCSConfiguration{ 635 Bucket: "my-bucket", 636 PathStrategy: "legacy", 637 DefaultOrg: "kubernetes", 638 DefaultRepo: "kubernetes", 639 MediaTypes: map[string]string{"log": "text/plain"}, 640 }, 641 GCSCredentialsSecret: pStr("secret-name"), 642 CookiefileSecret: pStr("yummy/.gitcookies"), 643 }, 644 Agent: prowapi.KubernetesAgent, 645 Refs: &prowapi.Refs{ 646 Org: "org-name", 647 Repo: "repo-name", 648 BaseRef: "base-ref", 649 BaseSHA: "base-sha", 650 Pulls: []prowapi.Pull{{ 651 Number: 1, 652 Author: "author-name", 653 SHA: "pull-sha", 654 HeadRef: "my-big-change", 655 Title: "pull-title", 656 }}, 657 PathAlias: "somewhere/else", 658 }, 659 ExtraRefs: []prowapi.Refs{}, 660 PodSpec: &coreapi.PodSpec{ 661 Containers: []coreapi.Container{ 662 { 663 Image: "tester", 664 Command: []string{"/bin/thing"}, 665 Args: []string{"some", "args"}, 666 Env: []coreapi.EnvVar{ 667 {Name: "MY_ENV", Value: "rocks"}, 668 }, 669 }, 670 }, 671 }, 672 }, 673 }, 674 { 675 podName: "pod", 676 buildID: "blabla", 677 labels: map[string]string{"needstobe": "inherited"}, 678 pjSpec: prowapi.ProwJobSpec{ 679 Type: prowapi.PresubmitJob, 680 Job: "job-name", 681 Context: "job-context", 682 DecorationConfig: &prowapi.DecorationConfig{ 683 Timeout: &prowapi.Duration{Duration: 120 * time.Minute}, 684 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 685 UtilityImages: &prowapi.UtilityImages{ 686 CloneRefs: "clonerefs:tag", 687 InitUpload: "initupload:tag", 688 Entrypoint: "entrypoint:tag", 689 Sidecar: "sidecar:tag", 690 }, 691 GCSConfiguration: &prowapi.GCSConfiguration{ 692 Bucket: "my-bucket", 693 PathStrategy: "legacy", 694 DefaultOrg: "kubernetes", 695 DefaultRepo: "kubernetes", 696 }, 697 GCSCredentialsSecret: pStr("secret-name"), 698 CookiefileSecret: pStr("yummy"), 699 }, 700 Agent: prowapi.KubernetesAgent, 701 Refs: &prowapi.Refs{ 702 Org: "org-name", 703 Repo: "repo-name", 704 BaseRef: "base-ref", 705 BaseSHA: "base-sha", 706 Pulls: []prowapi.Pull{{ 707 Number: 1, 708 Author: "author-name", 709 SHA: "pull-sha", 710 HeadRef: "fix-typos-99", 711 Title: "pull-title", 712 }}, 713 PathAlias: "somewhere/else", 714 }, 715 ExtraRefs: []prowapi.Refs{}, 716 PodSpec: &coreapi.PodSpec{ 717 Containers: []coreapi.Container{ 718 { 719 Image: "tester", 720 Command: []string{"/bin/thing"}, 721 Args: []string{"some", "args"}, 722 Env: []coreapi.EnvVar{ 723 {Name: "MY_ENV", Value: "rocks"}, 724 }, 725 }, 726 }, 727 }, 728 }, 729 }, 730 { 731 podName: "pod", 732 buildID: "blabla", 733 labels: map[string]string{"needstobe": "inherited"}, 734 pjSpec: prowapi.ProwJobSpec{ 735 Type: prowapi.PresubmitJob, 736 Job: "job-name", 737 Context: "job-context", 738 DecorationConfig: &prowapi.DecorationConfig{ 739 Timeout: &prowapi.Duration{Duration: 120 * time.Minute}, 740 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 741 UtilityImages: &prowapi.UtilityImages{ 742 CloneRefs: "clonerefs:tag", 743 InitUpload: "initupload:tag", 744 Entrypoint: "entrypoint:tag", 745 Sidecar: "sidecar:tag", 746 }, 747 GCSConfiguration: &prowapi.GCSConfiguration{ 748 Bucket: "my-bucket", 749 PathStrategy: "legacy", 750 DefaultOrg: "kubernetes", 751 DefaultRepo: "kubernetes", 752 }, 753 GCSCredentialsSecret: pStr("secret-name"), 754 SSHKeySecrets: []string{"ssh-1", "ssh-2"}, 755 SSHHostFingerprints: []string{"hello", "world"}, 756 }, 757 Agent: prowapi.KubernetesAgent, 758 Refs: &prowapi.Refs{ 759 Org: "org-name", 760 Repo: "repo-name", 761 BaseRef: "base-ref", 762 BaseSHA: "base-sha", 763 Pulls: []prowapi.Pull{{ 764 Number: 1, 765 Author: "author-name", 766 SHA: "pull-sha", 767 HeadRef: "fixes-fixes-fixes", 768 Title: "pull-title", 769 }}, 770 PathAlias: "somewhere/else", 771 }, 772 ExtraRefs: []prowapi.Refs{}, 773 PodSpec: &coreapi.PodSpec{ 774 Containers: []coreapi.Container{ 775 { 776 Image: "tester", 777 Command: []string{"/bin/thing"}, 778 Args: []string{"some", "args"}, 779 Env: []coreapi.EnvVar{ 780 {Name: "MY_ENV", Value: "rocks"}, 781 }, 782 TerminationMessagePolicy: coreapi.TerminationMessageReadFile, 783 }, 784 }, 785 }, 786 }, 787 }, 788 { 789 podName: "pod", 790 buildID: "blabla", 791 labels: map[string]string{"needstobe": "inherited"}, 792 pjSpec: prowapi.ProwJobSpec{ 793 Type: prowapi.PresubmitJob, 794 Job: "job-name", 795 Context: "job-context", 796 DecorationConfig: &prowapi.DecorationConfig{ 797 Timeout: &prowapi.Duration{Duration: 120 * time.Minute}, 798 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 799 UtilityImages: &prowapi.UtilityImages{ 800 CloneRefs: "clonerefs:tag", 801 InitUpload: "initupload:tag", 802 Entrypoint: "entrypoint:tag", 803 Sidecar: "sidecar:tag", 804 }, 805 GCSConfiguration: &prowapi.GCSConfiguration{ 806 Bucket: "my-bucket", 807 PathStrategy: "legacy", 808 DefaultOrg: "kubernetes", 809 DefaultRepo: "kubernetes", 810 }, 811 GCSCredentialsSecret: pStr("secret-name"), 812 SSHKeySecrets: []string{"ssh-1", "ssh-2"}, 813 }, 814 Agent: prowapi.KubernetesAgent, 815 Refs: &prowapi.Refs{ 816 Org: "org-name", 817 Repo: "repo-name", 818 BaseRef: "base-ref", 819 BaseSHA: "base-sha", 820 Pulls: []prowapi.Pull{{ 821 Number: 1, 822 Author: "author-name", 823 SHA: "pull-sha", 824 HeadRef: "fixes-9", 825 Title: "pull-title", 826 }}, 827 PathAlias: "somewhere/else", 828 }, 829 ExtraRefs: []prowapi.Refs{}, 830 PodSpec: &coreapi.PodSpec{ 831 Containers: []coreapi.Container{ 832 { 833 Image: "tester", 834 Command: []string{"/bin/thing"}, 835 Args: []string{"some", "args"}, 836 Env: []coreapi.EnvVar{ 837 {Name: "MY_ENV", Value: "rocks"}, 838 }, 839 }, 840 }, 841 }, 842 }, 843 }, 844 { 845 podName: "pod", 846 buildID: "blabla", 847 labels: map[string]string{"needstobe": "inherited"}, 848 pjSpec: prowapi.ProwJobSpec{ 849 Type: prowapi.PeriodicJob, 850 Job: "job-name", 851 Context: "job-context", 852 DecorationConfig: &prowapi.DecorationConfig{ 853 Timeout: &prowapi.Duration{Duration: 120 * time.Minute}, 854 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 855 UtilityImages: &prowapi.UtilityImages{ 856 CloneRefs: "clonerefs:tag", 857 InitUpload: "initupload:tag", 858 Entrypoint: "entrypoint:tag", 859 Sidecar: "sidecar:tag", 860 }, 861 GCSConfiguration: &prowapi.GCSConfiguration{ 862 Bucket: "my-bucket", 863 PathStrategy: "legacy", 864 DefaultOrg: "kubernetes", 865 DefaultRepo: "kubernetes", 866 }, 867 GCSCredentialsSecret: pStr("secret-name"), 868 SSHKeySecrets: []string{"ssh-1", "ssh-2"}, 869 }, 870 Agent: prowapi.KubernetesAgent, 871 PodSpec: &coreapi.PodSpec{ 872 Containers: []coreapi.Container{ 873 { 874 Image: "tester", 875 Command: []string{"/bin/thing"}, 876 Args: []string{"some", "args"}, 877 Env: []coreapi.EnvVar{ 878 {Name: "MY_ENV", Value: "rocks"}, 879 }, 880 }, 881 }, 882 }, 883 }, 884 }, 885 { 886 podName: "pod", 887 buildID: "blabla", 888 labels: map[string]string{"needstobe": "inherited"}, 889 pjSpec: prowapi.ProwJobSpec{ 890 Type: prowapi.PresubmitJob, 891 Job: "job-name", 892 Context: "job-context", 893 DecorationConfig: &prowapi.DecorationConfig{ 894 Timeout: &prowapi.Duration{Duration: 120 * time.Minute}, 895 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 896 UtilityImages: &prowapi.UtilityImages{ 897 CloneRefs: "clonerefs:tag", 898 InitUpload: "initupload:tag", 899 Entrypoint: "entrypoint:tag", 900 Sidecar: "sidecar:tag", 901 }, 902 GCSConfiguration: &prowapi.GCSConfiguration{ 903 Bucket: "my-bucket", 904 PathStrategy: "legacy", 905 DefaultOrg: "kubernetes", 906 DefaultRepo: "kubernetes", 907 }, 908 GCSCredentialsSecret: pStr("secret-name"), 909 SSHKeySecrets: []string{"ssh-1", "ssh-2"}, 910 SkipCloning: &truth, 911 }, 912 Agent: prowapi.KubernetesAgent, 913 Refs: &prowapi.Refs{ 914 Org: "org-name", 915 Repo: "repo-name", 916 BaseRef: "base-ref", 917 BaseSHA: "base-sha", 918 Pulls: []prowapi.Pull{{ 919 Number: 1, 920 Author: "author-name", 921 SHA: "pull-sha", 922 HeadRef: "best-branch-name", 923 Title: "pull-title", 924 }}, 925 PathAlias: "somewhere/else", 926 }, 927 ExtraRefs: []prowapi.Refs{ 928 { 929 Org: "extra-org", 930 Repo: "extra-repo", 931 }, 932 }, 933 PodSpec: &coreapi.PodSpec{ 934 Containers: []coreapi.Container{ 935 { 936 Image: "tester", 937 Command: []string{"/bin/thing"}, 938 Args: []string{"some", "args"}, 939 Env: []coreapi.EnvVar{ 940 {Name: "MY_ENV", Value: "rocks"}, 941 }, 942 }, 943 }, 944 }, 945 }, 946 }, 947 { 948 podName: "pod", 949 buildID: "blabla", 950 labels: map[string]string{"needstobe": "inherited"}, 951 pjSpec: prowapi.ProwJobSpec{ 952 Type: prowapi.PresubmitJob, 953 Job: "job-name", 954 Context: "job-context", 955 DecorationConfig: &prowapi.DecorationConfig{ 956 Timeout: &prowapi.Duration{Duration: 120 * time.Minute}, 957 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 958 UtilityImages: &prowapi.UtilityImages{ 959 CloneRefs: "clonerefs:tag", 960 InitUpload: "initupload:tag", 961 Entrypoint: "entrypoint:tag", 962 Sidecar: "sidecar:tag", 963 }, 964 GCSConfiguration: &prowapi.GCSConfiguration{ 965 Bucket: "my-bucket", 966 PathStrategy: "legacy", 967 DefaultOrg: "kubernetes", 968 DefaultRepo: "kubernetes", 969 }, 970 GCSCredentialsSecret: pStr("secret-name"), 971 SSHKeySecrets: []string{"ssh-1", "ssh-2"}, 972 CookiefileSecret: pStr("yummy"), 973 }, 974 Agent: prowapi.KubernetesAgent, 975 Refs: &prowapi.Refs{ 976 Org: "org-name", 977 Repo: "repo-name", 978 BaseRef: "base-ref", 979 BaseSHA: "base-sha", 980 Pulls: []prowapi.Pull{{ 981 Number: 1, 982 Author: "author-name", 983 SHA: "pull-sha", 984 HeadRef: "pr-head-ref-11", 985 Title: "pull-title", 986 }}, 987 PathAlias: "somewhere/else", 988 }, 989 ExtraRefs: []prowapi.Refs{ 990 { 991 Org: "extra-org", 992 Repo: "extra-repo", 993 }, 994 }, 995 PodSpec: &coreapi.PodSpec{ 996 Containers: []coreapi.Container{ 997 { 998 Name: "test-0", 999 Image: "tester", 1000 Command: []string{"/bin/thing"}, 1001 Args: []string{"some", "args"}, 1002 Env: []coreapi.EnvVar{ 1003 {Name: "MY_ENV", Value: "rocks"}, 1004 }, 1005 }, 1006 { 1007 Name: "test-1", 1008 Image: "othertester", 1009 Command: []string{"/bin/otherthing"}, 1010 Args: []string{"other", "args"}, 1011 Env: []coreapi.EnvVar{ 1012 {Name: "MY_ENV", Value: "stones"}, 1013 }, 1014 }, 1015 }, 1016 }, 1017 }, 1018 }, 1019 { 1020 podName: "pod", 1021 buildID: "blabla", 1022 labels: map[string]string{"needstobe": "inherited"}, 1023 pjSpec: prowapi.ProwJobSpec{ 1024 Type: prowapi.PresubmitJob, 1025 Job: "job-name", 1026 Context: "job-context", 1027 DecorationConfig: &prowapi.DecorationConfig{ 1028 Timeout: &prowapi.Duration{Duration: 120 * time.Minute}, 1029 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 1030 UtilityImages: &prowapi.UtilityImages{ 1031 CloneRefs: "clonerefs:tag", 1032 InitUpload: "initupload:tag", 1033 Entrypoint: "entrypoint:tag", 1034 Sidecar: "sidecar:tag", 1035 }, 1036 GCSConfiguration: &prowapi.GCSConfiguration{ 1037 Bucket: "my-bucket", 1038 PathStrategy: "legacy", 1039 DefaultOrg: "kubernetes", 1040 DefaultRepo: "kubernetes", 1041 MediaTypes: map[string]string{"log": "text/plain"}, 1042 }, 1043 // Specify K8s SA rather than cloud storage secret key. 1044 DefaultServiceAccountName: pStr("default-SA"), 1045 CookiefileSecret: pStr("yummy/.gitcookies"), 1046 }, 1047 Agent: prowapi.KubernetesAgent, 1048 Refs: &prowapi.Refs{ 1049 Org: "org-name", 1050 Repo: "repo-name", 1051 BaseRef: "base-ref", 1052 BaseSHA: "base-sha", 1053 Pulls: []prowapi.Pull{{ 1054 Number: 1, 1055 Author: "author-name", 1056 SHA: "pull-sha", 1057 HeadRef: "orig-branch-name", 1058 Title: "pull-title", 1059 }}, 1060 PathAlias: "somewhere/else", 1061 }, 1062 ExtraRefs: []prowapi.Refs{}, 1063 PodSpec: &coreapi.PodSpec{ 1064 Containers: []coreapi.Container{ 1065 { 1066 Image: "tester", 1067 Command: []string{"/bin/thing"}, 1068 Args: []string{"some", "args"}, 1069 Env: []coreapi.EnvVar{ 1070 {Name: "MY_ENV", Value: "rocks"}, 1071 }, 1072 }, 1073 }, 1074 }, 1075 }, 1076 }, 1077 { 1078 podName: "pod", 1079 buildID: "blabla", 1080 labels: map[string]string{"needstobe": "inherited"}, 1081 pjSpec: prowapi.ProwJobSpec{ 1082 Type: prowapi.PresubmitJob, 1083 Job: "job-name", 1084 Context: "job-context", 1085 DecorationConfig: &prowapi.DecorationConfig{ 1086 Timeout: &prowapi.Duration{Duration: 120 * time.Minute}, 1087 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 1088 UtilityImages: &prowapi.UtilityImages{ 1089 CloneRefs: "clonerefs:tag", 1090 InitUpload: "initupload:tag", 1091 Entrypoint: "entrypoint:tag", 1092 Sidecar: "sidecar:tag", 1093 }, 1094 GCSConfiguration: &prowapi.GCSConfiguration{ 1095 Bucket: "my-bucket", 1096 PathStrategy: "legacy", 1097 DefaultOrg: "kubernetes", 1098 DefaultRepo: "kubernetes", 1099 MediaTypes: map[string]string{"log": "text/plain"}, 1100 }, 1101 // Specify K8s SA rather than cloud storage secret key. 1102 DefaultServiceAccountName: pStr("default-SA"), 1103 CookiefileSecret: pStr("yummy/.gitcookies"), 1104 RunAsGroup: pInt64(1000), 1105 RunAsUser: pInt64(1000), 1106 FsGroup: pInt64(2000), 1107 }, 1108 Agent: prowapi.KubernetesAgent, 1109 Refs: &prowapi.Refs{ 1110 Org: "org-name", 1111 Repo: "repo-name", 1112 BaseRef: "base-ref", 1113 BaseSHA: "base-sha", 1114 Pulls: []prowapi.Pull{{ 1115 Number: 1, 1116 Author: "author-name", 1117 SHA: "pull-sha", 1118 HeadRef: "orig-branch-name", 1119 Title: "pull-title", 1120 }}, 1121 PathAlias: "somewhere/else", 1122 }, 1123 ExtraRefs: []prowapi.Refs{}, 1124 PodSpec: &coreapi.PodSpec{ 1125 Containers: []coreapi.Container{ 1126 { 1127 Image: "tester", 1128 Command: []string{"/bin/thing"}, 1129 Args: []string{"some", "args"}, 1130 Env: []coreapi.EnvVar{ 1131 {Name: "MY_ENV", Value: "rocks"}, 1132 }, 1133 }, 1134 }, 1135 }, 1136 }, 1137 }, 1138 } 1139 1140 findContainer := func(name string, pod coreapi.Pod) *coreapi.Container { 1141 for _, c := range pod.Spec.Containers { 1142 if c.Name == name { 1143 return &c 1144 } 1145 } 1146 return nil 1147 } 1148 findEnv := func(key string, container coreapi.Container) *string { 1149 for _, env := range container.Env { 1150 if env.Name == key { 1151 v := env.Value 1152 return &v 1153 } 1154 1155 } 1156 return nil 1157 } 1158 1159 type checker interface { 1160 ConfigVar() string 1161 LoadConfig(string) error 1162 Validate() error 1163 } 1164 1165 checkEnv := func(pod coreapi.Pod, name string, opt checker) error { 1166 c := findContainer(name, pod) 1167 if c == nil { 1168 return nil 1169 } 1170 env := opt.ConfigVar() 1171 val := findEnv(env, *c) 1172 if val == nil { 1173 return fmt.Errorf("missing %s env var", env) 1174 } 1175 if err := opt.LoadConfig(*val); err != nil { 1176 return fmt.Errorf("load: %w", err) 1177 } 1178 if err := opt.Validate(); err != nil { 1179 return fmt.Errorf("validate: %w", err) 1180 } 1181 return nil 1182 } 1183 1184 for i, test := range tests { 1185 t.Run(strconv.Itoa(i), func(t *testing.T) { 1186 pj := prowapi.ProwJob{ObjectMeta: metav1.ObjectMeta{Name: test.podName, Labels: test.labels}, Spec: test.pjSpec, Status: test.pjStatus} 1187 pj.Status.BuildID = test.buildID 1188 got, err := ProwJobToPod(pj) 1189 if err != nil { 1190 t.Errorf("unexpected error: %v", err) 1191 } 1192 fixtureName := filepath.Join("testdata", fmt.Sprintf("%s.yaml", strings.ReplaceAll(t.Name(), "/", "_"))) 1193 if os.Getenv("UPDATE") != "" { 1194 marshalled, err := yaml.Marshal(got) 1195 if err != nil { 1196 t.Fatalf("failed to marhsal pod: %v", err) 1197 } 1198 if err := os.WriteFile(fixtureName, marshalled, 0644); err != nil { 1199 t.Errorf("failed to update fixture: %v", err) 1200 } 1201 } 1202 expectedRaw, err := os.ReadFile(fixtureName) 1203 if err != nil { 1204 t.Fatalf("failed to read fixture: %v", err) 1205 } 1206 expected := &coreapi.Pod{} 1207 if err := yaml.Unmarshal(expectedRaw, expected); err != nil { 1208 t.Fatalf("failed to unmarshal fixture: %v", err) 1209 } 1210 if !equality.Semantic.DeepEqual(got, expected) { 1211 t.Errorf("unexpected pod diff:\n%s. You can update the fixtures by running this test with UPDATE=true if this is expected.", diff.ObjectReflectDiff(expected, got)) 1212 } 1213 if err := checkEnv(*got, "sidecar", sidecar.NewOptions()); err != nil { 1214 t.Errorf("bad sidecar env: %v", err) 1215 } 1216 if err := checkEnv(*got, "initupload", initupload.NewOptions()); err != nil { 1217 t.Errorf("bad clonerefs env: %v", err) 1218 } 1219 if err := checkEnv(*got, "clonerefs", &clonerefs.Options{}); err != nil { 1220 t.Errorf("bad clonerefs env: %v", err) 1221 } 1222 if test.pjSpec.DecorationConfig != nil { // all jobs get a test container 1223 // But only decorated jobs need valid entrypoint options 1224 if err := checkEnv(*got, "test", entrypoint.NewOptions()); err != nil { 1225 t.Errorf("bad test entrypoint: %v", err) 1226 } 1227 } 1228 }) 1229 } 1230 } 1231 1232 func TestProwJobToPod_setsTerminationGracePeriodSeconds(t *testing.T) { 1233 testCases := []struct { 1234 name string 1235 prowjob *prowapi.ProwJob 1236 expectedTerminationGracePeriodSeconds int64 1237 }{ 1238 { 1239 name: "GracePeriodSeconds from decoration config", 1240 prowjob: &prowapi.ProwJob{ 1241 Spec: prowapi.ProwJobSpec{ 1242 PodSpec: &coreapi.PodSpec{Containers: []coreapi.Container{{}}}, 1243 DecorationConfig: &prowapi.DecorationConfig{ 1244 UtilityImages: &prowapi.UtilityImages{}, 1245 GracePeriod: &prowapi.Duration{Duration: 10 * time.Second}, 1246 }, 1247 }, 1248 }, 1249 expectedTerminationGracePeriodSeconds: 12, 1250 }, 1251 { 1252 name: "Existing GracePeriodSeconds is not overwritten", 1253 prowjob: &prowapi.ProwJob{ 1254 Spec: prowapi.ProwJobSpec{ 1255 PodSpec: &coreapi.PodSpec{TerminationGracePeriodSeconds: utilpointer.Int64(60), Containers: []coreapi.Container{{}}}, 1256 DecorationConfig: &prowapi.DecorationConfig{ 1257 UtilityImages: &prowapi.UtilityImages{}, 1258 Timeout: &prowapi.Duration{Duration: 10 * time.Second}, 1259 }, 1260 }, 1261 }, 1262 expectedTerminationGracePeriodSeconds: 60, 1263 }, 1264 } 1265 1266 for idx := range testCases { 1267 tc := testCases[idx] 1268 t.Run(tc.name, func(t *testing.T) { 1269 t.Parallel() 1270 if err := decorate(tc.prowjob.Spec.PodSpec, tc.prowjob, map[string]string{}, ""); err != nil { 1271 t.Fatalf("decoration failed: %v", err) 1272 } 1273 if tc.prowjob.Spec.PodSpec.TerminationGracePeriodSeconds == nil || *tc.prowjob.Spec.PodSpec.TerminationGracePeriodSeconds != tc.expectedTerminationGracePeriodSeconds { 1274 t.Errorf("expected pods TerminationGracePeriodSeconds to be %d was %v", tc.expectedTerminationGracePeriodSeconds, tc.prowjob.Spec.PodSpec.TerminationGracePeriodSeconds) 1275 } 1276 }) 1277 } 1278 } 1279 1280 func TestSidecar(t *testing.T) { 1281 var testCases = []struct { 1282 name string 1283 config *prowapi.DecorationConfig 1284 gcsOptions gcsupload.Options 1285 blobStorageMounts []coreapi.VolumeMount 1286 logMount coreapi.VolumeMount 1287 outputMount *coreapi.VolumeMount 1288 encodedJobSpec string 1289 requirePassingEntries, ignoreInterrupts bool 1290 secretVolumeMounts []coreapi.VolumeMount 1291 wrappers []wrapper.Options 1292 }{ 1293 { 1294 name: "basic case", 1295 config: &prowapi.DecorationConfig{ 1296 UtilityImages: &prowapi.UtilityImages{Sidecar: "sidecar-image"}, 1297 }, 1298 gcsOptions: gcsupload.Options{ 1299 Items: []string{"first", "second"}, 1300 GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "bucket"}, 1301 }, 1302 blobStorageMounts: []coreapi.VolumeMount{{Name: "blob", MountPath: "/blob"}}, 1303 logMount: coreapi.VolumeMount{Name: "logs", MountPath: "/logs"}, 1304 outputMount: &coreapi.VolumeMount{Name: "outputs", MountPath: "/outputs"}, 1305 encodedJobSpec: "spec", 1306 requirePassingEntries: true, 1307 ignoreInterrupts: true, 1308 wrappers: []wrapper.Options{{Args: []string{"yes"}}}, 1309 }, 1310 { 1311 name: "with secrets", 1312 config: &prowapi.DecorationConfig{ 1313 UtilityImages: &prowapi.UtilityImages{Sidecar: "sidecar-image"}, 1314 }, 1315 gcsOptions: gcsupload.Options{ 1316 Items: []string{"first", "second"}, 1317 GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "bucket"}, 1318 }, 1319 blobStorageMounts: []coreapi.VolumeMount{{Name: "blob", MountPath: "/blob"}}, 1320 logMount: coreapi.VolumeMount{Name: "logs", MountPath: "/logs"}, 1321 outputMount: &coreapi.VolumeMount{Name: "outputs", MountPath: "/outputs"}, 1322 encodedJobSpec: "spec", 1323 requirePassingEntries: true, 1324 ignoreInterrupts: true, 1325 secretVolumeMounts: []coreapi.VolumeMount{ 1326 {Name: "very", MountPath: "/very"}, 1327 {Name: "secret", MountPath: "/secret"}, 1328 {Name: "stuff", MountPath: "/stuff"}, 1329 }, 1330 wrappers: []wrapper.Options{{Args: []string{"yes"}}}, 1331 }, 1332 } 1333 1334 for _, testCase := range testCases { 1335 t.Run(testCase.name, func(t *testing.T) { 1336 container, err := Sidecar( 1337 testCase.config, testCase.gcsOptions, 1338 testCase.blobStorageMounts, testCase.logMount, testCase.outputMount, 1339 testCase.encodedJobSpec, 1340 testCase.requirePassingEntries, testCase.ignoreInterrupts, 1341 testCase.secretVolumeMounts, testCase.wrappers..., 1342 ) 1343 if err != nil { 1344 t.Fatalf("%s: got an error from Sidecar(): %v", testCase.name, err) 1345 } 1346 testutil.CompareWithSerializedFixture(t, container) 1347 }) 1348 } 1349 } 1350 1351 func TestDecorate(t *testing.T) { 1352 gCSCredentialsSecret := "gcs-secret" 1353 defaultServiceAccountName := "default-sa" 1354 censor := true 1355 ignoreInterrupts := true 1356 resourcePtr := func(s string) *resource.Quantity { 1357 q := resource.MustParse(s) 1358 return &q 1359 } 1360 var testCases = []struct { 1361 name string 1362 spec *coreapi.PodSpec 1363 pj *prowapi.ProwJob 1364 rawEnv map[string]string 1365 outputDir string 1366 }{ 1367 { 1368 name: "basic happy case", 1369 spec: &coreapi.PodSpec{ 1370 Volumes: []coreapi.Volume{ 1371 {Name: "secret", VolumeSource: coreapi.VolumeSource{Secret: &coreapi.SecretVolumeSource{SecretName: "secretname"}}}, 1372 }, 1373 Containers: []coreapi.Container{ 1374 {Name: "test", Command: []string{"/bin/ls"}, Args: []string{"-l", "-a"}, VolumeMounts: []coreapi.VolumeMount{{Name: "secret", MountPath: "/secret"}}}, 1375 }, 1376 ServiceAccountName: "tester", 1377 }, 1378 pj: &prowapi.ProwJob{ 1379 Spec: prowapi.ProwJobSpec{ 1380 DecorationConfig: &prowapi.DecorationConfig{ 1381 Timeout: &prowapi.Duration{Duration: time.Minute}, 1382 GracePeriod: &prowapi.Duration{Duration: time.Hour}, 1383 UtilityImages: &prowapi.UtilityImages{ 1384 CloneRefs: "cloneimage", 1385 InitUpload: "initimage", 1386 Entrypoint: "entrypointimage", 1387 Sidecar: "sidecarimage", 1388 }, 1389 Resources: &prowapi.Resources{ 1390 CloneRefs: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1391 InitUpload: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1392 PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1393 Sidecar: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1394 }, 1395 GCSConfiguration: &prowapi.GCSConfiguration{ 1396 Bucket: "bucket", 1397 PathStrategy: "single", 1398 DefaultOrg: "org", 1399 DefaultRepo: "repo", 1400 }, 1401 GCSCredentialsSecret: &gCSCredentialsSecret, 1402 DefaultServiceAccountName: &defaultServiceAccountName, 1403 }, 1404 Refs: &prowapi.Refs{ 1405 Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234", 1406 Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}}, 1407 }, 1408 ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}}, 1409 }, 1410 }, 1411 rawEnv: map[string]string{"custom": "env"}, 1412 }, 1413 { 1414 name: "enforcing memory limit", 1415 spec: &coreapi.PodSpec{ 1416 Containers: []coreapi.Container{ 1417 { 1418 Name: "test", 1419 Command: []string{"/bin/ls"}, 1420 Args: []string{"-l", "-a"}, 1421 Resources: coreapi.ResourceRequirements{ 1422 Requests: coreapi.ResourceList{ 1423 "memory": resource.MustParse("8Gi"), 1424 }, 1425 Limits: coreapi.ResourceList{ 1426 "memory": resource.MustParse("100Gi"), 1427 }, 1428 }, 1429 }, 1430 }, 1431 ServiceAccountName: "tester", 1432 }, 1433 pj: &prowapi.ProwJob{ 1434 Spec: prowapi.ProwJobSpec{ 1435 DecorationConfig: &prowapi.DecorationConfig{ 1436 Timeout: &prowapi.Duration{Duration: time.Minute}, 1437 GracePeriod: &prowapi.Duration{Duration: time.Hour}, 1438 UtilityImages: &prowapi.UtilityImages{ 1439 CloneRefs: "cloneimage", 1440 InitUpload: "initimage", 1441 Entrypoint: "entrypointimage", 1442 Sidecar: "sidecarimage", 1443 }, 1444 Resources: &prowapi.Resources{ 1445 CloneRefs: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1446 InitUpload: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1447 PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1448 Sidecar: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1449 }, 1450 GCSConfiguration: &prowapi.GCSConfiguration{ 1451 Bucket: "bucket", 1452 PathStrategy: "single", 1453 DefaultOrg: "org", 1454 DefaultRepo: "repo", 1455 }, 1456 GCSCredentialsSecret: &gCSCredentialsSecret, 1457 DefaultServiceAccountName: &defaultServiceAccountName, 1458 SetLimitEqualsMemoryRequest: utilpointer.Bool(true), 1459 }, 1460 Refs: &prowapi.Refs{ 1461 Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234", 1462 Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}}, 1463 }, 1464 ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}}, 1465 }, 1466 }, 1467 rawEnv: map[string]string{"custom": "env"}, 1468 }, 1469 { 1470 name: "default memory request", 1471 spec: &coreapi.PodSpec{ 1472 Containers: []coreapi.Container{ 1473 { 1474 Name: "test", 1475 Command: []string{"/bin/ls"}, 1476 Args: []string{"-l", "-a"}, 1477 Resources: coreapi.ResourceRequirements{ 1478 Requests: coreapi.ResourceList{ 1479 "memory": resource.MustParse("8Gi"), 1480 }, 1481 Limits: coreapi.ResourceList{ 1482 "memory": resource.MustParse("100Gi"), 1483 }, 1484 }, 1485 }, 1486 { 1487 Name: "test2", 1488 Command: []string{"/bin/ls"}, 1489 Args: []string{"-l", "-a"}, 1490 }, 1491 }, 1492 ServiceAccountName: "tester", 1493 }, 1494 pj: &prowapi.ProwJob{ 1495 Spec: prowapi.ProwJobSpec{ 1496 DecorationConfig: &prowapi.DecorationConfig{ 1497 Timeout: &prowapi.Duration{Duration: time.Minute}, 1498 GracePeriod: &prowapi.Duration{Duration: time.Hour}, 1499 UtilityImages: &prowapi.UtilityImages{ 1500 CloneRefs: "cloneimage", 1501 InitUpload: "initimage", 1502 Entrypoint: "entrypointimage", 1503 Sidecar: "sidecarimage", 1504 }, 1505 Resources: &prowapi.Resources{ 1506 CloneRefs: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1507 InitUpload: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1508 PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1509 Sidecar: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1510 }, 1511 GCSConfiguration: &prowapi.GCSConfiguration{ 1512 Bucket: "bucket", 1513 PathStrategy: "single", 1514 DefaultOrg: "org", 1515 DefaultRepo: "repo", 1516 }, 1517 GCSCredentialsSecret: &gCSCredentialsSecret, 1518 DefaultServiceAccountName: &defaultServiceAccountName, 1519 SetLimitEqualsMemoryRequest: utilpointer.Bool(true), 1520 DefaultMemoryRequest: resourcePtr("4Gi"), 1521 }, 1522 Refs: &prowapi.Refs{ 1523 Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234", 1524 Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}}, 1525 }, 1526 ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}}, 1527 }, 1528 }, 1529 rawEnv: map[string]string{"custom": "env"}, 1530 }, 1531 { 1532 name: "censor secrets in sidecar", 1533 spec: &coreapi.PodSpec{ 1534 Volumes: []coreapi.Volume{ 1535 {Name: "secret", VolumeSource: coreapi.VolumeSource{Secret: &coreapi.SecretVolumeSource{SecretName: "secretname"}}}, 1536 }, 1537 Containers: []coreapi.Container{ 1538 {Name: "test", Command: []string{"/bin/ls"}, Args: []string{"-l", "-a"}, VolumeMounts: []coreapi.VolumeMount{{Name: "secret", MountPath: "/secret"}}}, 1539 }, 1540 ServiceAccountName: "tester", 1541 }, 1542 pj: &prowapi.ProwJob{ 1543 Spec: prowapi.ProwJobSpec{ 1544 DecorationConfig: &prowapi.DecorationConfig{ 1545 Timeout: &prowapi.Duration{Duration: time.Minute}, 1546 GracePeriod: &prowapi.Duration{Duration: time.Hour}, 1547 UtilityImages: &prowapi.UtilityImages{ 1548 CloneRefs: "cloneimage", 1549 InitUpload: "initimage", 1550 Entrypoint: "entrypointimage", 1551 Sidecar: "sidecarimage", 1552 }, 1553 Resources: &prowapi.Resources{ 1554 CloneRefs: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1555 InitUpload: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1556 PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1557 Sidecar: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1558 }, 1559 GCSConfiguration: &prowapi.GCSConfiguration{ 1560 Bucket: "bucket", 1561 PathStrategy: "single", 1562 DefaultOrg: "org", 1563 DefaultRepo: "repo", 1564 }, 1565 GCSCredentialsSecret: &gCSCredentialsSecret, 1566 DefaultServiceAccountName: &defaultServiceAccountName, 1567 CensorSecrets: &censor, 1568 }, 1569 Refs: &prowapi.Refs{ 1570 Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234", 1571 Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}}, 1572 }, 1573 ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}}, 1574 }, 1575 }, 1576 rawEnv: map[string]string{"custom": "env"}, 1577 }, 1578 { 1579 name: "ignore interrupts in sidecar", 1580 spec: &coreapi.PodSpec{ 1581 Volumes: []coreapi.Volume{ 1582 {Name: "secret", VolumeSource: coreapi.VolumeSource{Secret: &coreapi.SecretVolumeSource{SecretName: "secretname"}}}, 1583 }, 1584 Containers: []coreapi.Container{ 1585 {Name: "test", Command: []string{"/bin/ls"}, Args: []string{"-l", "-a"}, VolumeMounts: []coreapi.VolumeMount{{Name: "secret", MountPath: "/secret"}}}, 1586 }, 1587 ServiceAccountName: "tester", 1588 }, 1589 pj: &prowapi.ProwJob{ 1590 Spec: prowapi.ProwJobSpec{ 1591 DecorationConfig: &prowapi.DecorationConfig{ 1592 Timeout: &prowapi.Duration{Duration: time.Minute}, 1593 GracePeriod: &prowapi.Duration{Duration: time.Hour}, 1594 UploadIgnoresInterrupts: &ignoreInterrupts, 1595 UtilityImages: &prowapi.UtilityImages{ 1596 CloneRefs: "cloneimage", 1597 InitUpload: "initimage", 1598 Entrypoint: "entrypointimage", 1599 Sidecar: "sidecarimage", 1600 }, 1601 Resources: &prowapi.Resources{ 1602 CloneRefs: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1603 InitUpload: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1604 PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1605 Sidecar: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}}, 1606 }, 1607 GCSConfiguration: &prowapi.GCSConfiguration{ 1608 Bucket: "bucket", 1609 PathStrategy: "single", 1610 DefaultOrg: "org", 1611 DefaultRepo: "repo", 1612 }, 1613 GCSCredentialsSecret: &gCSCredentialsSecret, 1614 DefaultServiceAccountName: &defaultServiceAccountName, 1615 }, 1616 Refs: &prowapi.Refs{ 1617 Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234", 1618 Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}}, 1619 }, 1620 ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}}, 1621 }, 1622 }, 1623 rawEnv: map[string]string{"custom": "env"}, 1624 }, 1625 } 1626 1627 for _, testCase := range testCases { 1628 t.Run(testCase.name, func(t *testing.T) { 1629 if err := decorate(testCase.spec, testCase.pj, testCase.rawEnv, testCase.outputDir); err != nil { 1630 t.Fatalf("got an error from decorate(): %v", err) 1631 } 1632 testutil.CompareWithSerializedFixture(t, testCase.spec) 1633 }) 1634 } 1635 }