sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/config/config_test.go (about) 1 /* 2 Copyright 2017 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 config 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "path/filepath" 24 "reflect" 25 "regexp" 26 "strconv" 27 "strings" 28 "sync" 29 "testing" 30 "text/template" 31 "time" 32 33 "github.com/google/go-cmp/cmp" 34 "github.com/google/go-cmp/cmp/cmpopts" 35 fuzz "github.com/google/gofuzz" 36 pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" 37 v1 "k8s.io/api/core/v1" 38 apiequality "k8s.io/apimachinery/pkg/api/equality" 39 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 40 "k8s.io/apimachinery/pkg/labels" 41 "k8s.io/apimachinery/pkg/util/diff" 42 utilerrors "k8s.io/apimachinery/pkg/util/errors" 43 "k8s.io/apimachinery/pkg/util/sets" 44 utilpointer "k8s.io/utils/pointer" 45 "sigs.k8s.io/yaml" 46 47 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 48 "sigs.k8s.io/prow/pkg/config/secret" 49 "sigs.k8s.io/prow/pkg/git/types" 50 "sigs.k8s.io/prow/pkg/github" 51 "sigs.k8s.io/prow/pkg/github/fakegithub" 52 "sigs.k8s.io/prow/pkg/kube" 53 "sigs.k8s.io/prow/pkg/pod-utils/decorate" 54 "sigs.k8s.io/prow/pkg/pod-utils/downwardapi" 55 ) 56 57 func pStr(str string) *string { 58 return &str 59 } 60 61 func TestKeysForIdentifier(t *testing.T) { 62 tests := []struct { 63 name string 64 identifier string 65 want []string 66 }{ 67 { 68 name: "base", 69 identifier: "foo", 70 want: []string{"foo", "*"}, 71 }, 72 { 73 name: "with-http", 74 identifier: "http://foo", 75 want: []string{"http://foo", "foo", "*"}, 76 }, 77 { 78 name: "with-https", 79 identifier: "https://foo", 80 want: []string{"https://foo", "foo", "*"}, 81 }, 82 { 83 name: "org-name-contains-https", 84 identifier: "https-foo", 85 want: []string{"https-foo", "*"}, 86 }, 87 } 88 89 for _, tc := range tests { 90 tc := tc 91 t.Run(tc.name, func(t *testing.T) { 92 if diff := cmp.Diff(tc.want, keysForIdentifier(tc.identifier)); diff != "" { 93 t.Errorf("Keys mismatch. Want(-), got(+):\n%s", diff) 94 } 95 }) 96 } 97 } 98 99 func TestDefaultJobBase(t *testing.T) { 100 bar := "bar" 101 filled := JobBase{ 102 Agent: "foo", 103 Namespace: &bar, 104 Cluster: "build", 105 } 106 cases := []struct { 107 name string 108 config ProwConfig 109 base func(j *JobBase) 110 expected func(j *JobBase) 111 }{ 112 { 113 name: "no changes when fields are already set", 114 }, 115 { 116 name: "empty agent results in kubernetes", 117 base: func(j *JobBase) { 118 j.Agent = "" 119 }, 120 expected: func(j *JobBase) { 121 j.Agent = string(prowapi.KubernetesAgent) 122 }, 123 }, 124 { 125 name: "nil namespace becomes PodNamespace", 126 config: ProwConfig{ 127 PodNamespace: "pod-namespace", 128 ProwJobNamespace: "wrong", 129 }, 130 base: func(j *JobBase) { 131 j.Namespace = nil 132 }, 133 expected: func(j *JobBase) { 134 p := "pod-namespace" 135 j.Namespace = &p 136 }, 137 }, 138 { 139 name: "empty namespace becomes PodNamespace", 140 config: ProwConfig{ 141 PodNamespace: "new-pod-namespace", 142 ProwJobNamespace: "still-wrong", 143 }, 144 base: func(j *JobBase) { 145 var empty string 146 j.Namespace = &empty 147 }, 148 expected: func(j *JobBase) { 149 p := "new-pod-namespace" 150 j.Namespace = &p 151 }, 152 }, 153 { 154 name: "empty cluster becomes DefaultClusterAlias", 155 base: func(j *JobBase) { 156 j.Cluster = "" 157 }, 158 expected: func(j *JobBase) { 159 j.Cluster = kube.DefaultClusterAlias 160 }, 161 }, 162 } 163 164 for _, tc := range cases { 165 t.Run(tc.name, func(t *testing.T) { 166 actual := filled 167 if tc.base != nil { 168 tc.base(&actual) 169 } 170 expected := actual 171 if tc.expected != nil { 172 tc.expected(&expected) 173 } 174 tc.config.defaultJobBase(&actual) 175 if !reflect.DeepEqual(actual, expected) { 176 t.Errorf("expected %#v\n!=\nactual %#v", expected, actual) 177 } 178 }) 179 } 180 } 181 182 func TestSpyglassConfig(t *testing.T) { 183 testCases := []struct { 184 name string 185 spyglassConfig string 186 expectedViewers map[string][]string 187 expectedRegexMatches map[string][]string 188 expectedSizeLimit int64 189 expectError bool 190 }{ 191 { 192 name: "Default: build log, metadata, junit", 193 spyglassConfig: ` 194 deck: 195 spyglass: 196 size_limit: 500e+6 197 viewers: 198 "started.json|finished.json": 199 - "metadata" 200 "build-log.txt": 201 - "buildlog" 202 "artifacts/junit.*\\.xml": 203 - "junit" 204 `, 205 expectedViewers: map[string][]string{ 206 "started.json|finished.json": {"metadata"}, 207 "build-log.txt": {"buildlog"}, 208 "artifacts/junit.*\\.xml": {"junit"}, 209 }, 210 expectedRegexMatches: map[string][]string{ 211 "started.json|finished.json": {"started.json", "finished.json"}, 212 "build-log.txt": {"build-log.txt"}, 213 "artifacts/junit.*\\.xml": {"artifacts/junit01.xml", "artifacts/junit_runner.xml"}, 214 }, 215 expectedSizeLimit: 500e6, 216 expectError: false, 217 }, 218 { 219 name: "Backwards compatibility", 220 spyglassConfig: ` 221 deck: 222 spyglass: 223 size_limit: 500e+6 224 viewers: 225 "started.json|finished.json": 226 - "metadata-viewer" 227 "build-log.txt": 228 - "build-log-viewer" 229 "artifacts/junit.*\\.xml": 230 - "junit-viewer" 231 `, 232 expectedViewers: map[string][]string{ 233 "started.json|finished.json": {"metadata"}, 234 "build-log.txt": {"buildlog"}, 235 "artifacts/junit.*\\.xml": {"junit"}, 236 }, 237 expectedSizeLimit: 500e6, 238 expectError: false, 239 }, 240 { 241 name: "Invalid spyglass size limit", 242 spyglassConfig: ` 243 deck: 244 spyglass: 245 size_limit: -4 246 viewers: 247 "started.json|finished.json": 248 - "metadata-viewer" 249 "build-log.txt": 250 - "build-log-viewer" 251 "artifacts/junit.*\\.xml": 252 - "junit-viewer" 253 `, 254 expectError: true, 255 }, 256 { 257 name: "Invalid Spyglass regexp", 258 spyglassConfig: ` 259 deck: 260 spyglass: 261 size_limit: 5 262 viewers: 263 "started.json\|]finished.json": 264 - "metadata-viewer" 265 `, 266 expectError: true, 267 }, 268 { 269 name: "Invalid Spyglass gcs browser web prefix", 270 spyglassConfig: ` 271 deck: 272 spyglass: 273 gcs_browser_prefix: https://gcsweb.k8s.io/gcs/ 274 gcs_browser_prefixes: 275 '*': https://gcsweb.k8s.io/gcs/ 276 `, 277 expectError: true, 278 }, 279 { 280 name: "Invalid Spyglass gcs browser web prefix by bucket", 281 spyglassConfig: ` 282 deck: 283 spyglass: 284 gcs_browser_prefix: https://gcsweb.k8s.io/gcs/ 285 gcs_browser_prefixes_by_bucket: 286 '*': https://gcsweb.k8s.io/gcs/ 287 `, 288 expectError: true, 289 }, 290 } 291 for _, tc := range testCases { 292 // save the config 293 spyglassConfigDir := t.TempDir() 294 295 spyglassConfig := filepath.Join(spyglassConfigDir, "config.yaml") 296 if err := os.WriteFile(spyglassConfig, []byte(tc.spyglassConfig), 0666); err != nil { 297 t.Fatalf("fail to write spyglass config: %v", err) 298 } 299 300 cfg, err := Load(spyglassConfig, "", nil, "") 301 if (err != nil) != tc.expectError { 302 t.Fatalf("tc %s: expected error: %v, got: %v, error: %v", tc.name, tc.expectError, (err != nil), err) 303 } 304 305 if err != nil { 306 continue 307 } 308 got := cfg.Deck.Spyglass.Viewers 309 for re, viewNames := range got { 310 expected, ok := tc.expectedViewers[re] 311 if !ok { 312 t.Errorf("With re %s, got %s, was not found in expected.", re, viewNames) 313 continue 314 } 315 if !reflect.DeepEqual(expected, viewNames) { 316 t.Errorf("With re %s, got %s, expected view name %s", re, viewNames, expected) 317 } 318 319 } 320 for re, viewNames := range tc.expectedViewers { 321 gotNames, ok := got[re] 322 if !ok { 323 t.Errorf("With re %s, expected %s, was not found in got.", re, viewNames) 324 continue 325 } 326 if !reflect.DeepEqual(gotNames, viewNames) { 327 t.Errorf("With re %s, got %s, expected view name %s", re, gotNames, viewNames) 328 } 329 } 330 331 for expectedRegex, matches := range tc.expectedRegexMatches { 332 compiledRegex, ok := cfg.Deck.Spyglass.RegexCache[expectedRegex] 333 if !ok { 334 t.Errorf("tc %s, regex %s was not found in the spyglass regex cache", tc.name, expectedRegex) 335 continue 336 } 337 for _, match := range matches { 338 if !compiledRegex.MatchString(match) { 339 t.Errorf("tc %s expected compiled regex %s to match %s, did not match.", tc.name, expectedRegex, match) 340 } 341 } 342 343 } 344 if cfg.Deck.Spyglass.SizeLimit != tc.expectedSizeLimit { 345 t.Errorf("%s expected SizeLimit %d, got %d", tc.name, tc.expectedSizeLimit, cfg.Deck.Spyglass.SizeLimit) 346 } 347 } 348 349 } 350 351 func TestGetGCSBrowserPrefix(t *testing.T) { 352 testCases := []struct { 353 id string 354 config Spyglass 355 expected string 356 }{ 357 { 358 id: "only default", 359 config: Spyglass{ 360 GCSBrowserPrefixesByRepo: map[string]string{ 361 "*": "https://default.com/gcs/", 362 }, 363 }, 364 expected: "https://default.com/gcs/", 365 }, 366 { 367 id: "org exists", 368 config: Spyglass{ 369 GCSBrowserPrefixesByRepo: map[string]string{ 370 "*": "https://default.com/gcs/", 371 "org": "https://org.com/gcs/", 372 }, 373 }, 374 expected: "https://org.com/gcs/", 375 }, 376 { 377 id: "repo exists", 378 config: Spyglass{ 379 GCSBrowserPrefixesByRepo: map[string]string{ 380 "*": "https://default.com/gcs/", 381 "org": "https://org.com/gcs/", 382 "org/repo": "https://repo.com/gcs/", 383 }, 384 }, 385 expected: "https://repo.com/gcs/", 386 }, 387 { 388 id: "repo overrides bucket", 389 config: Spyglass{ 390 GCSBrowserPrefixesByRepo: map[string]string{ 391 "*": "https://default.com/gcs/", 392 "org": "https://org.com/gcs/", 393 "org/repo": "https://repo.com/gcs/", 394 }, 395 GCSBrowserPrefixesByBucket: map[string]string{ 396 "*": "https://default.com/gcs/", 397 "bucket": "https://bucket.com/gcs/", 398 }, 399 }, 400 expected: "https://repo.com/gcs/", 401 }, 402 { 403 id: "bucket exists", 404 config: Spyglass{ 405 GCSBrowserPrefixesByRepo: map[string]string{ 406 "*": "https://default.com/gcs/", 407 }, 408 GCSBrowserPrefixesByBucket: map[string]string{ 409 "*": "https://default.com/gcs/", 410 "bucket": "https://bucket.com/gcs/", 411 }, 412 }, 413 expected: "https://bucket.com/gcs/", 414 }, 415 } 416 417 for _, tc := range testCases { 418 actual := tc.config.GetGCSBrowserPrefix("org", "repo", "bucket") 419 if !reflect.DeepEqual(actual, tc.expected) { 420 t.Fatalf("%s", cmp.Diff(tc.expected, actual)) 421 } 422 } 423 } 424 425 func TestDefaultMatches(t *testing.T) { 426 for _, tc := range []struct { 427 desc string 428 givenOrgRepo string 429 givenCluster string 430 orgRepo string 431 cluster string 432 want bool 433 }{ 434 { 435 desc: "empty", 436 orgRepo: "org/repo", 437 cluster: "cluster", 438 want: true, 439 }, 440 { 441 desc: "givenOrgRepo empty", 442 givenOrgRepo: "", 443 orgRepo: "org/repo", 444 want: true, 445 }, 446 { 447 desc: "givenOrgRepo star", 448 givenOrgRepo: "*", 449 orgRepo: "org/repo", 450 want: true, 451 }, 452 { 453 desc: "givenOrgRepo org match", 454 givenOrgRepo: "org", 455 orgRepo: "org/repo", 456 want: true, 457 }, 458 { 459 desc: "givenOrgRepo full match", 460 givenOrgRepo: "org/repo", 461 orgRepo: "org/repo", 462 want: true, 463 }, 464 { 465 desc: "givenOrgRepo gerrit review org match", 466 givenOrgRepo: "some.org", 467 orgRepo: "some-review.org/repo", 468 want: true, 469 }, 470 { 471 desc: "givenOrgRepo gerrit review full match", 472 givenOrgRepo: "some.org/repo", 473 orgRepo: "some-review.org/repo", 474 want: true, 475 }, 476 // The following two cases cover an unexpected existing configuration 477 // that matches a literal http/s prefix. Though unadvised, it should 478 // not be broken by fuzz matching http prefixes. 479 { 480 desc: "givenOrgRepo http full match", 481 givenOrgRepo: "http://org/repo", 482 orgRepo: "http://org/repo", 483 want: true, 484 }, 485 { 486 desc: "givenOrgRepo https full match", 487 givenOrgRepo: "https://org/repo", 488 orgRepo: "https://org/repo", 489 want: true, 490 }, 491 { 492 desc: "givenOrgRepo http fuzz org match", 493 givenOrgRepo: "org", 494 orgRepo: "http://org/repo", 495 want: true, 496 }, 497 { 498 desc: "givenOrgRepo https fuzz org match", 499 givenOrgRepo: "org", 500 orgRepo: "https://org/repo", 501 want: true, 502 }, 503 { 504 desc: "givenOrgRepo https fuzz gerrit review org match", 505 givenOrgRepo: "some.org", 506 orgRepo: "https://some-review.org/repo", 507 want: true, 508 }, 509 { 510 desc: "givenOrgRepo http fuzz full match", 511 givenOrgRepo: "org/repo", 512 orgRepo: "http://org/repo", 513 want: true, 514 }, 515 { 516 desc: "givenOrgRepo https fuzz full match", 517 givenOrgRepo: "org/repo", 518 orgRepo: "https://org/repo", 519 want: true, 520 }, 521 { 522 desc: "givenOrgRepo org mismatch", 523 givenOrgRepo: "org2", 524 orgRepo: "org/repo", 525 want: false, 526 }, 527 { 528 desc: "givenOrgRepo repo mismatch", 529 givenOrgRepo: "org/repo2", 530 orgRepo: "org/repo", 531 want: false, 532 }, 533 { 534 desc: "givenOrgRepo gerrit review org mismatch", 535 givenOrgRepo: "some.other.org", 536 orgRepo: "some.other-review.org", 537 want: false, 538 }, 539 { 540 desc: "givenCluster empty", 541 givenCluster: "", 542 cluster: "cluster", 543 want: true, 544 }, 545 { 546 desc: "givenCluster star", 547 givenCluster: "*", 548 cluster: "cluster", 549 want: true, 550 }, 551 { 552 desc: "givenCluster match", 553 givenCluster: "cluster", 554 cluster: "cluster", 555 want: true, 556 }, 557 { 558 desc: "givenCluster mismatch", 559 givenCluster: "cluster2", 560 cluster: "cluster", 561 want: false, 562 }, 563 } { 564 t.Run(tc.desc, func(t *testing.T) { 565 if got := matches(tc.givenOrgRepo, tc.givenCluster, tc.orgRepo, tc.cluster); got != tc.want { 566 t.Errorf("matches() got %v, want %v", got, tc.want) 567 } 568 }) 569 } 570 } 571 572 func TestDecorationRawYaml(t *testing.T) { 573 t.Parallel() 574 var testCases = []struct { 575 name string 576 expectError bool 577 expectStrictError bool 578 rawConfig string 579 expected *prowapi.DecorationConfig 580 }{ 581 { 582 name: "no default", 583 expectError: true, 584 rawConfig: ` 585 periodics: 586 - name: kubernetes-defaulted-decoration 587 interval: 1h 588 decorate: true 589 spec: 590 containers: 591 - image: golang:latest 592 args: 593 - "test" 594 - "./..."`, 595 }, 596 { 597 name: "with bad default", 598 rawConfig: ` 599 plank: 600 default_decoration_configs: 601 '*': 602 timeout: 2h 603 grace_period: 15s 604 utility_images: 605 # clonerefs: "clonerefs:default" 606 initupload: "initupload:default" 607 entrypoint: "entrypoint:default" 608 sidecar: "sidecar:default" 609 gcs_configuration: 610 bucket: "default-bucket" 611 path_strategy: "legacy" 612 default_org: "kubernetes" 613 default_repo: "kubernetes" 614 gcs_credentials_secret: "default-service-account" 615 616 periodics: 617 - name: kubernetes-defaulted-decoration 618 interval: 1h 619 decorate: true 620 spec: 621 containers: 622 - image: golang:latest 623 args: 624 - "test" 625 - "./..."`, 626 expectError: true, 627 }, 628 { 629 name: "repo should inherit from default config", 630 rawConfig: ` 631 plank: 632 default_decoration_configs: 633 '*': 634 timeout: 2h 635 grace_period: 15s 636 utility_images: 637 clonerefs: "clonerefs:default" 638 initupload: "initupload:default" 639 entrypoint: "entrypoint:default" 640 sidecar: "sidecar:default" 641 gcs_configuration: 642 bucket: "default-bucket" 643 path_strategy: "legacy" 644 default_org: "kubernetes" 645 default_repo: "kubernetes" 646 gcs_credentials_secret: "default-service-account" 647 'org/inherit': 648 timeout: 2h 649 grace_period: 15s 650 utility_images: {} 651 gcs_configuration: 652 bucket: "default-bucket" 653 path_strategy: "legacy" 654 default_org: "kubernetes" 655 default_repo: "kubernetes" 656 gcs_credentials_secret: "default-service-account" 657 periodics: 658 - name: kubernetes-defaulted-decoration 659 interval: 1h 660 decorate: true 661 spec: 662 containers: 663 - image: golang:latest 664 args: 665 - "test" 666 - "./..."`, 667 }, 668 { 669 name: "with default and repo, use default", 670 rawConfig: ` 671 plank: 672 default_decoration_configs: 673 '*': 674 timeout: 2h 675 grace_period: 15s 676 utility_images: 677 clonerefs: "clonerefs:default" 678 initupload: "initupload:default" 679 entrypoint: "entrypoint:default" 680 sidecar: "sidecar:default" 681 gcs_configuration: 682 bucket: "default-bucket" 683 path_strategy: "legacy" 684 default_org: "kubernetes" 685 default_repo: "kubernetes" 686 gcs_credentials_secret: "default-service-account" 687 'random/repo': 688 timeout: 2h 689 grace_period: 15s 690 utility_images: 691 clonerefs: "clonerefs:random" 692 initupload: "initupload:random" 693 entrypoint: "entrypoint:random" 694 sidecar: "sidecar:org" 695 gcs_configuration: 696 bucket: "ignore" 697 path_strategy: "legacy" 698 default_org: "random" 699 default_repo: "repo" 700 gcs_credentials_secret: "random-service-account" 701 702 periodics: 703 - name: kubernetes-defaulted-decoration 704 interval: 1h 705 decorate: true 706 spec: 707 containers: 708 - image: golang:latest 709 args: 710 - "test" 711 - "./..."`, 712 expected: &prowapi.DecorationConfig{ 713 Timeout: &prowapi.Duration{Duration: 2 * time.Hour}, 714 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 715 UtilityImages: &prowapi.UtilityImages{ 716 CloneRefs: "clonerefs:default", 717 InitUpload: "initupload:default", 718 Entrypoint: "entrypoint:default", 719 Sidecar: "sidecar:default", 720 }, 721 GCSConfiguration: &prowapi.GCSConfiguration{ 722 Bucket: "default-bucket", 723 PathStrategy: prowapi.PathStrategyLegacy, 724 DefaultOrg: "kubernetes", 725 DefaultRepo: "kubernetes", 726 }, 727 GCSCredentialsSecret: pStr("default-service-account"), 728 }, 729 }, 730 { 731 name: "with non-existent additional field", 732 expectStrictError: true, 733 rawConfig: ` 734 lolNotARealField: bogus 735 plank: 736 default_decoration_configs: 737 '*': 738 timeout: 2h 739 grace_period: 15s 740 utility_images: 741 clonerefs: "clonerefs:default" 742 initupload: "initupload:default" 743 entrypoint: "entrypoint:default" 744 sidecar: "sidecar:default" 745 gcs_configuration: 746 bucket: "default-bucket" 747 path_strategy: "legacy" 748 default_org: "kubernetes" 749 default_repo: "kubernetes" 750 gcs_credentials_secret: "default-service-account" 751 'random/repo': 752 timeout: 2h 753 grace_period: 15s 754 utility_images: 755 clonerefs: "clonerefs:random" 756 initupload: "initupload:random" 757 entrypoint: "entrypoint:random" 758 sidecar: "sidecar:org" 759 gcs_configuration: 760 bucket: "ignore" 761 path_strategy: "legacy" 762 default_org: "random" 763 default_repo: "repo" 764 gcs_credentials_secret: "random-service-account" 765 766 periodics: 767 - name: kubernetes-defaulted-decoration 768 interval: 1h 769 decorate: true 770 spec: 771 containers: 772 - image: golang:latest 773 args: 774 - "test" 775 - "./..."`, 776 expected: &prowapi.DecorationConfig{ 777 Timeout: &prowapi.Duration{Duration: 2 * time.Hour}, 778 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 779 UtilityImages: &prowapi.UtilityImages{ 780 CloneRefs: "clonerefs:default", 781 InitUpload: "initupload:default", 782 Entrypoint: "entrypoint:default", 783 Sidecar: "sidecar:default", 784 }, 785 GCSConfiguration: &prowapi.GCSConfiguration{ 786 Bucket: "default-bucket", 787 PathStrategy: prowapi.PathStrategyLegacy, 788 DefaultOrg: "kubernetes", 789 DefaultRepo: "kubernetes", 790 }, 791 GCSCredentialsSecret: pStr("default-service-account"), 792 }, 793 }, 794 { 795 name: "with non-existent additional field allowed under 'prow_ignored'", 796 rawConfig: ` 797 prow_ignored: 798 lolNotARealField: bogus 799 plank: 800 default_decoration_configs: 801 '*': 802 timeout: 2h 803 grace_period: 15s 804 utility_images: 805 clonerefs: "clonerefs:default" 806 initupload: "initupload:default" 807 entrypoint: "entrypoint:default" 808 sidecar: "sidecar:default" 809 gcs_configuration: 810 bucket: "default-bucket" 811 path_strategy: "legacy" 812 default_org: "kubernetes" 813 default_repo: "kubernetes" 814 gcs_credentials_secret: "default-service-account" 815 'random/repo': 816 timeout: 2h 817 grace_period: 15s 818 utility_images: 819 clonerefs: "clonerefs:random" 820 initupload: "initupload:random" 821 entrypoint: "entrypoint:random" 822 sidecar: "sidecar:org" 823 gcs_configuration: 824 bucket: "ignore" 825 path_strategy: "legacy" 826 default_org: "random" 827 default_repo: "repo" 828 gcs_credentials_secret: "random-service-account" 829 830 periodics: 831 - name: kubernetes-defaulted-decoration 832 interval: 1h 833 decorate: true 834 spec: 835 containers: 836 - image: golang:latest 837 args: 838 - "test" 839 - "./..."`, 840 expected: &prowapi.DecorationConfig{ 841 Timeout: &prowapi.Duration{Duration: 2 * time.Hour}, 842 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 843 UtilityImages: &prowapi.UtilityImages{ 844 CloneRefs: "clonerefs:default", 845 InitUpload: "initupload:default", 846 Entrypoint: "entrypoint:default", 847 Sidecar: "sidecar:default", 848 }, 849 GCSConfiguration: &prowapi.GCSConfiguration{ 850 Bucket: "default-bucket", 851 PathStrategy: prowapi.PathStrategyLegacy, 852 DefaultOrg: "kubernetes", 853 DefaultRepo: "kubernetes", 854 }, 855 GCSCredentialsSecret: pStr("default-service-account"), 856 }, 857 }, 858 { 859 name: "with default, no explicit decorate", 860 rawConfig: ` 861 plank: 862 default_decoration_configs: 863 '*': 864 timeout: 2h 865 grace_period: 15s 866 utility_images: 867 clonerefs: "clonerefs:default" 868 initupload: "initupload:default" 869 entrypoint: "entrypoint:default" 870 sidecar: "sidecar:default" 871 gcs_configuration: 872 bucket: "default-bucket" 873 path_strategy: "legacy" 874 default_org: "kubernetes" 875 default_repo: "kubernetes" 876 gcs_credentials_secret: "default-service-account" 877 878 periodics: 879 - name: kubernetes-defaulted-decoration 880 interval: 1h 881 decorate: true 882 spec: 883 containers: 884 - image: golang:latest 885 args: 886 - "test" 887 - "./..."`, 888 expected: &prowapi.DecorationConfig{ 889 Timeout: &prowapi.Duration{Duration: 2 * time.Hour}, 890 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 891 UtilityImages: &prowapi.UtilityImages{ 892 CloneRefs: "clonerefs:default", 893 InitUpload: "initupload:default", 894 Entrypoint: "entrypoint:default", 895 Sidecar: "sidecar:default", 896 }, 897 GCSConfiguration: &prowapi.GCSConfiguration{ 898 Bucket: "default-bucket", 899 PathStrategy: prowapi.PathStrategyLegacy, 900 DefaultOrg: "kubernetes", 901 DefaultRepo: "kubernetes", 902 }, 903 GCSCredentialsSecret: pStr("default-service-account"), 904 }, 905 }, 906 { 907 name: "with default, has explicit decorate", 908 rawConfig: ` 909 plank: 910 default_decoration_configs: 911 '*': 912 timeout: 2h 913 grace_period: 15s 914 utility_images: 915 clonerefs: "clonerefs:default" 916 initupload: "initupload:default" 917 entrypoint: "entrypoint:default" 918 sidecar: "sidecar:default" 919 gcs_configuration: 920 bucket: "default-bucket" 921 path_strategy: "legacy" 922 default_org: "kubernetes" 923 default_repo: "kubernetes" 924 gcs_credentials_secret: "default-service-account" 925 926 periodics: 927 - name: kubernetes-defaulted-decoration 928 interval: 1h 929 decorate: true 930 decoration_config: 931 timeout: 1 932 grace_period: 1 933 utility_images: 934 clonerefs: "clonerefs:explicit" 935 initupload: "initupload:explicit" 936 entrypoint: "entrypoint:explicit" 937 sidecar: "sidecar:explicit" 938 gcs_configuration: 939 bucket: "explicit-bucket" 940 path_strategy: "explicit" 941 gcs_credentials_secret: "explicit-service-account" 942 spec: 943 containers: 944 - image: golang:latest 945 args: 946 - "test" 947 - "./..."`, 948 expected: &prowapi.DecorationConfig{ 949 Timeout: &prowapi.Duration{Duration: 1 * time.Nanosecond}, 950 GracePeriod: &prowapi.Duration{Duration: 1 * time.Nanosecond}, 951 UtilityImages: &prowapi.UtilityImages{ 952 CloneRefs: "clonerefs:explicit", 953 InitUpload: "initupload:explicit", 954 Entrypoint: "entrypoint:explicit", 955 Sidecar: "sidecar:explicit", 956 }, 957 GCSConfiguration: &prowapi.GCSConfiguration{ 958 Bucket: "explicit-bucket", 959 PathStrategy: prowapi.PathStrategyExplicit, 960 DefaultOrg: "kubernetes", 961 DefaultRepo: "kubernetes", 962 }, 963 GCSCredentialsSecret: pStr("explicit-service-account"), 964 }, 965 }, 966 { 967 name: "with default, configures bucket explicitly", 968 rawConfig: ` 969 plank: 970 default_decoration_configs: 971 '*': 972 timeout: 2h 973 grace_period: 15s 974 utility_images: 975 clonerefs: "clonerefs:default" 976 initupload: "initupload:default" 977 entrypoint: "entrypoint:default" 978 sidecar: "sidecar:default" 979 gcs_configuration: 980 bucket: "default-bucket" 981 path_strategy: "legacy" 982 default_org: "kubernetes" 983 default_repo: "kubernetes" 984 mediaTypes: 985 log: text/plain 986 gcs_credentials_secret: "default-service-account" 987 988 periodics: 989 - name: kubernetes-defaulted-decoration 990 interval: 1h 991 decorate: true 992 decoration_config: 993 gcs_configuration: 994 bucket: "explicit-bucket" 995 gcs_credentials_secret: "explicit-service-account" 996 spec: 997 containers: 998 - image: golang:latest 999 args: 1000 - "test" 1001 - "./..."`, 1002 expected: &prowapi.DecorationConfig{ 1003 Timeout: &prowapi.Duration{Duration: 2 * time.Hour}, 1004 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 1005 UtilityImages: &prowapi.UtilityImages{ 1006 CloneRefs: "clonerefs:default", 1007 InitUpload: "initupload:default", 1008 Entrypoint: "entrypoint:default", 1009 Sidecar: "sidecar:default", 1010 }, 1011 GCSConfiguration: &prowapi.GCSConfiguration{ 1012 Bucket: "explicit-bucket", 1013 PathStrategy: prowapi.PathStrategyLegacy, 1014 DefaultOrg: "kubernetes", 1015 DefaultRepo: "kubernetes", 1016 MediaTypes: map[string]string{"log": "text/plain"}, 1017 }, 1018 GCSCredentialsSecret: pStr("explicit-service-account"), 1019 }, 1020 }, 1021 { 1022 name: "Just the timeout is overwritten via more specific default_decoration_config ", 1023 rawConfig: ` 1024 plank: 1025 default_decoration_configs: 1026 '*': 1027 timeout: 2h 1028 grace_period: 15s 1029 utility_images: 1030 clonerefs: "clonerefs:default" 1031 initupload: "initupload:default" 1032 entrypoint: "entrypoint:default" 1033 sidecar: "sidecar:default" 1034 gcs_configuration: 1035 bucket: "default-bucket" 1036 path_strategy: "legacy" 1037 default_org: "kubernetes" 1038 default_repo: "kubernetes" 1039 mediaTypes: 1040 log: text/plain 1041 gcs_credentials_secret: "default-service-account" 1042 'org/repo': 1043 timeout: 4h 1044 1045 periodics: 1046 - name: kubernetes-defaulted-decoration 1047 interval: 1h 1048 decorate: true 1049 extra_refs: 1050 - org: org 1051 repo: repo 1052 spec: 1053 containers: 1054 - image: golang:latest 1055 args: 1056 - "test" 1057 - "./..."`, 1058 expected: &prowapi.DecorationConfig{ 1059 Timeout: &prowapi.Duration{Duration: 4 * time.Hour}, 1060 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 1061 UtilityImages: &prowapi.UtilityImages{ 1062 CloneRefs: "clonerefs:default", 1063 InitUpload: "initupload:default", 1064 Entrypoint: "entrypoint:default", 1065 Sidecar: "sidecar:default", 1066 }, 1067 GCSConfiguration: &prowapi.GCSConfiguration{ 1068 Bucket: "default-bucket", 1069 PathStrategy: prowapi.PathStrategyLegacy, 1070 DefaultOrg: "kubernetes", 1071 DefaultRepo: "kubernetes", 1072 MediaTypes: map[string]string{"log": "text/plain"}, 1073 }, 1074 GCSCredentialsSecret: pStr("default-service-account"), 1075 }, 1076 }, 1077 { 1078 name: "new format; global, org, repo, cluster, org+cluster", 1079 rawConfig: ` 1080 plank: 1081 default_decoration_config_entries: 1082 - config: 1083 timeout: 2h 1084 grace_period: 15s 1085 utility_images: 1086 clonerefs: "clonerefs:default" 1087 initupload: "initupload:default" 1088 entrypoint: "entrypoint:default" 1089 sidecar: "sidecar:default" 1090 gcs_configuration: 1091 bucket: "default-bucket" 1092 path_strategy: "legacy" 1093 default_org: "kubernetes" 1094 default_repo: "kubernetes" 1095 gcs_credentials_secret: "default-service-account" 1096 - repo: "org" 1097 config: 1098 timeout: 1h 1099 - repo: "org/repo" 1100 config: 1101 timeout: 3h 1102 - cluster: "trusted" 1103 config: 1104 grace_period: 30s 1105 - repo: "org/foo" 1106 cluster: "trusted" 1107 config: 1108 grace_period: 1m 1109 1110 periodics: 1111 - name: kubernetes-defaulted-decoration 1112 interval: 1h 1113 decorate: true 1114 cluster: trusted 1115 extra_refs: 1116 - org: org 1117 repo: foo 1118 base_ref: master 1119 spec: 1120 containers: 1121 - image: golang:latest 1122 args: 1123 - "test" 1124 - "./..." 1125 `, 1126 expected: &prowapi.DecorationConfig{ 1127 Timeout: &prowapi.Duration{Duration: 1 * time.Hour}, 1128 GracePeriod: &prowapi.Duration{Duration: 1 * time.Minute}, 1129 UtilityImages: &prowapi.UtilityImages{ 1130 CloneRefs: "clonerefs:default", 1131 InitUpload: "initupload:default", 1132 Entrypoint: "entrypoint:default", 1133 Sidecar: "sidecar:default", 1134 }, 1135 GCSConfiguration: &prowapi.GCSConfiguration{ 1136 Bucket: "default-bucket", 1137 PathStrategy: prowapi.PathStrategyLegacy, 1138 DefaultOrg: "kubernetes", 1139 DefaultRepo: "kubernetes", 1140 }, 1141 GCSCredentialsSecret: pStr("default-service-account"), 1142 }, 1143 }, 1144 } 1145 1146 for _, tc := range testCases { 1147 t.Run(tc.name, func(t *testing.T) { 1148 // save the config 1149 prowConfigDir := t.TempDir() 1150 1151 prowConfig := filepath.Join(prowConfigDir, "config.yaml") 1152 if err := os.WriteFile(prowConfig, []byte(tc.rawConfig), 0666); err != nil { 1153 t.Fatalf("fail to write prow config: %v", err) 1154 } 1155 1156 // all errors in Load should also apply in LoadStrict 1157 // some errors in LoadStrict will not apply in Load 1158 tc.expectStrictError = tc.expectStrictError || tc.expectError 1159 _, err := LoadStrict(prowConfig, "", nil, "") 1160 if tc.expectStrictError && err == nil { 1161 t.Errorf("tc %s: Expect error for LoadStrict, but got nil", tc.name) 1162 } else if !tc.expectStrictError && err != nil { 1163 t.Fatalf("tc %s: Expect no error for LoadStrict, but got error %v", tc.name, err) 1164 } 1165 1166 cfg, err := Load(prowConfig, "", nil, "") 1167 if tc.expectError && err == nil { 1168 t.Errorf("tc %s: Expect error for Load, but got nil", tc.name) 1169 } else if !tc.expectError && err != nil { 1170 t.Fatalf("tc %s: Expect no error for Load, but got error %v", tc.name, err) 1171 } 1172 1173 if tc.expected != nil { 1174 if len(cfg.Periodics) != 1 { 1175 t.Fatalf("tc %s: Expect to have one periodic job, got none", tc.name) 1176 } 1177 1178 if diff := cmp.Diff(cfg.Periodics[0].DecorationConfig, tc.expected, cmpopts.EquateEmpty()); diff != "" { 1179 t.Errorf("got diff: %s", diff) 1180 } 1181 } 1182 }) 1183 } 1184 } 1185 1186 func TestGerritRawYaml(t *testing.T) { 1187 t.Parallel() 1188 var testCases = []struct { 1189 name string 1190 expectError bool 1191 rawConfig string 1192 expected Gerrit 1193 }{ 1194 { 1195 name: "no-default", 1196 expectError: false, 1197 rawConfig: ` 1198 gerrit: 1199 `, 1200 expected: Gerrit{ 1201 TickInterval: &metav1.Duration{Duration: time.Minute}, 1202 RateLimit: 5, 1203 }, 1204 }, 1205 { 1206 name: "override-default", 1207 expectError: false, 1208 rawConfig: ` 1209 gerrit: 1210 tick_interval: 2s 1211 ratelimit: 10 1212 allowed_presubmit_trigger_re: "/test units" 1213 `, 1214 expected: Gerrit{ 1215 AllowedPresubmitTriggerReRawString: "/test units", 1216 TickInterval: &metav1.Duration{Duration: time.Second * 2}, 1217 RateLimit: 10, 1218 }, 1219 }, 1220 { 1221 name: "simple-org-repo", 1222 expectError: false, 1223 rawConfig: ` 1224 gerrit: 1225 org_repos_config: 1226 - org: org-a 1227 repos: 1228 - repo-b 1229 `, 1230 expected: Gerrit{ 1231 TickInterval: &metav1.Duration{Duration: time.Minute}, 1232 RateLimit: 5, 1233 OrgReposConfig: &GerritOrgRepoConfigs{ 1234 { 1235 Org: "org-a", 1236 Repos: []string{"repo-b"}, 1237 }, 1238 }, 1239 }, 1240 }, 1241 { 1242 name: "multiple-org-repo", 1243 expectError: false, 1244 rawConfig: ` 1245 gerrit: 1246 org_repos_config: 1247 - org: org-a 1248 repos: 1249 - repo-b 1250 - org: org-c 1251 repos: 1252 - repo-d 1253 `, 1254 expected: Gerrit{ 1255 TickInterval: &metav1.Duration{Duration: time.Minute}, 1256 RateLimit: 5, 1257 OrgReposConfig: &GerritOrgRepoConfigs{ 1258 { 1259 Org: "org-a", 1260 Repos: []string{"repo-b"}, 1261 }, 1262 { 1263 Org: "org-c", 1264 Repos: []string{"repo-d"}, 1265 }, 1266 }, 1267 }, 1268 }, 1269 } 1270 1271 for _, tc := range testCases { 1272 t.Run(tc.name, func(t *testing.T) { 1273 // save the config 1274 prowConfigDir := t.TempDir() 1275 1276 prowConfig := filepath.Join(prowConfigDir, "config.yaml") 1277 if err := os.WriteFile(prowConfig, []byte(tc.rawConfig), 0666); err != nil { 1278 t.Fatalf("fail to write prow config: %v", err) 1279 } 1280 1281 cfg, err := Load(prowConfig, "", nil, "") 1282 if tc.expectError && err == nil { 1283 t.Errorf("tc %s: Expect error, but got nil", tc.name) 1284 } else if !tc.expectError && err != nil { 1285 t.Fatalf("tc %s: Expect no error, but got error %v", tc.name, err) 1286 } 1287 1288 if d := cmp.Diff(tc.expected, cfg.Gerrit, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(Gerrit{}, "AllowedPresubmitTriggerRe")); d != "" { 1289 t.Errorf("got d: %s", d) 1290 } 1291 }) 1292 } 1293 } 1294 1295 func TestDisabledClustersRawYaml(t *testing.T) { 1296 t.Parallel() 1297 var testCases = []struct { 1298 name string 1299 expectError bool 1300 rawConfig string 1301 expected []string 1302 }{ 1303 { 1304 name: "default value", 1305 expectError: false, 1306 rawConfig: `a: b`, 1307 }, 1308 { 1309 name: "basic case", 1310 expectError: false, 1311 rawConfig: `disabled_clusters: 1312 - build01 1313 - build08 1314 `, 1315 expected: []string{"build01", "build08"}, 1316 }, 1317 { 1318 name: "duplicates without ordering", 1319 expectError: false, 1320 rawConfig: `disabled_clusters: 1321 - build08 1322 - build08 1323 - build01 1324 `, 1325 expected: []string{"build08", "build08", "build01"}, 1326 }, 1327 } 1328 1329 for _, tc := range testCases { 1330 t.Run(tc.name, func(t *testing.T) { 1331 // save the config 1332 prowConfigDir := t.TempDir() 1333 1334 prowConfig := filepath.Join(prowConfigDir, "config.yaml") 1335 if err := os.WriteFile(prowConfig, []byte(tc.rawConfig), 0666); err != nil { 1336 t.Fatalf("fail to write prow config: %v", err) 1337 } 1338 1339 cfg, err := Load(prowConfig, "", nil, "") 1340 if tc.expectError && err == nil { 1341 t.Errorf("tc %s: Expect error, but got nil", tc.name) 1342 } else if !tc.expectError && err != nil { 1343 t.Fatalf("tc %s: Expect no error, but got error %v", tc.name, err) 1344 } 1345 1346 if d := cmp.Diff(tc.expected, cfg.DisabledClusters); d != "" { 1347 t.Errorf("got d: %s", d) 1348 } 1349 }) 1350 } 1351 } 1352 1353 func TestValidateAgent(t *testing.T) { 1354 jenk := string(prowapi.JenkinsAgent) 1355 k := string(prowapi.KubernetesAgent) 1356 ns := "default" 1357 base := JobBase{ 1358 Agent: k, 1359 Namespace: &ns, 1360 Spec: &v1.PodSpec{}, 1361 UtilityConfig: UtilityConfig{ 1362 DecorationConfig: &prowapi.DecorationConfig{}, 1363 }, 1364 } 1365 1366 cases := []struct { 1367 name string 1368 base func(j *JobBase) 1369 pass bool 1370 }{ 1371 { 1372 name: "accept unknown agent", 1373 base: func(j *JobBase) { 1374 j.Agent = "random-agent" 1375 }, 1376 pass: true, 1377 }, 1378 { 1379 name: "kubernetes agent requires spec", 1380 base: func(j *JobBase) { 1381 j.Spec = nil 1382 }, 1383 }, 1384 { 1385 name: "non-nil namespace required", 1386 base: func(j *JobBase) { 1387 j.Namespace = nil 1388 }, 1389 }, 1390 { 1391 name: "filled namespace required", 1392 base: func(j *JobBase) { 1393 var s string 1394 j.Namespace = &s 1395 }, 1396 }, 1397 { 1398 name: "custom namespace requires knative-build agent", 1399 base: func(j *JobBase) { 1400 s := "custom-namespace" 1401 j.Namespace = &s 1402 }, 1403 }, 1404 { 1405 name: "accept kubernetes agent", 1406 pass: true, 1407 }, 1408 { 1409 name: "accept kubernetes agent without decoration", 1410 base: func(j *JobBase) { 1411 j.DecorationConfig = nil 1412 }, 1413 pass: true, 1414 }, 1415 { 1416 name: "accept jenkins agent", 1417 base: func(j *JobBase) { 1418 j.Agent = jenk 1419 j.Spec = nil 1420 j.DecorationConfig = nil 1421 }, 1422 pass: true, 1423 }, 1424 { 1425 name: "error_on_eviction allowed for kubernetes agent", 1426 base: func(j *JobBase) { 1427 j.ErrorOnEviction = true 1428 }, 1429 pass: true, 1430 }, 1431 } 1432 1433 for _, tc := range cases { 1434 t.Run(tc.name, func(t *testing.T) { 1435 jb := base 1436 if tc.base != nil { 1437 tc.base(&jb) 1438 } 1439 switch err := validateAgent(jb, ns); { 1440 case err == nil && !tc.pass: 1441 t.Error("validation failed to raise an error") 1442 case err != nil && tc.pass: 1443 t.Errorf("validation should have passed, got: %v", err) 1444 } 1445 }) 1446 } 1447 } 1448 1449 func TestValidatePodSpec(t *testing.T) { 1450 periodEnv := sets.New[string](downwardapi.EnvForType(prowapi.PeriodicJob)...) 1451 postEnv := sets.New[string](downwardapi.EnvForType(prowapi.PostsubmitJob)...) 1452 preEnv := sets.New[string](downwardapi.EnvForType(prowapi.PresubmitJob)...) 1453 cases := []struct { 1454 name string 1455 jobType prowapi.ProwJobType 1456 spec func(s *v1.PodSpec) 1457 decorationConfig *prowapi.DecorationConfig 1458 noSpec bool 1459 pass bool 1460 }{ 1461 { 1462 name: "allow nil spec", 1463 noSpec: true, 1464 pass: true, 1465 }, 1466 { 1467 name: "happy case", 1468 pass: true, 1469 }, 1470 { 1471 name: "reject init containers", 1472 spec: func(s *v1.PodSpec) { 1473 s.InitContainers = []v1.Container{ 1474 {}, 1475 } 1476 }, 1477 }, 1478 { 1479 name: "reject 0 containers", 1480 spec: func(s *v1.PodSpec) { 1481 s.Containers = nil 1482 }, 1483 }, 1484 { 1485 name: "reject 2 containers", 1486 spec: func(s *v1.PodSpec) { 1487 s.Containers = append(s.Containers, v1.Container{}) 1488 }, 1489 }, 1490 { 1491 name: "reject reserved presubmit env", 1492 jobType: prowapi.PresubmitJob, 1493 spec: func(s *v1.PodSpec) { 1494 // find a presubmit value 1495 for n := range preEnv.Difference(postEnv).Difference(periodEnv) { 1496 1497 s.Containers[0].Env = append(s.Containers[0].Env, v1.EnvVar{Name: n, Value: "whatever"}) 1498 } 1499 if len(s.Containers[0].Env) == 0 { 1500 t.Fatal("empty env") 1501 } 1502 }, 1503 }, 1504 { 1505 name: "reject reserved postsubmit env", 1506 jobType: prowapi.PostsubmitJob, 1507 spec: func(s *v1.PodSpec) { 1508 // find a postsubmit value 1509 for n := range postEnv.Difference(periodEnv) { 1510 1511 s.Containers[0].Env = append(s.Containers[0].Env, v1.EnvVar{Name: n, Value: "whatever"}) 1512 } 1513 if len(s.Containers[0].Env) == 0 { 1514 t.Fatal("empty env") 1515 } 1516 }, 1517 }, 1518 { 1519 name: "reject reserved periodic env", 1520 jobType: prowapi.PeriodicJob, 1521 spec: func(s *v1.PodSpec) { 1522 // find a postsubmit value 1523 for n := range periodEnv { 1524 1525 s.Containers[0].Env = append(s.Containers[0].Env, v1.EnvVar{Name: n, Value: "whatever"}) 1526 } 1527 if len(s.Containers[0].Env) == 0 { 1528 t.Fatal("empty env") 1529 } 1530 }, 1531 }, 1532 { 1533 name: "reject reserved mount name", 1534 spec: func(s *v1.PodSpec) { 1535 s.Containers[0].VolumeMounts = append(s.Containers[0].VolumeMounts, v1.VolumeMount{ 1536 Name: sets.List(decorate.VolumeMounts(nil))[0], 1537 MountPath: "/whatever", 1538 }) 1539 }, 1540 }, 1541 { 1542 name: "reject reserved mount path", 1543 spec: func(s *v1.PodSpec) { 1544 s.Containers[0].VolumeMounts = append(s.Containers[0].VolumeMounts, v1.VolumeMount{ 1545 Name: "fun", 1546 MountPath: sets.List(decorate.VolumeMountPathsOnTestContainer())[0], 1547 }) 1548 }, 1549 }, 1550 { 1551 name: "accept conflicting mount path parent", 1552 spec: func(s *v1.PodSpec) { 1553 s.Containers[0].VolumeMounts = append(s.Containers[0].VolumeMounts, v1.VolumeMount{ 1554 Name: "foo", 1555 MountPath: filepath.Dir(sets.List(decorate.VolumeMountPathsOnTestContainer())[0]), 1556 }) 1557 s.Volumes = append(s.Volumes, v1.Volume{ 1558 Name: "foo", 1559 }) 1560 }, 1561 pass: true, 1562 }, 1563 { 1564 name: "accept conflicting mount path child", 1565 spec: func(s *v1.PodSpec) { 1566 s.Containers[0].VolumeMounts = append(s.Containers[0].VolumeMounts, v1.VolumeMount{ 1567 Name: "foo", 1568 MountPath: filepath.Join(sets.List(decorate.VolumeMountPathsOnTestContainer())[0], "extra"), 1569 }) 1570 s.Volumes = append(s.Volumes, v1.Volume{ 1571 Name: "foo", 1572 }) 1573 }, 1574 pass: true, 1575 }, 1576 { 1577 name: "accept mount path that works only through decoration volume", 1578 spec: func(s *v1.PodSpec) { 1579 s.Containers[0].VolumeMounts = append(s.Containers[0].VolumeMounts, v1.VolumeMount{ 1580 Name: "gcs-credentials", 1581 MountPath: "/secrets/gcs", 1582 }) 1583 }, 1584 pass: true, 1585 }, 1586 { 1587 name: "accept mount path that works only through decoration volume specified by user", 1588 decorationConfig: &prowapi.DecorationConfig{OauthTokenSecret: &prowapi.OauthTokenSecret{Name: "my-oauth-secret-name", Key: "oauth"}}, 1589 spec: func(s *v1.PodSpec) { 1590 s.Containers[0].VolumeMounts = append(s.Containers[0].VolumeMounts, v1.VolumeMount{ 1591 Name: "my-oauth-secret-name", 1592 MountPath: "/secrets/oauth", 1593 }) 1594 }, 1595 pass: true, 1596 }, 1597 { 1598 name: "accept multiple mount paths that works only through decoration volume specified by user", 1599 decorationConfig: &prowapi.DecorationConfig{ 1600 OauthTokenSecret: &prowapi.OauthTokenSecret{Name: "my-oauth-secret-name", Key: "oauth"}, 1601 SSHKeySecrets: []string{"ssh-private-1", "ssh-private-2"}, 1602 }, 1603 spec: func(s *v1.PodSpec) { 1604 s.Containers[0].VolumeMounts = append(s.Containers[0].VolumeMounts, []v1.VolumeMount{ 1605 { 1606 Name: "my-oauth-secret-name", 1607 MountPath: "/secrets/oauth", 1608 }, 1609 { 1610 Name: "ssh-private-1", 1611 MountPath: "/secrets/ssh-private-1", 1612 }, 1613 { 1614 Name: "ssh-private-2", 1615 MountPath: "/secrets/ssh-private-2", 1616 }, 1617 }...) 1618 }, 1619 pass: true, 1620 }, 1621 { 1622 name: "reject reserved volume", 1623 spec: func(s *v1.PodSpec) { 1624 s.Volumes = append(s.Volumes, v1.Volume{Name: sets.List(decorate.VolumeMounts(nil))[0]}) 1625 }, 1626 }, 1627 { 1628 name: "reject duplicate env", 1629 spec: func(s *v1.PodSpec) { 1630 s.Containers[0].Env = append(s.Containers[0].Env, v1.EnvVar{Name: "foo", Value: "bar"}) 1631 s.Containers[0].Env = append(s.Containers[0].Env, v1.EnvVar{Name: "foo", Value: "baz"}) 1632 }, 1633 }, 1634 { 1635 name: "reject duplicate volume", 1636 spec: func(s *v1.PodSpec) { 1637 s.Volumes = append(s.Volumes, v1.Volume{Name: "foo"}) 1638 s.Volumes = append(s.Volumes, v1.Volume{Name: "foo"}) 1639 }, 1640 }, 1641 { 1642 name: "reject undefined volume reference", 1643 spec: func(s *v1.PodSpec) { 1644 s.Containers[0].VolumeMounts = append(s.Containers[0].VolumeMounts, v1.VolumeMount{Name: "foo", MountPath: "/not-used-by-decoration-utils"}) 1645 }, 1646 }, 1647 } 1648 1649 spec := v1.PodSpec{ 1650 Containers: []v1.Container{ 1651 {}, 1652 }, 1653 } 1654 1655 for _, tc := range cases { 1656 t.Run(tc.name, func(t *testing.T) { 1657 jt := prowapi.PresubmitJob 1658 if tc.jobType != "" { 1659 jt = tc.jobType 1660 } 1661 current := spec.DeepCopy() 1662 if tc.noSpec { 1663 current = nil 1664 } else if tc.spec != nil { 1665 tc.spec(current) 1666 } 1667 switch err := validatePodSpec(jt, current, tc.decorationConfig); { 1668 case err == nil && !tc.pass: 1669 t.Error("validation failed to raise an error") 1670 case err != nil && tc.pass: 1671 t.Errorf("validation should have passed, got: %v", err) 1672 } 1673 }) 1674 } 1675 } 1676 1677 func TestValidatePipelineRunSpec(t *testing.T) { 1678 cases := []struct { 1679 name string 1680 jobType prowapi.ProwJobType 1681 spec func(s *pipelinev1beta1.PipelineRunSpec) 1682 extraRefs []prowapi.Refs 1683 noSpec bool 1684 pass bool 1685 }{ 1686 { 1687 name: "allow nil spec", 1688 noSpec: true, 1689 pass: true, 1690 }, 1691 { 1692 name: "happy case", 1693 pass: true, 1694 }, 1695 { 1696 name: "reject implicit ref for periodic", 1697 jobType: prowapi.PeriodicJob, 1698 spec: func(s *pipelinev1beta1.PipelineRunSpec) { 1699 s.PipelineSpec = &pipelinev1beta1.PipelineSpec{ 1700 Tasks: []pipelinev1beta1.PipelineTask{{Name: "git ref", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_IMPLICIT_GIT_REF"}}}} 1701 }, 1702 pass: false, 1703 }, 1704 { 1705 name: "allow implicit ref for presubmit", 1706 jobType: prowapi.PresubmitJob, 1707 spec: func(s *pipelinev1beta1.PipelineRunSpec) { 1708 s.PipelineSpec = &pipelinev1beta1.PipelineSpec{ 1709 Tasks: []pipelinev1beta1.PipelineTask{{Name: "git ref", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_IMPLICIT_GIT_REF"}}}} 1710 }, 1711 pass: true, 1712 }, 1713 { 1714 name: "allow implicit ref for postsubmit", 1715 jobType: prowapi.PostsubmitJob, 1716 spec: func(s *pipelinev1beta1.PipelineRunSpec) { 1717 s.PipelineSpec = &pipelinev1beta1.PipelineSpec{ 1718 Tasks: []pipelinev1beta1.PipelineTask{{Name: "git ref", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_IMPLICIT_GIT_REF"}}}} 1719 }, 1720 pass: true, 1721 }, 1722 { 1723 name: "reject extra refs usage with no extra refs", 1724 spec: func(s *pipelinev1beta1.PipelineRunSpec) { 1725 s.PipelineSpec = &pipelinev1beta1.PipelineSpec{ 1726 Tasks: []pipelinev1beta1.PipelineTask{{Name: "git ref", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_EXTRA_GIT_REF_0"}}}} 1727 }, 1728 pass: false, 1729 }, 1730 { 1731 name: "allow extra refs usage with extra refs", 1732 spec: func(s *pipelinev1beta1.PipelineRunSpec) { 1733 s.PipelineSpec = &pipelinev1beta1.PipelineSpec{ 1734 Tasks: []pipelinev1beta1.PipelineTask{{Name: "git ref", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_EXTRA_GIT_REF_0"}}}} 1735 }, 1736 extraRefs: []prowapi.Refs{{Org: "o", Repo: "r"}}, 1737 pass: true, 1738 }, 1739 { 1740 name: "reject wrong extra refs index usage", 1741 spec: func(s *pipelinev1beta1.PipelineRunSpec) { 1742 s.PipelineSpec = &pipelinev1beta1.PipelineSpec{ 1743 Tasks: []pipelinev1beta1.PipelineTask{{Name: "git ref", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_EXTRA_GIT_REF_1"}}}} 1744 }, 1745 extraRefs: []prowapi.Refs{{Org: "o", Repo: "r"}}, 1746 pass: false, 1747 }, 1748 { 1749 name: "reject extra refs without usage", 1750 extraRefs: []prowapi.Refs{{Org: "o", Repo: "r"}}, 1751 pass: false, 1752 }, 1753 { 1754 name: "allow unrelated resource refs", 1755 spec: func(s *pipelinev1beta1.PipelineRunSpec) { 1756 s.PipelineSpec = &pipelinev1beta1.PipelineSpec{ 1757 Tasks: []pipelinev1beta1.PipelineTask{{Name: "git ref", TaskRef: &pipelinev1beta1.TaskRef{Name: "some-other-ref"}}}} 1758 }, 1759 pass: true, 1760 }, 1761 { 1762 name: "reject leading zeros when extra ref usage is otherwise valid", 1763 spec: func(s *pipelinev1beta1.PipelineRunSpec) { 1764 s.PipelineSpec = &pipelinev1beta1.PipelineSpec{ 1765 Tasks: []pipelinev1beta1.PipelineTask{{Name: "git ref", TaskRef: &pipelinev1beta1.TaskRef{Name: "PROW_EXTRA_GIT_REF_000"}}}} 1766 }, 1767 extraRefs: []prowapi.Refs{{Org: "o", Repo: "r"}}, 1768 pass: false, 1769 }, 1770 } 1771 1772 spec := pipelinev1beta1.PipelineRunSpec{} 1773 1774 for _, tc := range cases { 1775 t.Run(tc.name, func(t *testing.T) { 1776 jt := prowapi.PresubmitJob 1777 if tc.jobType != "" { 1778 jt = tc.jobType 1779 } 1780 current := spec.DeepCopy() 1781 if tc.noSpec { 1782 current = nil 1783 } else if tc.spec != nil { 1784 tc.spec(current) 1785 } 1786 switch err := ValidatePipelineRunSpec(jt, tc.extraRefs, current); { 1787 case err == nil && !tc.pass: 1788 t.Error("validation failed to raise an error") 1789 case err != nil && tc.pass: 1790 t.Errorf("validation should have passed, got: %v", err) 1791 } 1792 }) 1793 } 1794 } 1795 1796 func TestValidateDecoration(t *testing.T) { 1797 defCfg := prowapi.DecorationConfig{ 1798 UtilityImages: &prowapi.UtilityImages{ 1799 CloneRefs: "clone-me", 1800 InitUpload: "upload-me", 1801 Entrypoint: "enter-me", 1802 Sidecar: "official-drink-of-the-org", 1803 }, 1804 GCSCredentialsSecret: pStr("upload-secret"), 1805 GCSConfiguration: &prowapi.GCSConfiguration{ 1806 PathStrategy: prowapi.PathStrategyExplicit, 1807 DefaultOrg: "so-org", 1808 DefaultRepo: "very-repo", 1809 }, 1810 } 1811 cases := []struct { 1812 name string 1813 container v1.Container 1814 config *prowapi.DecorationConfig 1815 pass bool 1816 }{ 1817 { 1818 name: "allow no decoration", 1819 pass: true, 1820 }, 1821 { 1822 name: "happy case with cmd", 1823 config: &defCfg, 1824 container: v1.Container{ 1825 Command: []string{"hello", "world"}, 1826 }, 1827 pass: true, 1828 }, 1829 { 1830 name: "happy case with args", 1831 config: &defCfg, 1832 container: v1.Container{ 1833 Args: []string{"hello", "world"}, 1834 }, 1835 pass: true, 1836 }, 1837 { 1838 name: "reject invalid decoration config", 1839 config: &prowapi.DecorationConfig{}, 1840 container: v1.Container{ 1841 Command: []string{"hello", "world"}, 1842 }, 1843 }, 1844 { 1845 name: "reject container that has no cmd, no args", 1846 config: &defCfg, 1847 }, 1848 } 1849 for _, tc := range cases { 1850 t.Run(tc.name, func(t *testing.T) { 1851 switch err := validateDecoration(tc.container, tc.config); { 1852 case err == nil && !tc.pass: 1853 t.Error("validation failed to raise an error") 1854 case err != nil && tc.pass: 1855 t.Errorf("validation should have passed, got: %v", err) 1856 } 1857 }) 1858 } 1859 } 1860 1861 func TestValidateLabels(t *testing.T) { 1862 cases := []struct { 1863 name string 1864 labels map[string]string 1865 pass bool 1866 }{ 1867 { 1868 name: "happy case", 1869 pass: true, 1870 }, 1871 { 1872 name: "reject reserved label", 1873 labels: map[string]string{ 1874 decorate.Labels()[0]: "anything", 1875 }, 1876 }, 1877 { 1878 name: "reject bad label key", 1879 labels: map[string]string{ 1880 "_underscore-prefix": "annoying", 1881 }, 1882 }, 1883 { 1884 name: "reject bad label value", 1885 labels: map[string]string{ 1886 "whatever": "_private-is-rejected", 1887 }, 1888 }, 1889 } 1890 1891 for _, tc := range cases { 1892 t.Run(tc.name, func(t *testing.T) { 1893 switch err := validateLabels(tc.labels); { 1894 case err == nil && !tc.pass: 1895 t.Error("validation failed to raise an error") 1896 case err != nil && tc.pass: 1897 t.Errorf("validation should have passed, got: %v", err) 1898 } 1899 }) 1900 } 1901 } 1902 1903 func TestValidateMultipleContainers(t *testing.T) { 1904 ka := string(prowapi.KubernetesAgent) 1905 yes := true 1906 defCfg := prowapi.DecorationConfig{ 1907 UtilityImages: &prowapi.UtilityImages{ 1908 CloneRefs: "clone-me", 1909 InitUpload: "upload-me", 1910 Entrypoint: "enter-me", 1911 Sidecar: "official-drink-of-the-org", 1912 }, 1913 GCSCredentialsSecret: pStr("upload-secret"), 1914 GCSConfiguration: &prowapi.GCSConfiguration{ 1915 PathStrategy: prowapi.PathStrategyExplicit, 1916 DefaultOrg: "so-org", 1917 DefaultRepo: "very-repo", 1918 }, 1919 } 1920 goodSpec := v1.PodSpec{ 1921 Containers: []v1.Container{ 1922 { 1923 Name: "test1", 1924 Command: []string{"hello", "world"}, 1925 }, 1926 { 1927 Name: "test2", 1928 Args: []string{"hello", "world"}, 1929 }, 1930 }, 1931 } 1932 cfg := Config{ 1933 ProwConfig: ProwConfig{PodNamespace: "target-namespace"}, 1934 } 1935 cases := []struct { 1936 name string 1937 base JobBase 1938 pass bool 1939 }{ 1940 { 1941 name: "valid kubernetes job with multiple containers", 1942 base: JobBase{ 1943 Name: "name", 1944 Agent: ka, 1945 UtilityConfig: UtilityConfig{Decorate: &yes, DecorationConfig: &defCfg}, 1946 Spec: &goodSpec, 1947 Namespace: &cfg.PodNamespace, 1948 }, 1949 pass: true, 1950 }, 1951 { 1952 name: "invalid: containers with no cmd or args", 1953 base: JobBase{ 1954 Name: "name", 1955 Agent: ka, 1956 UtilityConfig: UtilityConfig{Decorate: &yes, DecorationConfig: &defCfg}, 1957 Spec: &v1.PodSpec{ 1958 Containers: []v1.Container{ 1959 { 1960 Name: "test1", 1961 }, 1962 { 1963 Name: "test2", 1964 }, 1965 }, 1966 }, 1967 Namespace: &cfg.PodNamespace, 1968 }, 1969 }, 1970 { 1971 name: "invalid: containers with no names", 1972 base: JobBase{ 1973 Name: "name", 1974 Agent: ka, 1975 UtilityConfig: UtilityConfig{Decorate: &yes, DecorationConfig: &defCfg}, 1976 Spec: &v1.PodSpec{ 1977 Containers: []v1.Container{ 1978 { 1979 Command: []string{"hello", "world"}, 1980 }, 1981 { 1982 Args: []string{"hello", "world"}, 1983 }, 1984 }, 1985 }, 1986 Namespace: &cfg.PodNamespace, 1987 }, 1988 }, 1989 { 1990 name: "invalid: no decoration enabled", 1991 base: JobBase{ 1992 Name: "name", 1993 Agent: ka, 1994 Spec: &goodSpec, 1995 Namespace: &cfg.PodNamespace, 1996 }, 1997 }, 1998 { 1999 name: "invalid: container names reserved for decoration", 2000 base: JobBase{ 2001 Name: "name", 2002 Agent: ka, 2003 UtilityConfig: UtilityConfig{Decorate: &yes, DecorationConfig: &defCfg}, 2004 Spec: &v1.PodSpec{ 2005 Containers: []v1.Container{ 2006 { 2007 Name: "place-entrypoint", 2008 Command: []string{"hello", "world"}, 2009 }, 2010 { 2011 Name: "sidecar", 2012 Args: []string{"hello", "world"}, 2013 }, 2014 }, 2015 }, Namespace: &cfg.PodNamespace, 2016 }, 2017 }, 2018 } 2019 2020 for _, tc := range cases { 2021 t.Run(tc.name, func(t *testing.T) { 2022 switch err := cfg.validateJobBase(tc.base, prowapi.PresubmitJob); { 2023 case err == nil && !tc.pass: 2024 t.Error("validation failed to raise an error") 2025 case err != nil && tc.pass: 2026 t.Errorf("validation should have passed, got: %v", err) 2027 } 2028 }) 2029 } 2030 } 2031 2032 func TestValidateJobBase(t *testing.T) { 2033 ka := string(prowapi.KubernetesAgent) 2034 ja := string(prowapi.JenkinsAgent) 2035 goodSpec := v1.PodSpec{ 2036 Containers: []v1.Container{ 2037 {}, 2038 }, 2039 } 2040 cfg := Config{ 2041 ProwConfig: ProwConfig{ 2042 Plank: Plank{JobQueueCapacities: map[string]int{"queue": 0}}, 2043 PodNamespace: "target-namespace", 2044 }, 2045 } 2046 cases := []struct { 2047 name string 2048 base JobBase 2049 pass bool 2050 }{ 2051 { 2052 name: "valid kubernetes job", 2053 base: JobBase{ 2054 Name: "name", 2055 Agent: ka, 2056 Spec: &goodSpec, 2057 Namespace: &cfg.PodNamespace, 2058 }, 2059 pass: true, 2060 }, 2061 { 2062 name: "valid jenkins job", 2063 base: JobBase{ 2064 Name: "name", 2065 Agent: ja, 2066 Namespace: &cfg.PodNamespace, 2067 }, 2068 pass: true, 2069 }, 2070 { 2071 name: "valid jenkins job - nested job", 2072 base: JobBase{ 2073 Name: "folder/job", 2074 Agent: ja, 2075 Namespace: &cfg.PodNamespace, 2076 }, 2077 pass: true, 2078 }, 2079 { 2080 name: "invalid jenkins job", 2081 base: JobBase{ 2082 Name: "job.", 2083 Agent: ja, 2084 Namespace: &cfg.PodNamespace, 2085 }, 2086 pass: false, 2087 }, 2088 { 2089 name: "invalid concurrency", 2090 base: JobBase{ 2091 Name: "name", 2092 MaxConcurrency: -1, 2093 Agent: ka, 2094 Spec: &goodSpec, 2095 Namespace: &cfg.PodNamespace, 2096 }, 2097 }, 2098 { 2099 name: "invalid pod spec", 2100 base: JobBase{ 2101 Name: "name", 2102 Agent: ka, 2103 Namespace: &cfg.PodNamespace, 2104 Spec: &v1.PodSpec{}, // no containers 2105 }, 2106 }, 2107 { 2108 name: "invalid decoration", 2109 base: JobBase{ 2110 Name: "name", 2111 Agent: ka, 2112 Spec: &goodSpec, 2113 UtilityConfig: UtilityConfig{ 2114 DecorationConfig: &prowapi.DecorationConfig{}, // missing many fields 2115 }, 2116 Namespace: &cfg.PodNamespace, 2117 }, 2118 }, 2119 { 2120 name: "invalid labels", 2121 base: JobBase{ 2122 Name: "name", 2123 Agent: ka, 2124 Spec: &goodSpec, 2125 Labels: map[string]string{ 2126 "_leading_underscore": "_rejected", 2127 }, 2128 Namespace: &cfg.PodNamespace, 2129 }, 2130 }, 2131 { 2132 name: "invalid name", 2133 base: JobBase{ 2134 Name: "a/b", 2135 Agent: ka, 2136 Spec: &goodSpec, 2137 Namespace: &cfg.PodNamespace, 2138 }, 2139 pass: false, 2140 }, 2141 { 2142 name: "valid complex name", 2143 base: JobBase{ 2144 Name: "a-b.c", 2145 Agent: ka, 2146 Spec: &goodSpec, 2147 Namespace: &cfg.PodNamespace, 2148 }, 2149 pass: true, 2150 }, 2151 { 2152 name: "invalid rerun_permissions", 2153 base: JobBase{ 2154 RerunAuthConfig: &prowapi.RerunAuthConfig{ 2155 AllowAnyone: true, 2156 GitHubUsers: []string{"user"}, 2157 }, 2158 }, 2159 pass: false, 2160 }, 2161 { 2162 name: "valid job queue name", 2163 base: JobBase{ 2164 Name: "name", 2165 JobQueueName: "queue", 2166 }, 2167 pass: true, 2168 }, 2169 { 2170 name: "invalid job queue name", 2171 base: JobBase{ 2172 Name: "name", 2173 JobQueueName: "invalid-queue", 2174 }, 2175 pass: false, 2176 }, 2177 } 2178 2179 for _, tc := range cases { 2180 t.Run(tc.name, func(t *testing.T) { 2181 switch err := cfg.validateJobBase(tc.base, prowapi.PresubmitJob); { 2182 case err == nil && !tc.pass: 2183 t.Error("validation failed to raise an error") 2184 case err != nil && tc.pass: 2185 t.Errorf("validation should have passed, got: %v", err) 2186 } 2187 }) 2188 } 2189 } 2190 2191 func TestValidateDeck(t *testing.T) { 2192 boolTrue := true 2193 boolFalse := false 2194 cases := []struct { 2195 name string 2196 deck Deck 2197 expectedErr string 2198 }{ 2199 { 2200 name: "empty Deck is valid", 2201 deck: Deck{}, 2202 expectedErr: "", 2203 }, 2204 { 2205 name: "AdditionalAllowedBuckets has items, SkipStoragePathValidation is false => no errors", 2206 deck: Deck{SkipStoragePathValidation: &boolFalse, AdditionalAllowedBuckets: []string{"foo", "bar", "batz"}}, 2207 expectedErr: "", 2208 }, 2209 { 2210 name: "AdditionalAllowedBuckets has items, SkipStoragePathValidation is default value => error", 2211 deck: Deck{AdditionalAllowedBuckets: []string{"hello", "world"}}, 2212 expectedErr: "skip_storage_path_validation is enabled", 2213 }, 2214 { 2215 name: "AdditionalAllowedBuckets has items, SkipStoragePathValidation is true => error", 2216 deck: Deck{SkipStoragePathValidation: &boolTrue, AdditionalAllowedBuckets: []string{"hello", "world"}}, 2217 expectedErr: "skip_storage_path_validation is enabled", 2218 }, 2219 } 2220 2221 for _, tc := range cases { 2222 t.Run(tc.name, func(t *testing.T) { 2223 expectingErr := len(tc.expectedErr) > 0 2224 err := tc.deck.Validate() 2225 if expectingErr && err == nil { 2226 t.Fatalf("expecting error (%v), but did not get an error", tc.expectedErr) 2227 } 2228 if !expectingErr && err != nil { 2229 t.Fatalf("not expecting error, but got an error: %v", err) 2230 } 2231 if expectingErr && err != nil && !strings.Contains(err.Error(), tc.expectedErr) { 2232 t.Fatalf("expected error (%v), but got unknown error, instead: %v", tc.expectedErr, err) 2233 } 2234 }) 2235 } 2236 } 2237 2238 func TestValidateRefs(t *testing.T) { 2239 cases := []struct { 2240 name string 2241 extraRefs []prowapi.Refs 2242 expected error 2243 }{ 2244 { 2245 name: "validation error for extra ref specifying the same repo for which the job is configured", 2246 extraRefs: []prowapi.Refs{ 2247 { 2248 Org: "org", 2249 Repo: "repo", 2250 }, 2251 }, 2252 expected: fmt.Errorf("invalid job test on repo org/repo: the following refs specified more than once: %s", 2253 "org/repo"), 2254 }, 2255 { 2256 name: "validation error lists all duplications", 2257 extraRefs: []prowapi.Refs{ 2258 { 2259 Org: "org", 2260 Repo: "repo", 2261 }, 2262 { 2263 Org: "org", 2264 Repo: "foo", 2265 }, 2266 { 2267 Org: "org", 2268 Repo: "bar", 2269 }, 2270 { 2271 Org: "org", 2272 Repo: "foo", 2273 }, 2274 }, 2275 expected: fmt.Errorf("invalid job test on repo org/repo: the following refs specified more than once: %s", 2276 "org/foo,org/repo"), 2277 }, 2278 { 2279 name: "no errors if there are no duplications", 2280 extraRefs: []prowapi.Refs{ 2281 { 2282 Org: "org", 2283 Repo: "foo", 2284 }, 2285 }, 2286 }, 2287 } 2288 2289 for _, tc := range cases { 2290 t.Run(tc.name, func(t *testing.T) { 2291 job := JobBase{ 2292 Name: "test", 2293 UtilityConfig: UtilityConfig{ 2294 ExtraRefs: tc.extraRefs, 2295 }, 2296 } 2297 if err := ValidateRefs("org/repo", job); !reflect.DeepEqual(err, tc.expected) { 2298 t.Errorf("expected %#v\n!=\nactual %#v", tc.expected, err) 2299 } 2300 }) 2301 } 2302 } 2303 2304 func TestValidateReportingWithGerritLabel(t *testing.T) { 2305 cases := []struct { 2306 name string 2307 labels map[string]string 2308 reporter Reporter 2309 expected error 2310 }{ 2311 { 2312 name: "no errors if job is set to report", 2313 reporter: Reporter{ 2314 Context: "context", 2315 }, 2316 labels: map[string]string{ 2317 kube.GerritReportLabel: "label", 2318 }, 2319 }, 2320 { 2321 name: "no errors if Gerrit report label is not defined", 2322 reporter: Reporter{SkipReport: true}, 2323 labels: map[string]string{ 2324 "label": "value", 2325 }, 2326 }, 2327 { 2328 name: "no errors if job is set to skip report and Gerrit report label is empty", 2329 reporter: Reporter{SkipReport: true}, 2330 labels: map[string]string{ 2331 kube.GerritReportLabel: "", 2332 }, 2333 }, 2334 { 2335 name: "error if job is set to skip report and Gerrit report label is set to non-empty", 2336 reporter: Reporter{SkipReport: true}, 2337 labels: map[string]string{ 2338 kube.GerritReportLabel: "label", 2339 }, 2340 expected: fmt.Errorf("gerrit report label %s set to non-empty string but job is configured to skip reporting.", kube.GerritReportLabel), 2341 }, 2342 } 2343 2344 for _, tc := range cases { 2345 t.Run(tc.name, func(t *testing.T) { 2346 cfg := Config{ 2347 ProwConfig: ProwConfig{ 2348 PodNamespace: "default-namespace", 2349 }, 2350 } 2351 base := JobBase{ 2352 Name: "test-job", 2353 Labels: tc.labels, 2354 } 2355 presubmits := []Presubmit{ 2356 { 2357 JobBase: base, 2358 Reporter: tc.reporter, 2359 }, 2360 } 2361 var expected error 2362 if tc.expected != nil { 2363 expected = fmt.Errorf("invalid presubmit job %s: %w", "test-job", tc.expected) 2364 } 2365 if err := cfg.validatePresubmits(presubmits); !reflect.DeepEqual(err, utilerrors.NewAggregate([]error{expected})) { 2366 t.Errorf("did not get expected validation result:\n%v", cmp.Diff(expected, err)) 2367 } 2368 2369 postsubmits := []Postsubmit{ 2370 { 2371 JobBase: base, 2372 Reporter: tc.reporter, 2373 }, 2374 } 2375 if tc.expected != nil { 2376 expected = fmt.Errorf("invalid postsubmit job %s: %w", "test-job", tc.expected) 2377 } 2378 if err := cfg.validatePostsubmits(postsubmits); !reflect.DeepEqual(err, utilerrors.NewAggregate([]error{expected})) { 2379 t.Errorf("did not get expected validation result:\n%v", cmp.Diff(expected, err)) 2380 } 2381 }) 2382 } 2383 } 2384 2385 func TestGerritAllRepos(t *testing.T) { 2386 tests := []struct { 2387 name string 2388 in *GerritOrgRepoConfigs 2389 want map[string]map[string]*GerritQueryFilter 2390 }{ 2391 { 2392 name: "multiple-org", 2393 in: &GerritOrgRepoConfigs{ 2394 { 2395 Org: "org-1", 2396 Repos: []string{"repo-1"}, 2397 }, 2398 { 2399 Org: "org-2", 2400 Repos: []string{"repo-2"}, 2401 }, 2402 }, 2403 want: map[string]map[string]*GerritQueryFilter{"org-1": {"repo-1": nil}, "org-2": {"repo-2": nil}}, 2404 }, 2405 { 2406 name: "org-union", 2407 in: &GerritOrgRepoConfigs{ 2408 { 2409 Org: "org-1", 2410 Repos: []string{"repo-1"}, 2411 }, 2412 { 2413 Org: "org-1", 2414 Repos: []string{"repo-2"}, 2415 }, 2416 }, 2417 want: map[string]map[string]*GerritQueryFilter{"org-1": {"repo-1": nil, "repo-2": nil}}, 2418 }, 2419 { 2420 name: "empty", 2421 in: &GerritOrgRepoConfigs{}, 2422 want: nil, 2423 }, 2424 } 2425 2426 for _, tc := range tests { 2427 t.Run(tc.name, func(t *testing.T) { 2428 got := tc.in.AllRepos() 2429 if diff := cmp.Diff(tc.want, got); diff != "" { 2430 t.Errorf("output mismatch. got(+), want(-):\n%s", diff) 2431 } 2432 }) 2433 } 2434 } 2435 2436 func TestGerritOptOutHelpRepos(t *testing.T) { 2437 tests := []struct { 2438 name string 2439 in *GerritOrgRepoConfigs 2440 want map[string]sets.Set[string] 2441 }{ 2442 { 2443 name: "multiple-org", 2444 in: &GerritOrgRepoConfigs{ 2445 { 2446 Org: "org-1", 2447 Repos: []string{"repo-1"}, 2448 OptOutHelp: true, 2449 }, 2450 { 2451 Org: "org-2", 2452 Repos: []string{"repo-2"}, 2453 OptOutHelp: true, 2454 }, 2455 }, 2456 want: map[string]sets.Set[string]{ 2457 "org-1": sets.New[string]("repo-1"), 2458 "org-2": sets.New[string]("repo-2"), 2459 }, 2460 }, 2461 { 2462 name: "org-union", 2463 in: &GerritOrgRepoConfigs{ 2464 { 2465 Org: "org-1", 2466 Repos: []string{"repo-1"}, 2467 OptOutHelp: true, 2468 }, 2469 { 2470 Org: "org-1", 2471 Repos: []string{"repo-2"}, 2472 OptOutHelp: true, 2473 }, 2474 }, 2475 want: map[string]sets.Set[string]{ 2476 "org-1": sets.New[string]("repo-1", "repo-2"), 2477 }, 2478 }, 2479 { 2480 name: "skip-non-optout", 2481 in: &GerritOrgRepoConfigs{ 2482 { 2483 Org: "org-1", 2484 Repos: []string{"repo-1"}, 2485 }, 2486 { 2487 Org: "org-1", 2488 Repos: []string{"repo-2"}, 2489 OptOutHelp: true, 2490 }, 2491 }, 2492 want: map[string]sets.Set[string]{ 2493 "org-1": sets.New[string]("repo-2"), 2494 }, 2495 }, 2496 { 2497 name: "empty", 2498 in: &GerritOrgRepoConfigs{}, 2499 want: nil, 2500 }, 2501 } 2502 2503 for _, tc := range tests { 2504 t.Run(tc.name, func(t *testing.T) { 2505 got := tc.in.OptOutHelpRepos() 2506 if diff := cmp.Diff(tc.want, got); diff != "" { 2507 t.Errorf("output mismatch. got(+), want(-):\n%s", diff) 2508 } 2509 }) 2510 } 2511 } 2512 2513 // integration test for fake config loading 2514 func TestValidConfigLoading(t *testing.T) { 2515 ptrOrBool := func(p *bool) string { 2516 if p != nil { 2517 return fmt.Sprintf("%t", *p) 2518 } 2519 2520 return "nil" 2521 } 2522 var testCases = []struct { 2523 name string 2524 prowConfig string 2525 versionFileContent string 2526 jobConfigs []string 2527 expectError bool 2528 expectPodNameSpace string 2529 expectEnv map[string][]v1.EnvVar 2530 verify func(*Config) error 2531 }{ 2532 { 2533 name: "one config", 2534 prowConfig: ``, 2535 }, 2536 { 2537 name: "reject invalid kubernetes periodic", 2538 prowConfig: ``, 2539 jobConfigs: []string{ 2540 ` 2541 periodics: 2542 - interval: 10m 2543 agent: kubernetes 2544 build_spec: 2545 name: foo`, 2546 }, 2547 expectError: true, 2548 }, 2549 { 2550 name: "one periodic", 2551 prowConfig: ``, 2552 jobConfigs: []string{ 2553 ` 2554 periodics: 2555 - interval: 10m 2556 agent: kubernetes 2557 name: foo 2558 spec: 2559 containers: 2560 - image: alpine`, 2561 }, 2562 }, 2563 { 2564 name: "one periodic no agent, should default", 2565 prowConfig: ``, 2566 jobConfigs: []string{ 2567 ` 2568 periodics: 2569 - interval: 10m 2570 name: foo 2571 spec: 2572 containers: 2573 - image: alpine`, 2574 }, 2575 }, 2576 { 2577 name: "two periodics", 2578 prowConfig: ``, 2579 jobConfigs: []string{ 2580 ` 2581 periodics: 2582 - interval: 10m 2583 agent: kubernetes 2584 name: foo 2585 spec: 2586 containers: 2587 - image: alpine`, 2588 ` 2589 periodics: 2590 - interval: 10m 2591 agent: kubernetes 2592 name: bar 2593 spec: 2594 containers: 2595 - image: alpine`, 2596 }, 2597 }, 2598 { 2599 name: "duplicated periodics", 2600 prowConfig: ``, 2601 jobConfigs: []string{ 2602 ` 2603 periodics: 2604 - interval: 10m 2605 agent: kubernetes 2606 name: foo 2607 spec: 2608 containers: 2609 - image: alpine`, 2610 ` 2611 periodics: 2612 - interval: 10m 2613 agent: kubernetes 2614 name: foo 2615 spec: 2616 containers: 2617 - image: alpine`, 2618 }, 2619 expectError: true, 2620 }, 2621 { 2622 name: "one presubmit no context should default", 2623 prowConfig: ``, 2624 jobConfigs: []string{ 2625 ` 2626 presubmits: 2627 foo/bar: 2628 - agent: kubernetes 2629 name: presubmit-bar 2630 spec: 2631 containers: 2632 - image: alpine`, 2633 }, 2634 }, 2635 { 2636 name: "one presubmit no agent should default", 2637 prowConfig: ``, 2638 jobConfigs: []string{ 2639 ` 2640 presubmits: 2641 foo/bar: 2642 - context: bar 2643 name: presubmit-bar 2644 spec: 2645 containers: 2646 - image: alpine`, 2647 }, 2648 }, 2649 { 2650 name: "one presubmit, ok", 2651 prowConfig: ``, 2652 jobConfigs: []string{ 2653 ` 2654 presubmits: 2655 foo/bar: 2656 - agent: kubernetes 2657 name: presubmit-bar 2658 context: bar 2659 spec: 2660 containers: 2661 - image: alpine`, 2662 }, 2663 }, 2664 { 2665 name: "two presubmits", 2666 prowConfig: ``, 2667 jobConfigs: []string{ 2668 ` 2669 presubmits: 2670 foo/bar: 2671 - agent: kubernetes 2672 name: presubmit-bar 2673 context: bar 2674 spec: 2675 containers: 2676 - image: alpine`, 2677 ` 2678 presubmits: 2679 foo/baz: 2680 - agent: kubernetes 2681 name: presubmit-baz 2682 context: baz 2683 spec: 2684 containers: 2685 - image: alpine`, 2686 }, 2687 }, 2688 { 2689 name: "dup presubmits, one file", 2690 prowConfig: ``, 2691 jobConfigs: []string{ 2692 ` 2693 presubmits: 2694 foo/bar: 2695 - agent: kubernetes 2696 name: presubmit-bar 2697 context: bar 2698 spec: 2699 containers: 2700 - image: alpine 2701 - agent: kubernetes 2702 name: presubmit-bar 2703 context: bar 2704 spec: 2705 containers: 2706 - image: alpine`, 2707 }, 2708 expectError: true, 2709 }, 2710 { 2711 name: "dup presubmits, two files", 2712 prowConfig: ``, 2713 jobConfigs: []string{ 2714 ` 2715 presubmits: 2716 foo/bar: 2717 - agent: kubernetes 2718 name: presubmit-bar 2719 context: bar 2720 spec: 2721 containers: 2722 - image: alpine`, 2723 ` 2724 presubmits: 2725 foo/bar: 2726 - agent: kubernetes 2727 context: bar 2728 name: presubmit-bar 2729 spec: 2730 containers: 2731 - image: alpine`, 2732 }, 2733 expectError: true, 2734 }, 2735 { 2736 name: "dup presubmits not the same branch, two files", 2737 prowConfig: ``, 2738 jobConfigs: []string{ 2739 ` 2740 presubmits: 2741 foo/bar: 2742 - agent: kubernetes 2743 name: presubmit-bar 2744 context: bar 2745 branches: 2746 - master 2747 spec: 2748 containers: 2749 - image: alpine`, 2750 ` 2751 presubmits: 2752 foo/bar: 2753 - agent: kubernetes 2754 context: bar 2755 branches: 2756 - other 2757 name: presubmit-bar 2758 spec: 2759 containers: 2760 - image: alpine`, 2761 }, 2762 expectError: false, 2763 }, 2764 { 2765 name: "dup presubmits main file", 2766 prowConfig: ` 2767 presubmits: 2768 foo/bar: 2769 - agent: kubernetes 2770 name: presubmit-bar 2771 context: bar 2772 spec: 2773 containers: 2774 - image: alpine 2775 - agent: kubernetes 2776 context: bar 2777 name: presubmit-bar 2778 spec: 2779 containers: 2780 - image: alpine`, 2781 expectError: true, 2782 }, 2783 { 2784 name: "dup presubmits main file not on the same branch", 2785 prowConfig: ` 2786 presubmits: 2787 foo/bar: 2788 - agent: kubernetes 2789 name: presubmit-bar 2790 context: bar 2791 branches: 2792 - other 2793 spec: 2794 containers: 2795 - image: alpine 2796 - agent: kubernetes 2797 context: bar 2798 branches: 2799 - master 2800 name: presubmit-bar 2801 spec: 2802 containers: 2803 - image: alpine`, 2804 expectError: false, 2805 }, 2806 { 2807 name: "one postsubmit, ok", 2808 prowConfig: ``, 2809 jobConfigs: []string{ 2810 ` 2811 postsubmits: 2812 foo/bar: 2813 - agent: kubernetes 2814 name: postsubmit-bar 2815 spec: 2816 containers: 2817 - image: alpine`, 2818 }, 2819 }, 2820 { 2821 name: "one postsubmit no agent, should default", 2822 prowConfig: ``, 2823 jobConfigs: []string{ 2824 ` 2825 postsubmits: 2826 foo/bar: 2827 - name: postsubmit-bar 2828 spec: 2829 containers: 2830 - image: alpine`, 2831 }, 2832 }, 2833 { 2834 name: "two postsubmits", 2835 prowConfig: ``, 2836 jobConfigs: []string{ 2837 ` 2838 postsubmits: 2839 foo/bar: 2840 - agent: kubernetes 2841 name: postsubmit-bar 2842 spec: 2843 containers: 2844 - image: alpine`, 2845 ` 2846 postsubmits: 2847 foo/baz: 2848 - agent: kubernetes 2849 name: postsubmit-baz 2850 spec: 2851 containers: 2852 - image: alpine`, 2853 }, 2854 }, 2855 { 2856 name: "dup postsubmits, one file", 2857 prowConfig: ``, 2858 jobConfigs: []string{ 2859 ` 2860 postsubmits: 2861 foo/bar: 2862 - agent: kubernetes 2863 name: postsubmit-bar 2864 spec: 2865 containers: 2866 - image: alpine 2867 - agent: kubernetes 2868 name: postsubmit-bar 2869 spec: 2870 containers: 2871 - image: alpine`, 2872 }, 2873 expectError: true, 2874 }, 2875 { 2876 name: "dup postsubmits, two files", 2877 prowConfig: ``, 2878 jobConfigs: []string{ 2879 ` 2880 postsubmits: 2881 foo/bar: 2882 - agent: kubernetes 2883 name: postsubmit-bar 2884 spec: 2885 containers: 2886 - image: alpine`, 2887 ` 2888 postsubmits: 2889 foo/bar: 2890 - agent: kubernetes 2891 name: postsubmit-bar 2892 spec: 2893 containers: 2894 - image: alpine`, 2895 }, 2896 expectError: true, 2897 }, 2898 { 2899 name: "test valid presets in main config", 2900 prowConfig: ` 2901 presets: 2902 - labels: 2903 preset-baz: "true" 2904 env: 2905 - name: baz 2906 value: fejtaverse`, 2907 jobConfigs: []string{ 2908 `periodics: 2909 - interval: 10m 2910 agent: kubernetes 2911 name: foo 2912 labels: 2913 preset-baz: "true" 2914 spec: 2915 containers: 2916 - image: alpine`, 2917 ` 2918 periodics: 2919 - interval: 10m 2920 agent: kubernetes 2921 name: bar 2922 labels: 2923 preset-baz: "true" 2924 spec: 2925 containers: 2926 - image: alpine`, 2927 }, 2928 expectEnv: map[string][]v1.EnvVar{ 2929 "foo": { 2930 { 2931 Name: "baz", 2932 Value: "fejtaverse", 2933 }, 2934 }, 2935 "bar": { 2936 { 2937 Name: "baz", 2938 Value: "fejtaverse", 2939 }, 2940 }, 2941 }, 2942 }, 2943 { 2944 name: "test valid presets in job configs", 2945 prowConfig: ``, 2946 jobConfigs: []string{ 2947 ` 2948 presets: 2949 - labels: 2950 preset-baz: "true" 2951 env: 2952 - name: baz 2953 value: fejtaverse 2954 periodics: 2955 - interval: 10m 2956 agent: kubernetes 2957 name: foo 2958 labels: 2959 preset-baz: "true" 2960 spec: 2961 containers: 2962 - image: alpine`, 2963 ` 2964 periodics: 2965 - interval: 10m 2966 agent: kubernetes 2967 name: bar 2968 labels: 2969 preset-baz: "true" 2970 spec: 2971 containers: 2972 - image: alpine`, 2973 }, 2974 expectEnv: map[string][]v1.EnvVar{ 2975 "foo": { 2976 { 2977 Name: "baz", 2978 Value: "fejtaverse", 2979 }, 2980 }, 2981 "bar": { 2982 { 2983 Name: "baz", 2984 Value: "fejtaverse", 2985 }, 2986 }, 2987 }, 2988 }, 2989 { 2990 name: "test valid presets in both main & job configs", 2991 prowConfig: ` 2992 presets: 2993 - labels: 2994 preset-baz: "true" 2995 env: 2996 - name: baz 2997 value: fejtaverse`, 2998 jobConfigs: []string{ 2999 ` 3000 presets: 3001 - labels: 3002 preset-k8s: "true" 3003 env: 3004 - name: k8s 3005 value: kubernetes 3006 periodics: 3007 - interval: 10m 3008 agent: kubernetes 3009 name: foo 3010 labels: 3011 preset-baz: "true" 3012 preset-k8s: "true" 3013 spec: 3014 containers: 3015 - image: alpine`, 3016 ` 3017 periodics: 3018 - interval: 10m 3019 agent: kubernetes 3020 name: bar 3021 labels: 3022 preset-baz: "true" 3023 spec: 3024 containers: 3025 - image: alpine`, 3026 }, 3027 expectEnv: map[string][]v1.EnvVar{ 3028 "foo": { 3029 { 3030 Name: "baz", 3031 Value: "fejtaverse", 3032 }, 3033 { 3034 Name: "k8s", 3035 Value: "kubernetes", 3036 }, 3037 }, 3038 "bar": { 3039 { 3040 Name: "baz", 3041 Value: "fejtaverse", 3042 }, 3043 }, 3044 }, 3045 }, 3046 { 3047 name: "decorated periodic missing `command`", 3048 prowConfig: ``, 3049 jobConfigs: []string{ 3050 ` 3051 periodics: 3052 - interval: 10m 3053 agent: kubernetes 3054 name: foo 3055 decorate: true 3056 spec: 3057 containers: 3058 - image: alpine`, 3059 }, 3060 expectError: true, 3061 }, 3062 { 3063 name: "all repos contains repos from tide, presubmits and postsubmits", 3064 prowConfig: ` 3065 tide: 3066 queries: 3067 - repos: 3068 - stranded/fish`, 3069 jobConfigs: []string{` 3070 presubmits: 3071 k/k: 3072 - name: my-job 3073 spec: 3074 containers: 3075 - name: lost-vessel 3076 image: vessel:latest 3077 command: ["ride"]`, 3078 ` 3079 postsubmits: 3080 k/test-infra: 3081 - name: my-job 3082 spec: 3083 containers: 3084 - name: lost-vessel 3085 image: vessel:latest 3086 command: ["ride"]`, 3087 }, 3088 verify: func(c *Config) error { 3089 if diff := c.AllRepos.Difference(sets.New[string]("k/k", "k/test-infra", "stranded/fish")); len(diff) != 0 { 3090 return fmt.Errorf("expected no diff, got %q", diff) 3091 } 3092 return nil 3093 }, 3094 }, 3095 { 3096 name: "no jobs doesn't make AllRepos a nilpointer", 3097 verify: func(c *Config) error { 3098 if c.AllRepos == nil { 3099 return errors.New("config.AllRepos is nil") 3100 } 3101 return nil 3102 }, 3103 }, 3104 { 3105 name: "ProwYAMLGetterWithDefaults gets set", 3106 verify: func(c *Config) error { 3107 if c.ProwYAMLGetterWithDefaults == nil { 3108 return errors.New("config.ProwYAMLGetterWithDefaults is nil") 3109 } 3110 return nil 3111 }, 3112 }, 3113 { 3114 name: "InRepoConfigAllowedClusters gets defaulted if unset", 3115 verify: func(c *Config) error { 3116 if len(c.InRepoConfig.AllowedClusters) != 1 || 3117 len(c.InRepoConfig.AllowedClusters["*"]) != 1 || 3118 c.InRepoConfig.AllowedClusters["*"][0] != kube.DefaultClusterAlias { 3119 return fmt.Errorf("expected c.InRepoConfig.AllowedClusters to contain exactly one global entry to allow the buildcluster, was %v", c.InRepoConfig.AllowedClusters) 3120 } 3121 return nil 3122 }, 3123 }, 3124 { 3125 name: "InRepoConfigAllowedClusters gets defaulted if no global setting", 3126 prowConfig: ` 3127 in_repo_config: 3128 allowed_clusters: 3129 foo/bar: ["my-cluster"] 3130 `, 3131 verify: func(c *Config) error { 3132 if len(c.InRepoConfig.AllowedClusters) != 2 || 3133 len(c.InRepoConfig.AllowedClusters["*"]) != 1 || 3134 c.InRepoConfig.AllowedClusters["*"][0] != kube.DefaultClusterAlias { 3135 return fmt.Errorf("expected c.InRepoConfig.AllowedClusters to contain exactly one global entry to allow the buildcluster, was %v", c.InRepoConfig.AllowedClusters) 3136 } 3137 return nil 3138 }, 3139 }, 3140 { 3141 name: "InRepoConfigAllowedClusters respects explicit empty default", 3142 prowConfig: ` 3143 in_repo_config: 3144 allowed_clusters: 3145 "*": [] 3146 `, 3147 verify: func(c *Config) error { 3148 if len(c.InRepoConfig.AllowedClusters) != 1 || 3149 len(c.InRepoConfig.AllowedClusters["*"]) != 0 { 3150 return fmt.Errorf("expected c.InRepoConfig.AllowedClusters to contain no global entry, was %v", c.InRepoConfig.AllowedClusters) 3151 } 3152 return nil 3153 }, 3154 }, 3155 { 3156 name: "InRepoConfigAllowedClusters doesn't get overwritten", 3157 prowConfig: ` 3158 in_repo_config: 3159 allowed_clusters: 3160 foo/bar: ["my-cluster"] 3161 `, 3162 verify: func(c *Config) error { 3163 if len(c.InRepoConfig.AllowedClusters) != 2 || 3164 len(c.InRepoConfig.AllowedClusters["foo/bar"]) != 1 || 3165 c.InRepoConfig.AllowedClusters["foo/bar"][0] != "my-cluster" { 3166 return fmt.Errorf("expected c.InRepoConfig.AllowedClusters to contain exactly one entry for foo/bar, was %v", c.InRepoConfig.AllowedClusters) 3167 } 3168 return nil 3169 }, 3170 }, 3171 { 3172 name: "PubSubSubscriptions set but not PubSubTriggers", 3173 prowConfig: ` 3174 pubsub_subscriptions: 3175 projA: 3176 - topicB 3177 - topicC 3178 `, 3179 verify: func(c *Config) error { 3180 if diff := cmp.Diff(c.PubSubTriggers, PubSubTriggers([]PubSubTrigger{ 3181 { 3182 Project: "projA", 3183 Topics: []string{"topicB", "topicC"}, 3184 AllowedClusters: []string{"*"}, 3185 MaxOutstandingMessages: 10, 3186 }, 3187 })); diff != "" { 3188 return fmt.Errorf("want(-), got(+): \n%s", diff) 3189 } 3190 return nil 3191 }, 3192 }, 3193 { 3194 name: "Version file sets the version", 3195 versionFileContent: "some-git-sha", 3196 verify: func(c *Config) error { 3197 if c.ConfigVersionSHA != "some-git-sha" { 3198 return fmt.Errorf("expected value of ConfigVersionSH field to be 'some-git-sha', was %q", c.ConfigVersionSHA) 3199 } 3200 return nil 3201 }, 3202 }, 3203 { 3204 name: "tide global target_url respected", 3205 prowConfig: ` 3206 tide: 3207 target_url: https://global.tide.com 3208 `, 3209 verify: func(c *Config) error { 3210 orgRepo := OrgRepo{Org: "org", Repo: "repo"} 3211 if got, expected := c.Tide.GetTargetURL(orgRepo), "https://global.tide.com"; got != expected { 3212 return fmt.Errorf("expected target URL for %q to be %q, but got %q", orgRepo.String(), expected, got) 3213 } 3214 return nil 3215 }, 3216 }, 3217 { 3218 name: "tide target_url and target_urls conflict", 3219 prowConfig: ` 3220 tide: 3221 target_url: https://global.tide.com 3222 target_urls: 3223 "org": https://org.tide.com 3224 `, 3225 expectError: true, 3226 }, 3227 { 3228 name: "tide specific target_urls respected", 3229 prowConfig: ` 3230 tide: 3231 target_urls: 3232 "*": https://star.tide.com 3233 "org": https://org.tide.com 3234 "org/repo": https://repo.tide.com 3235 `, 3236 verify: func(c *Config) error { 3237 orgRepo := OrgRepo{Org: "other-org", Repo: "other-repo"} 3238 if got, expected := c.Tide.GetTargetURL(orgRepo), "https://star.tide.com"; got != expected { 3239 return fmt.Errorf("expected target URL for %q to be %q, but got %q", orgRepo.String(), expected, got) 3240 } 3241 orgRepo = OrgRepo{Org: "org", Repo: "other-repo"} 3242 if got, expected := c.Tide.GetTargetURL(orgRepo), "https://org.tide.com"; got != expected { 3243 return fmt.Errorf("expected target URL for %q to be %q, but got %q", orgRepo.String(), expected, got) 3244 } 3245 orgRepo = OrgRepo{Org: "org", Repo: "repo"} 3246 if got, expected := c.Tide.GetTargetURL(orgRepo), "https://repo.tide.com"; got != expected { 3247 return fmt.Errorf("expected target URL for %q to be %q, but got %q", orgRepo.String(), expected, got) 3248 } 3249 return nil 3250 }, 3251 }, 3252 { 3253 name: "tide no target_url specified returns empty string", 3254 verify: func(c *Config) error { 3255 orgRepo := OrgRepo{Org: "org", Repo: "repo"} 3256 if got, expected := c.Tide.GetTargetURL(orgRepo), ""; got != expected { 3257 return fmt.Errorf("expected target URL for %q to be %q, but got %q", orgRepo.String(), expected, got) 3258 } 3259 return nil 3260 }, 3261 }, 3262 { 3263 name: "tide specific pr_status_base_urls respected", 3264 prowConfig: ` 3265 tide: 3266 pr_status_base_urls: 3267 "*": https://star.tide.com/pr 3268 "org": https://org.tide.com/pr 3269 "org/repo": https://repo.tide.com/pr 3270 `, 3271 verify: func(c *Config) error { 3272 orgRepo := OrgRepo{Org: "other-org", Repo: "other-repo"} 3273 if got, expected := c.Tide.GetPRStatusBaseURL(orgRepo), "https://star.tide.com/pr"; got != expected { 3274 return fmt.Errorf("expected PR Status Base URL for %q to be %q, but got %q", orgRepo.String(), expected, got) 3275 } 3276 orgRepo = OrgRepo{Org: "org", Repo: "other-repo"} 3277 if got, expected := c.Tide.GetPRStatusBaseURL(orgRepo), "https://org.tide.com/pr"; got != expected { 3278 return fmt.Errorf("expected PR Status Base URL for %q to be %q, but got %q", orgRepo.String(), expected, got) 3279 } 3280 orgRepo = OrgRepo{Org: "org", Repo: "repo"} 3281 if got, expected := c.Tide.GetPRStatusBaseURL(orgRepo), "https://repo.tide.com/pr"; got != expected { 3282 return fmt.Errorf("expected PR Status Base URL for %q to be %q, but got %q", orgRepo.String(), expected, got) 3283 } 3284 return nil 3285 }, 3286 }, 3287 { 3288 name: "postsubmit without explicit 'always_run' sets this field to nil by default", 3289 jobConfigs: []string{ 3290 ` 3291 postsubmits: 3292 k/test-infra: 3293 - name: my-job 3294 spec: 3295 containers: 3296 - name: lost-vessel 3297 image: vessel:latest 3298 command: ["ride"]`, 3299 }, 3300 verify: func(c *Config) error { 3301 jobs := c.PostsubmitsStatic["k/test-infra"] 3302 if jobs[0].AlwaysRun != nil { 3303 return fmt.Errorf("expected job to have 'always_run' set to nil, but got %q", ptrOrBool(jobs[0].AlwaysRun)) 3304 } 3305 return nil 3306 }, 3307 }, 3308 { 3309 name: "postsubmit with explicit 'always_run: true' sets this field to true", 3310 jobConfigs: []string{ 3311 ` 3312 postsubmits: 3313 k/test-infra: 3314 - name: my-job 3315 always_run: true 3316 spec: 3317 containers: 3318 - name: lost-vessel 3319 image: vessel:latest 3320 command: ["ride"]`, 3321 }, 3322 verify: func(c *Config) error { 3323 jobs := c.PostsubmitsStatic["k/test-infra"] 3324 if jobs[0].AlwaysRun == nil || *jobs[0].AlwaysRun == false { 3325 return fmt.Errorf("expected job to have 'always_run' set to true, but got %q", ptrOrBool(jobs[0].AlwaysRun)) 3326 } 3327 return nil 3328 }, 3329 }, 3330 { 3331 name: "postsubmit with explicit 'always_run: false' sets this field to false", 3332 jobConfigs: []string{ 3333 ` 3334 postsubmits: 3335 k/test-infra: 3336 - name: my-job 3337 always_run: false 3338 spec: 3339 containers: 3340 - name: lost-vessel 3341 image: vessel:latest 3342 command: ["ride"]`, 3343 }, 3344 verify: func(c *Config) error { 3345 jobs := c.PostsubmitsStatic["k/test-infra"] 3346 if jobs[0].AlwaysRun == nil || *jobs[0].AlwaysRun == true { 3347 return fmt.Errorf("expected job to have 'always_run' set to false, but got %q", ptrOrBool(jobs[0].AlwaysRun)) 3348 } 3349 return nil 3350 }, 3351 }, 3352 { 3353 name: "postsubmit with explicit 'always_run: true' and 'run_if_changed' set, err", 3354 jobConfigs: []string{ 3355 ` 3356 postsubmits: 3357 k/test-infra: 3358 - name: my-job 3359 always_run: true 3360 run_if_changed: "foo" 3361 spec: 3362 containers: 3363 - name: lost-vessel 3364 image: vessel:latest 3365 command: ["ride"]`, 3366 }, 3367 expectError: true, 3368 }, 3369 { 3370 name: "postsubmit with explicit 'always_run: true' and 'skip_if_only_changed' set, err", 3371 jobConfigs: []string{ 3372 ` 3373 postsubmits: 3374 k/test-infra: 3375 - name: my-job 3376 always_run: true 3377 skip_if_only_changed: "foo" 3378 spec: 3379 containers: 3380 - name: lost-vessel 3381 image: vessel:latest 3382 command: ["ride"]`, 3383 }, 3384 expectError: true, 3385 }, 3386 { 3387 name: "postsubmit with explicit 'always_run: false' and 'run_if_changed' set, OK", 3388 jobConfigs: []string{ 3389 ` 3390 postsubmits: 3391 k/test-infra: 3392 - name: my-job 3393 always_run: false 3394 run_if_changed: "foo" 3395 spec: 3396 containers: 3397 - name: lost-vessel 3398 image: vessel:latest 3399 command: ["ride"]`, 3400 }, 3401 }, 3402 { 3403 name: "postsubmit with explicit 'always_run: false' and 'skip_if_only_changed' set, OK", 3404 jobConfigs: []string{ 3405 ` 3406 postsubmits: 3407 k/test-infra: 3408 - name: my-job 3409 always_run: false 3410 skip_if_only_changed: "foo" 3411 spec: 3412 containers: 3413 - name: lost-vessel 3414 image: vessel:latest 3415 command: ["ride"]`, 3416 }, 3417 }, 3418 { 3419 name: "postsubmit with 'run_if_changed' set, then 'always_run' is 'nil'", 3420 jobConfigs: []string{ 3421 ` 3422 postsubmits: 3423 k/test-infra: 3424 - name: my-job 3425 run_if_changed: "foo" 3426 spec: 3427 containers: 3428 - name: lost-vessel 3429 image: vessel:latest 3430 command: ["ride"]`, 3431 }, 3432 verify: func(c *Config) error { 3433 jobs := c.PostsubmitsStatic["k/test-infra"] 3434 if jobs[0].AlwaysRun != nil { 3435 return fmt.Errorf("expected job to have 'always_run' set to nil, but got %q", ptrOrBool(jobs[0].AlwaysRun)) 3436 } 3437 return nil 3438 }, 3439 }, 3440 { 3441 name: "postsubmit with 'skip_if_only_changed' set, then 'always_run' is 'nil'", 3442 jobConfigs: []string{ 3443 ` 3444 postsubmits: 3445 k/test-infra: 3446 - name: my-job 3447 skip_if_only_changed: "foo" 3448 spec: 3449 containers: 3450 - name: lost-vessel 3451 image: vessel:latest 3452 command: ["ride"]`, 3453 }, 3454 verify: func(c *Config) error { 3455 jobs := c.PostsubmitsStatic["k/test-infra"] 3456 if jobs[0].AlwaysRun != nil { 3457 return fmt.Errorf("expected job to have 'always_run' set to nil, but got %q", ptrOrBool(jobs[0].AlwaysRun)) 3458 } 3459 return nil 3460 }, 3461 }, 3462 } 3463 3464 for _, tc := range testCases { 3465 t.Run(tc.name, func(t *testing.T) { 3466 3467 // save the config 3468 prowConfigDir := t.TempDir() 3469 3470 prowConfig := filepath.Join(prowConfigDir, "config.yaml") 3471 if err := os.WriteFile(prowConfig, []byte(tc.prowConfig), 0666); err != nil { 3472 t.Fatalf("fail to write prow config: %v", err) 3473 } 3474 3475 if tc.versionFileContent != "" { 3476 versionFile := filepath.Join(prowConfigDir, "VERSION") 3477 if err := os.WriteFile(versionFile, []byte(tc.versionFileContent), 0600); err != nil { 3478 t.Fatalf("failed to write prow version file: %v", err) 3479 } 3480 } 3481 3482 jobConfig := "" 3483 if len(tc.jobConfigs) > 0 { 3484 jobConfigDir := t.TempDir() 3485 3486 // cover both job config as a file & a dir 3487 if len(tc.jobConfigs) == 1 { 3488 // a single file 3489 jobConfig = filepath.Join(jobConfigDir, "config.yaml") 3490 if err := os.WriteFile(jobConfig, []byte(tc.jobConfigs[0]), 0666); err != nil { 3491 t.Fatalf("fail to write job config: %v", err) 3492 } 3493 } else { 3494 // a dir 3495 jobConfig = jobConfigDir 3496 for idx, config := range tc.jobConfigs { 3497 subConfig := filepath.Join(jobConfigDir, fmt.Sprintf("config_%d.yaml", idx)) 3498 if err := os.WriteFile(subConfig, []byte(config), 0666); err != nil { 3499 t.Fatalf("fail to write job config: %v", err) 3500 } 3501 } 3502 } 3503 } 3504 3505 cfg, err := Load(prowConfig, jobConfig, nil, "") 3506 if tc.expectError && err == nil { 3507 t.Errorf("tc %s: Expect error, but got nil", tc.name) 3508 } else if !tc.expectError && err != nil { 3509 t.Errorf("tc %s: Expect no error, but got error %v", tc.name, err) 3510 } 3511 3512 if err == nil { 3513 if tc.expectPodNameSpace == "" { 3514 tc.expectPodNameSpace = "default" 3515 } 3516 3517 if cfg.PodNamespace != tc.expectPodNameSpace { 3518 t.Errorf("tc %s: Expect PodNamespace %s, but got %v", tc.name, tc.expectPodNameSpace, cfg.PodNamespace) 3519 } 3520 3521 if len(tc.expectEnv) > 0 { 3522 for _, j := range cfg.AllStaticPresubmits(nil) { 3523 if envs, ok := tc.expectEnv[j.Name]; ok { 3524 if !reflect.DeepEqual(envs, j.Spec.Containers[0].Env) { 3525 t.Errorf("tc %s: expect env %v for job %s, got %+v", tc.name, envs, j.Name, j.Spec.Containers[0].Env) 3526 } 3527 } 3528 } 3529 3530 for _, j := range cfg.AllStaticPostsubmits(nil) { 3531 if envs, ok := tc.expectEnv[j.Name]; ok { 3532 if !reflect.DeepEqual(envs, j.Spec.Containers[0].Env) { 3533 t.Errorf("tc %s: expect env %v for job %s, got %+v", tc.name, envs, j.Name, j.Spec.Containers[0].Env) 3534 } 3535 } 3536 } 3537 3538 for _, j := range cfg.AllPeriodics() { 3539 if envs, ok := tc.expectEnv[j.Name]; ok { 3540 if !reflect.DeepEqual(envs, j.Spec.Containers[0].Env) { 3541 t.Errorf("tc %s: expect env %v for job %s, got %+v", tc.name, envs, j.Name, j.Spec.Containers[0].Env) 3542 } 3543 } 3544 } 3545 } 3546 } 3547 3548 if tc.verify != nil { 3549 if err := tc.verify(cfg); err != nil { 3550 t.Fatalf("verify failed: %v", err) 3551 } 3552 } 3553 }) 3554 } 3555 } 3556 3557 func TestReadJobConfigProwIgnore(t *testing.T) { 3558 expectExactly := func(expected ...string) func(c *JobConfig) error { 3559 return func(c *JobConfig) error { 3560 expected := sets.New[string](expected...) 3561 actual := sets.New[string]() 3562 for _, pres := range c.PresubmitsStatic { 3563 for _, pre := range pres { 3564 actual.Insert(pre.Name) 3565 } 3566 } 3567 if diff := expected.Difference(actual); diff.Len() > 0 { 3568 return fmt.Errorf("missing expected job(s): %q", sets.List(diff)) 3569 } 3570 if diff := actual.Difference(expected); diff.Len() > 0 { 3571 return fmt.Errorf("found unexpected job(s): %q", sets.List(diff)) 3572 } 3573 return nil 3574 } 3575 } 3576 commonFiles := map[string]string{ 3577 "foo_jobs.yaml": `presubmits: 3578 org/foo: 3579 - name: foo_1 3580 spec: 3581 containers: 3582 - image: my-image:latest 3583 command: ["do-the-thing"]`, 3584 "bar_jobs.yaml": `presubmits: 3585 org/bar: 3586 - name: bar_1 3587 spec: 3588 containers: 3589 - image: my-image:latest 3590 command: ["do-the-thing"]`, 3591 "subdir/baz_jobs.yaml": `presubmits: 3592 org/baz: 3593 - name: baz_1 3594 spec: 3595 containers: 3596 - image: my-image:latest 3597 command: ["do-the-thing"]`, 3598 "extraneous.md": `I am unrelated.`, 3599 } 3600 3601 var testCases = []struct { 3602 name string 3603 files map[string]string 3604 verify func(*JobConfig) error 3605 }{ 3606 { 3607 name: "no ignore files", 3608 verify: expectExactly("foo_1", "bar_1", "baz_1"), 3609 }, 3610 { 3611 name: "ignore file present, all ignored", 3612 files: map[string]string{ 3613 ProwIgnoreFileName: `*.yaml`, 3614 }, 3615 verify: expectExactly(), 3616 }, 3617 { 3618 name: "ignore file present, no match", 3619 files: map[string]string{ 3620 ProwIgnoreFileName: `*_ignored.yaml`, 3621 }, 3622 verify: expectExactly("foo_1", "bar_1", "baz_1"), 3623 }, 3624 { 3625 name: "ignore file present, matches bar file", 3626 files: map[string]string{ 3627 ProwIgnoreFileName: `bar_*.yaml`, 3628 }, 3629 verify: expectExactly("foo_1", "baz_1"), 3630 }, 3631 { 3632 name: "ignore file present, matches subdir", 3633 files: map[string]string{ 3634 ProwIgnoreFileName: `subdir/`, 3635 }, 3636 verify: expectExactly("foo_1", "bar_1"), 3637 }, 3638 { 3639 name: "ignore file present, matches bar and subdir", 3640 files: map[string]string{ 3641 ProwIgnoreFileName: `subdir/ 3642 bar_jobs.yaml`, 3643 }, 3644 verify: expectExactly("foo_1"), 3645 }, 3646 { 3647 name: "ignore file in subdir, matches only subdir files", 3648 files: map[string]string{ 3649 "subdir/" + ProwIgnoreFileName: `*.yaml`, 3650 }, 3651 verify: expectExactly("foo_1", "bar_1"), 3652 }, 3653 { 3654 name: "ignore file in root and subdir, matches bar and subdir", 3655 files: map[string]string{ 3656 "subdir/" + ProwIgnoreFileName: `*.yaml`, 3657 ProwIgnoreFileName: `bar_jobs.yaml`, 3658 }, 3659 verify: expectExactly("foo_1"), 3660 }, 3661 } 3662 3663 for _, tc := range testCases { 3664 tc := tc 3665 t.Run(tc.name, func(t *testing.T) { 3666 jobConfigDir := t.TempDir() 3667 err := os.Mkdir(filepath.Join(jobConfigDir, "subdir"), 0777) 3668 if err != nil { 3669 t.Fatalf("fail to make subdir: %v", err) 3670 } 3671 3672 for _, fileMap := range []map[string]string{commonFiles, tc.files} { 3673 for name, content := range fileMap { 3674 fullName := filepath.Join(jobConfigDir, name) 3675 if err := os.WriteFile(fullName, []byte(content), 0666); err != nil { 3676 t.Fatalf("fail to write file %s: %v", fullName, err) 3677 } 3678 } 3679 } 3680 3681 cfg, err := ReadJobConfig(jobConfigDir) 3682 if err != nil { 3683 t.Fatalf("Unexpected error reading job config: %v.", err) 3684 } 3685 3686 if tc.verify != nil { 3687 if err := tc.verify(&cfg); err != nil { 3688 t.Errorf("Verify failed: %v", err) 3689 } 3690 } 3691 }) 3692 } 3693 } 3694 3695 func TestBrancher_Intersects(t *testing.T) { 3696 testCases := []struct { 3697 name string 3698 a, b Brancher 3699 result bool 3700 }{ 3701 { 3702 name: "TwodifferentBranches", 3703 a: Brancher{ 3704 Branches: []string{"a"}, 3705 }, 3706 b: Brancher{ 3707 Branches: []string{"b"}, 3708 }, 3709 }, 3710 { 3711 name: "Opposite", 3712 a: Brancher{ 3713 SkipBranches: []string{"b"}, 3714 }, 3715 b: Brancher{ 3716 Branches: []string{"b"}, 3717 }, 3718 }, 3719 { 3720 name: "BothRunOnAllBranches", 3721 a: Brancher{}, 3722 b: Brancher{}, 3723 result: true, 3724 }, 3725 { 3726 name: "RunsOnAllBranchesAndSpecified", 3727 a: Brancher{}, 3728 b: Brancher{ 3729 Branches: []string{"b"}, 3730 }, 3731 result: true, 3732 }, 3733 { 3734 name: "SkipBranchesAndSet", 3735 a: Brancher{ 3736 SkipBranches: []string{"a", "b", "c"}, 3737 }, 3738 b: Brancher{ 3739 Branches: []string{"a"}, 3740 }, 3741 }, 3742 { 3743 name: "SkipBranchesAndSet", 3744 a: Brancher{ 3745 Branches: []string{"c"}, 3746 }, 3747 b: Brancher{ 3748 Branches: []string{"a"}, 3749 }, 3750 }, 3751 { 3752 name: "BothSkipBranches", 3753 a: Brancher{ 3754 SkipBranches: []string{"a", "b", "c"}, 3755 }, 3756 b: Brancher{ 3757 SkipBranches: []string{"d", "e", "f"}, 3758 }, 3759 result: true, 3760 }, 3761 { 3762 name: "BothSkipCommonBranches", 3763 a: Brancher{ 3764 SkipBranches: []string{"a", "b", "c"}, 3765 }, 3766 b: Brancher{ 3767 SkipBranches: []string{"b", "e", "f"}, 3768 }, 3769 result: true, 3770 }, 3771 { 3772 name: "NoIntersectionBecauseRegexSkip", 3773 a: Brancher{ 3774 SkipBranches: []string{`release-\d+\.\d+`}, 3775 }, 3776 b: Brancher{ 3777 Branches: []string{`release-1.14`, `release-1.13`}, 3778 }, 3779 result: false, 3780 }, 3781 { 3782 name: "IntersectionDespiteRegexSkip", 3783 a: Brancher{ 3784 SkipBranches: []string{`release-\d+\.\d+`}, 3785 }, 3786 b: Brancher{ 3787 Branches: []string{`release-1.14`, `master`}, 3788 }, 3789 result: true, 3790 }, 3791 } 3792 3793 for _, tc := range testCases { 3794 t.Run(tc.name, func(st *testing.T) { 3795 a, err := setBrancherRegexes(tc.a) 3796 if err != nil { 3797 st.Fatalf("Failed to set brancher A regexes: %v", err) 3798 } 3799 b, err := setBrancherRegexes(tc.b) 3800 if err != nil { 3801 st.Fatalf("Failed to set brancher B regexes: %v", err) 3802 } 3803 r1 := a.Intersects(b) 3804 r2 := b.Intersects(a) 3805 for _, result := range []bool{r1, r2} { 3806 if result != tc.result { 3807 st.Errorf("Expected %v got %v", tc.result, result) 3808 } 3809 } 3810 }) 3811 } 3812 } 3813 3814 // Integration test for fake secrets loading in a secret agent. 3815 // Checking also if the agent changes the secret's values as expected. 3816 func TestSecretAgentLoading(t *testing.T) { 3817 tempTokenValue := "121f3cb3e7f70feeb35f9204f5a988d7292c7ba1" 3818 changedTokenValue := "121f3cb3e7f70feeb35f9204f5a988d7292c7ba0" 3819 3820 // Creating a temporary directory. 3821 secretDir := t.TempDir() 3822 3823 // Create the first temporary secret. 3824 firstTempSecret := filepath.Join(secretDir, "firstTempSecret") 3825 if err := os.WriteFile(firstTempSecret, []byte(tempTokenValue), 0666); err != nil { 3826 t.Fatalf("fail to write secret: %v", err) 3827 } 3828 3829 // Create the second temporary secret. 3830 secondTempSecret := filepath.Join(secretDir, "secondTempSecret") 3831 if err := os.WriteFile(secondTempSecret, []byte(tempTokenValue), 0666); err != nil { 3832 t.Fatalf("fail to write secret: %v", err) 3833 } 3834 3835 tempSecrets := []string{firstTempSecret, secondTempSecret} 3836 // Starting the agent and add the two temporary secrets. 3837 if err := secret.Add(tempSecrets...); err != nil { 3838 t.Fatalf("Error starting secrets agent. %v", err) 3839 } 3840 3841 // Check if the values are as expected. 3842 for _, tempSecret := range tempSecrets { 3843 tempSecretValue := secret.GetSecret(tempSecret) 3844 if string(tempSecretValue) != tempTokenValue { 3845 t.Fatalf("In secret %s it was expected %s but found %s", 3846 tempSecret, tempTokenValue, tempSecretValue) 3847 } 3848 } 3849 3850 // Change the values of the files. 3851 if err := os.WriteFile(firstTempSecret, []byte(changedTokenValue), 0666); err != nil { 3852 t.Fatalf("fail to write secret: %v", err) 3853 } 3854 if err := os.WriteFile(secondTempSecret, []byte(changedTokenValue), 0666); err != nil { 3855 t.Fatalf("fail to write secret: %v", err) 3856 } 3857 3858 retries := 10 3859 var errors []string 3860 3861 // Check if the values changed as expected. 3862 for _, tempSecret := range tempSecrets { 3863 // Reset counter 3864 counter := 0 3865 for counter <= retries { 3866 tempSecretValue := secret.GetSecret(tempSecret) 3867 if string(tempSecretValue) != changedTokenValue { 3868 if counter == retries { 3869 errors = append(errors, fmt.Sprintf("In secret %s it was expected %s but found %s\n", 3870 tempSecret, changedTokenValue, tempSecretValue)) 3871 } else { 3872 // Secret agent needs some time to update the values. So wait and retry. 3873 time.Sleep(400 * time.Millisecond) 3874 } 3875 } else { 3876 break 3877 } 3878 counter++ 3879 } 3880 } 3881 3882 if len(errors) > 0 { 3883 t.Fatal(errors) 3884 } 3885 3886 } 3887 3888 func TestValidGitHubReportType(t *testing.T) { 3889 var testCases = []struct { 3890 name string 3891 prowConfig string 3892 expectError bool 3893 expectTypes []prowapi.ProwJobType 3894 }{ 3895 { 3896 name: "empty config should default to report for both presubmit and postsubmit", 3897 prowConfig: ``, 3898 expectTypes: []prowapi.ProwJobType{prowapi.PresubmitJob, prowapi.PostsubmitJob}, 3899 }, 3900 { 3901 name: "reject unsupported job types", 3902 prowConfig: ` 3903 github_reporter: 3904 job_types_to_report: 3905 - presubmit 3906 - batch 3907 `, 3908 expectError: true, 3909 }, 3910 { 3911 name: "accept valid job types", 3912 prowConfig: ` 3913 github_reporter: 3914 job_types_to_report: 3915 - presubmit 3916 - postsubmit 3917 `, 3918 expectTypes: []prowapi.ProwJobType{prowapi.PresubmitJob, prowapi.PostsubmitJob}, 3919 }, 3920 } 3921 3922 for _, tc := range testCases { 3923 // save the config 3924 prowConfigDir := t.TempDir() 3925 3926 prowConfig := filepath.Join(prowConfigDir, "config.yaml") 3927 if err := os.WriteFile(prowConfig, []byte(tc.prowConfig), 0666); err != nil { 3928 t.Fatalf("fail to write prow config: %v", err) 3929 } 3930 3931 cfg, err := Load(prowConfig, "", nil, "") 3932 if tc.expectError && err == nil { 3933 t.Errorf("tc %s: Expect error, but got nil", tc.name) 3934 } else if !tc.expectError && err != nil { 3935 t.Errorf("tc %s: Expect no error, but got error %v", tc.name, err) 3936 } 3937 3938 if err == nil { 3939 if !reflect.DeepEqual(cfg.GitHubReporter.JobTypesToReport, tc.expectTypes) { 3940 t.Errorf("tc %s: expected %#v\n!=\nactual %#v", tc.name, tc.expectTypes, cfg.GitHubReporter.JobTypesToReport) 3941 } 3942 } 3943 } 3944 } 3945 3946 func TestRerunAuthConfigsGetRerunAuthConfig(t *testing.T) { 3947 var testCases = []struct { 3948 name string 3949 configs RerunAuthConfigs 3950 jobSpec *prowapi.ProwJobSpec 3951 expected *prowapi.RerunAuthConfig 3952 }{ 3953 { 3954 name: "default to an empty config", 3955 configs: RerunAuthConfigs{}, 3956 jobSpec: &prowapi.ProwJobSpec{ 3957 Refs: &prowapi.Refs{Org: "my-default-org", Repo: "my-default-repo"}, 3958 }, 3959 expected: nil, 3960 }, 3961 { 3962 name: "unknown org or org/repo return wildcard", 3963 configs: RerunAuthConfigs{"*": prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}}, 3964 jobSpec: &prowapi.ProwJobSpec{ 3965 Refs: &prowapi.Refs{Org: "my-default-org", Repo: "my-default-repo"}, 3966 }, 3967 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 3968 }, 3969 { 3970 name: "no refs return wildcard empty string match", 3971 configs: RerunAuthConfigs{"": prowapi.RerunAuthConfig{GitHubUsers: []string{"leonardo"}}}, 3972 jobSpec: &prowapi.ProwJobSpec{}, 3973 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"leonardo"}}, 3974 }, 3975 { 3976 name: "no refs return wildcard override to star match", 3977 configs: RerunAuthConfigs{ 3978 "": prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 3979 "*": prowapi.RerunAuthConfig{GitHubUsers: []string{"scoobydoo"}}, 3980 "istio": prowapi.RerunAuthConfig{GitHubUsers: []string{"billybob"}}, 3981 }, 3982 jobSpec: &prowapi.ProwJobSpec{}, 3983 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scoobydoo"}}, 3984 }, 3985 { 3986 name: "no refs return wildcard but there is no match", 3987 configs: RerunAuthConfigs{ 3988 "istio": prowapi.RerunAuthConfig{GitHubUsers: []string{"billybob"}}, 3989 }, 3990 jobSpec: &prowapi.ProwJobSpec{}, 3991 expected: nil, 3992 }, 3993 { 3994 name: "use org if defined", 3995 configs: RerunAuthConfigs{ 3996 "*": prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 3997 "istio": prowapi.RerunAuthConfig{GitHubUsers: []string{"scoobydoo"}}, 3998 "istio/test-infra": prowapi.RerunAuthConfig{GitHubUsers: []string{"billybob"}}, 3999 }, 4000 jobSpec: &prowapi.ProwJobSpec{ 4001 Refs: &prowapi.Refs{Org: "istio", Repo: "istio"}, 4002 }, 4003 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scoobydoo"}}, 4004 }, 4005 { 4006 name: "use org/repo if defined", 4007 configs: RerunAuthConfigs{ 4008 "*": prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4009 "istio/istio": prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4010 }, 4011 jobSpec: &prowapi.ProwJobSpec{ 4012 Refs: &prowapi.Refs{Org: "istio", Repo: "istio"}, 4013 }, 4014 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4015 }, 4016 { 4017 name: "org/repo takes precedence over org", 4018 configs: RerunAuthConfigs{ 4019 "*": prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4020 "istio": prowapi.RerunAuthConfig{GitHubUsers: []string{"scrappydoo"}}, 4021 "istio/istio": prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4022 }, 4023 jobSpec: &prowapi.ProwJobSpec{ 4024 Refs: &prowapi.Refs{Org: "istio", Repo: "istio"}, 4025 }, 4026 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4027 }, 4028 { 4029 name: "use org/repo from extra refs", 4030 configs: RerunAuthConfigs{ 4031 "*": prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4032 "istio/istio": prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4033 }, 4034 jobSpec: &prowapi.ProwJobSpec{ 4035 ExtraRefs: []prowapi.Refs{{Org: "istio", Repo: "istio"}}, 4036 }, 4037 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4038 }, 4039 { 4040 name: "use only org/repo from first extra refs entry", 4041 configs: RerunAuthConfigs{ 4042 "*": prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4043 "istio/istio": prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4044 "other-org/other-repo": prowapi.RerunAuthConfig{GitHubUsers: []string{"anakin"}}, 4045 }, 4046 jobSpec: &prowapi.ProwJobSpec{ 4047 ExtraRefs: []prowapi.Refs{{Org: "istio", Repo: "istio"}, {Org: "other-org", Repo: "other-repo"}}, 4048 }, 4049 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4050 }, 4051 } 4052 4053 for _, tc := range testCases { 4054 t.Run(tc.name, func(t *testing.T) { 4055 d := Deck{} 4056 d.RerunAuthConfigs = tc.configs 4057 err := d.FinalizeDefaultRerunAuthConfigs() 4058 if err != nil { 4059 t.Fatal("Failed to finalize default rerun auth config.") 4060 } 4061 4062 if diff := cmp.Diff(tc.expected, d.GetRerunAuthConfig(tc.jobSpec)); diff != "" { 4063 t.Errorf("GetRerunAuthConfig returned unexpected value (-want +got):\n%s", diff) 4064 } 4065 }) 4066 } 4067 } 4068 4069 func TestDefaultRerunAuthConfigsGetRerunAuthConfig(t *testing.T) { 4070 var testCases = []struct { 4071 name string 4072 configs []*DefaultRerunAuthConfigEntry 4073 jobSpec *prowapi.ProwJobSpec 4074 expected *prowapi.RerunAuthConfig 4075 }{ 4076 { 4077 name: "default to an empty config", 4078 configs: []*DefaultRerunAuthConfigEntry{}, 4079 jobSpec: &prowapi.ProwJobSpec{ 4080 Refs: &prowapi.Refs{Org: "my-default-org", Repo: "my-default-repo"}, 4081 }, 4082 expected: nil, 4083 }, 4084 { 4085 name: "unknown org or org/repo return wildcard", 4086 configs: []*DefaultRerunAuthConfigEntry{ 4087 { 4088 OrgRepo: "*", 4089 Cluster: "", 4090 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4091 }, 4092 }, 4093 jobSpec: &prowapi.ProwJobSpec{ 4094 Refs: &prowapi.Refs{Org: "my-default-org", Repo: "my-default-repo"}, 4095 }, 4096 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4097 }, 4098 { 4099 name: "no refs return wildcard", 4100 configs: []*DefaultRerunAuthConfigEntry{ 4101 { 4102 OrgRepo: "*", 4103 Cluster: "", 4104 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"leonardo"}}, 4105 }, 4106 }, 4107 jobSpec: &prowapi.ProwJobSpec{}, 4108 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"leonardo"}}, 4109 }, 4110 { 4111 name: "no refs return wildcard empty string match", 4112 configs: []*DefaultRerunAuthConfigEntry{ 4113 { 4114 OrgRepo: "", 4115 Cluster: "", 4116 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"leonardo"}}, 4117 }, 4118 }, 4119 jobSpec: &prowapi.ProwJobSpec{}, 4120 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"leonardo"}}, 4121 }, 4122 { 4123 name: "no refs return wildcard override to star match", 4124 configs: []*DefaultRerunAuthConfigEntry{ 4125 { 4126 OrgRepo: "", 4127 Cluster: "", 4128 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4129 }, 4130 { 4131 OrgRepo: "*", 4132 Cluster: "", 4133 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scoobydoo"}}, 4134 }, 4135 { 4136 OrgRepo: "istio", 4137 Cluster: "", 4138 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4139 }, 4140 }, 4141 jobSpec: &prowapi.ProwJobSpec{}, 4142 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scoobydoo"}}, 4143 }, 4144 { 4145 name: "no refs return wildcard but there is no match", 4146 configs: []*DefaultRerunAuthConfigEntry{ 4147 { 4148 OrgRepo: "istio", 4149 Cluster: "", 4150 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"billybob"}}, 4151 }, 4152 }, 4153 jobSpec: &prowapi.ProwJobSpec{}, 4154 expected: nil, 4155 }, 4156 { 4157 name: "use org if defined", 4158 configs: []*DefaultRerunAuthConfigEntry{ 4159 { 4160 OrgRepo: "*", 4161 Cluster: "", 4162 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4163 }, 4164 { 4165 OrgRepo: "istio", 4166 Cluster: "", 4167 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scoobydoo"}}, 4168 }, 4169 { 4170 OrgRepo: "istio/test-infra", 4171 Cluster: "", 4172 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"billybob"}}, 4173 }, 4174 }, 4175 jobSpec: &prowapi.ProwJobSpec{ 4176 Refs: &prowapi.Refs{Org: "istio", Repo: "istio"}, 4177 }, 4178 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scoobydoo"}}, 4179 }, 4180 { 4181 name: "use org/repo if defined", 4182 configs: []*DefaultRerunAuthConfigEntry{ 4183 { 4184 OrgRepo: "*", 4185 Cluster: "", 4186 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4187 }, 4188 { 4189 OrgRepo: "istio/istio", 4190 Cluster: "", 4191 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4192 }, 4193 }, 4194 jobSpec: &prowapi.ProwJobSpec{ 4195 Refs: &prowapi.Refs{Org: "istio", Repo: "istio"}, 4196 }, 4197 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4198 }, 4199 { 4200 name: "org/repo takes precedence over org", 4201 configs: []*DefaultRerunAuthConfigEntry{ 4202 { 4203 OrgRepo: "*", 4204 Cluster: "", 4205 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4206 }, 4207 { 4208 OrgRepo: "istio", 4209 Cluster: "", 4210 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scrappydoo"}}, 4211 }, 4212 { 4213 OrgRepo: "istio/istio", 4214 Cluster: "", 4215 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4216 }, 4217 }, 4218 jobSpec: &prowapi.ProwJobSpec{ 4219 Refs: &prowapi.Refs{Org: "istio", Repo: "istio"}, 4220 }, 4221 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4222 }, 4223 { 4224 name: "cluster returns matching cluster", 4225 configs: []*DefaultRerunAuthConfigEntry{ 4226 { 4227 OrgRepo: "istio", 4228 Cluster: "", 4229 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scrappydoo"}}, 4230 }, 4231 { 4232 OrgRepo: "istio", 4233 Cluster: "trusted", 4234 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4235 }, 4236 }, 4237 jobSpec: &prowapi.ProwJobSpec{ 4238 Refs: &prowapi.Refs{Org: "istio", Repo: "istio"}, 4239 Cluster: "trusted", 4240 }, 4241 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4242 }, 4243 { 4244 name: "cluster returns wild card", 4245 configs: []*DefaultRerunAuthConfigEntry{ 4246 { 4247 OrgRepo: "istio", 4248 Cluster: "*", 4249 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scrappydoo"}}, 4250 }, 4251 { 4252 OrgRepo: "istio", 4253 Cluster: "cluster", 4254 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4255 }, 4256 }, 4257 jobSpec: &prowapi.ProwJobSpec{ 4258 Refs: &prowapi.Refs{Org: "istio", Repo: "istio"}, 4259 Cluster: "trusted", 4260 }, 4261 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"scrappydoo"}}, 4262 }, 4263 { 4264 name: "no refs with cluster returns overriding matching cluster", 4265 configs: []*DefaultRerunAuthConfigEntry{ 4266 { 4267 OrgRepo: "", 4268 Cluster: "*", 4269 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4270 }, 4271 { 4272 OrgRepo: "", 4273 Cluster: "trusted", 4274 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4275 }, 4276 }, 4277 jobSpec: &prowapi.ProwJobSpec{ 4278 Cluster: "trusted", 4279 }, 4280 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"airbender"}}, 4281 }, 4282 { 4283 name: "no matching orgrepo or cluster", 4284 configs: []*DefaultRerunAuthConfigEntry{ 4285 { 4286 OrgRepo: "notIstio", 4287 Cluster: "notTrusted", 4288 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4289 }, 4290 }, 4291 jobSpec: &prowapi.ProwJobSpec{ 4292 Refs: &prowapi.Refs{Org: "istio", Repo: "istio"}, 4293 Cluster: "trusted", 4294 }, 4295 expected: nil, 4296 }, 4297 { 4298 name: "use org/repo from extra refs", 4299 configs: []*DefaultRerunAuthConfigEntry{ 4300 { 4301 OrgRepo: "*", 4302 Cluster: "", 4303 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4304 }, 4305 { 4306 OrgRepo: "istio/istio", 4307 Cluster: "", 4308 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4309 }, 4310 }, 4311 jobSpec: &prowapi.ProwJobSpec{ 4312 ExtraRefs: []prowapi.Refs{{Org: "istio", Repo: "istio"}}, 4313 }, 4314 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4315 }, 4316 { 4317 name: "use only org/repo from first extra refs entry", 4318 configs: []*DefaultRerunAuthConfigEntry{ 4319 { 4320 OrgRepo: "*", 4321 Cluster: "", 4322 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"clarketm"}}, 4323 }, 4324 { 4325 OrgRepo: "istio/istio", 4326 Cluster: "", 4327 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4328 }, 4329 { 4330 OrgRepo: "other-org/other-repo", 4331 Cluster: "", 4332 Config: &prowapi.RerunAuthConfig{GitHubUsers: []string{"anakin"}}, 4333 }, 4334 }, 4335 jobSpec: &prowapi.ProwJobSpec{ 4336 ExtraRefs: []prowapi.Refs{{Org: "istio", Repo: "istio"}, {Org: "other-org", Repo: "other-repo"}}, 4337 }, 4338 expected: &prowapi.RerunAuthConfig{GitHubUsers: []string{"skywalker"}}, 4339 }, 4340 } 4341 4342 for _, tc := range testCases { 4343 t.Run(tc.name, func(t *testing.T) { 4344 d := Deck{} 4345 d.DefaultRerunAuthConfigs = tc.configs 4346 err := d.FinalizeDefaultRerunAuthConfigs() 4347 if err != nil { 4348 t.Fatal("Failed to finalize default rerun auth config.") 4349 } 4350 4351 if diff := cmp.Diff(tc.expected, d.GetRerunAuthConfig(tc.jobSpec)); diff != "" { 4352 t.Errorf("GetRerunAuthConfig returned unexpected value (-want +got):\n%s", diff) 4353 } 4354 }) 4355 } 4356 } 4357 4358 func TestMergeCommitTemplateLoading(t *testing.T) { 4359 var testCases = []struct { 4360 name string 4361 prowConfig string 4362 expectError bool 4363 expect map[string]TideMergeCommitTemplate 4364 }{ 4365 { 4366 name: "no template", 4367 prowConfig: ` 4368 tide: 4369 merge_commit_template: 4370 `, 4371 expect: nil, 4372 }, 4373 { 4374 name: "empty template", 4375 prowConfig: ` 4376 tide: 4377 merge_commit_template: 4378 kubernetes/ingress: 4379 `, 4380 expect: map[string]TideMergeCommitTemplate{ 4381 "kubernetes/ingress": {}, 4382 }, 4383 }, 4384 { 4385 name: "two proper templates", 4386 prowConfig: ` 4387 tide: 4388 merge_commit_template: 4389 kubernetes/ingress: 4390 title: "{{ .Title }}" 4391 body: "{{ .Body }}" 4392 `, 4393 expect: map[string]TideMergeCommitTemplate{ 4394 "kubernetes/ingress": { 4395 TitleTemplate: "{{ .Title }}", 4396 BodyTemplate: "{{ .Body }}", 4397 Title: template.Must(template.New("CommitTitle").Parse("{{ .Title }}")), 4398 Body: template.Must(template.New("CommitBody").Parse("{{ .Body }}")), 4399 }, 4400 }, 4401 }, 4402 { 4403 name: "only title template", 4404 prowConfig: ` 4405 tide: 4406 merge_commit_template: 4407 kubernetes/ingress: 4408 title: "{{ .Title }}" 4409 `, 4410 expect: map[string]TideMergeCommitTemplate{ 4411 "kubernetes/ingress": { 4412 TitleTemplate: "{{ .Title }}", 4413 BodyTemplate: "", 4414 Title: template.Must(template.New("CommitTitle").Parse("{{ .Title }}")), 4415 Body: nil, 4416 }, 4417 }, 4418 }, 4419 { 4420 name: "only body template", 4421 prowConfig: ` 4422 tide: 4423 merge_commit_template: 4424 kubernetes/ingress: 4425 body: "{{ .Body }}" 4426 `, 4427 expect: map[string]TideMergeCommitTemplate{ 4428 "kubernetes/ingress": { 4429 TitleTemplate: "", 4430 BodyTemplate: "{{ .Body }}", 4431 Title: nil, 4432 Body: template.Must(template.New("CommitBody").Parse("{{ .Body }}")), 4433 }, 4434 }, 4435 }, 4436 { 4437 name: "malformed title template", 4438 prowConfig: ` 4439 tide: 4440 merge_commit_template: 4441 kubernetes/ingress: 4442 title: "{{ .Title" 4443 `, 4444 expectError: true, 4445 }, 4446 { 4447 name: "malformed body template", 4448 prowConfig: ` 4449 tide: 4450 merge_commit_template: 4451 kubernetes/ingress: 4452 body: "{{ .Body" 4453 `, 4454 expectError: true, 4455 }, 4456 } 4457 4458 for _, tc := range testCases { 4459 // save the config 4460 prowConfigDir := t.TempDir() 4461 4462 prowConfig := filepath.Join(prowConfigDir, "config.yaml") 4463 if err := os.WriteFile(prowConfig, []byte(tc.prowConfig), 0666); err != nil { 4464 t.Fatalf("fail to write prow config: %v", err) 4465 } 4466 4467 cfg, err := Load(prowConfig, "", nil, "") 4468 if tc.expectError && err == nil { 4469 t.Errorf("tc %s: Expect error, but got nil", tc.name) 4470 } else if !tc.expectError && err != nil { 4471 t.Errorf("tc %s: Expect no error, but got error %v", tc.name, err) 4472 } 4473 4474 if err == nil { 4475 if !reflect.DeepEqual(cfg.Tide.MergeTemplate, tc.expect) { 4476 t.Errorf("tc %s: expected %#v\n!=\nactual %#v", tc.name, tc.expect, cfg.Tide.MergeTemplate) 4477 } 4478 } 4479 } 4480 } 4481 4482 func TestPlankJobURLPrefix(t *testing.T) { 4483 testCases := []struct { 4484 name string 4485 plank Plank 4486 prowjob *prowapi.ProwJob 4487 expectedJobURLPrefix string 4488 }{ 4489 { 4490 name: "Nil refs returns default JobURLPrefix", 4491 plank: Plank{JobURLPrefixConfig: map[string]string{"*": "https://my-prow"}}, 4492 expectedJobURLPrefix: "https://my-prow", 4493 }, 4494 { 4495 name: "No matching refs returns default JobURLPrefx", 4496 plank: Plank{ 4497 JobURLPrefixConfig: map[string]string{ 4498 "*": "https://my-prow", 4499 "my-org": "https://my-alternate-prow", 4500 }, 4501 }, 4502 prowjob: &prowapi.ProwJob{Spec: prowapi.ProwJobSpec{Refs: &prowapi.Refs{Org: "my-default-org", Repo: "my-default-repo"}}}, 4503 expectedJobURLPrefix: "https://my-prow", 4504 }, 4505 { 4506 name: "Matching repo returns JobURLPrefix from repo", 4507 plank: Plank{ 4508 JobURLPrefixConfig: map[string]string{ 4509 "*": "https://my-prow", 4510 "my-alternate-org": "https://my-third-prow", 4511 "my-alternate-org/my-repo": "https://my-alternate-prow", 4512 }, 4513 }, 4514 prowjob: &prowapi.ProwJob{Spec: prowapi.ProwJobSpec{Refs: &prowapi.Refs{Org: "my-alternate-org", Repo: "my-repo"}}}, 4515 expectedJobURLPrefix: "https://my-alternate-prow", 4516 }, 4517 { 4518 name: "Matching repo in extraRefs returns JobURLPrefix from repo", 4519 plank: Plank{ 4520 JobURLPrefixConfig: map[string]string{ 4521 "*": "https://my-prow", 4522 "my-alternate-org": "https://my-third-prow", 4523 "my-alternate-org/my-repo": "https://my-alternate-prow", 4524 }, 4525 }, 4526 prowjob: &prowapi.ProwJob{Spec: prowapi.ProwJobSpec{ExtraRefs: []prowapi.Refs{{Org: "my-alternate-org", Repo: "my-repo"}}}}, 4527 expectedJobURLPrefix: "https://my-alternate-prow", 4528 }, 4529 { 4530 name: "JobURLPrefix in decoration config overrides job_url_prefix_config", 4531 plank: Plank{ 4532 JobURLPrefixConfig: map[string]string{ 4533 "*": "https://my-prow", 4534 "my-alternate-org": "https://my-third-prow", 4535 "my-alternate-org/my-repo": "https://my-alternate-prow", 4536 }, 4537 }, 4538 prowjob: &prowapi.ProwJob{Spec: prowapi.ProwJobSpec{ 4539 DecorationConfig: &prowapi.DecorationConfig{GCSConfiguration: &prowapi.GCSConfiguration{JobURLPrefix: "https://overriden"}}, 4540 Refs: &prowapi.Refs{Org: "my-alternate-org", Repo: "my-repo"}, 4541 }}, 4542 expectedJobURLPrefix: "https://overriden", 4543 }, 4544 { 4545 name: "Matching org and not matching repo returns JobURLPrefix from org", 4546 plank: Plank{ 4547 JobURLPrefixConfig: map[string]string{ 4548 "*": "https://my-prow", 4549 "my-alternate-org": "https://my-third-prow", 4550 "my-alternate-org/my-repo": "https://my-alternate-prow", 4551 }, 4552 }, 4553 prowjob: &prowapi.ProwJob{Spec: prowapi.ProwJobSpec{Refs: &prowapi.Refs{Org: "my-alternate-org", Repo: "my-second-repo"}}}, 4554 expectedJobURLPrefix: "https://my-third-prow", 4555 }, 4556 { 4557 name: "Matching org in extraRefs and not matching repo returns JobURLPrefix from org", 4558 plank: Plank{ 4559 JobURLPrefixConfig: map[string]string{ 4560 "*": "https://my-prow", 4561 "my-alternate-org": "https://my-third-prow", 4562 "my-alternate-org/my-repo": "https://my-alternate-prow", 4563 }, 4564 }, 4565 prowjob: &prowapi.ProwJob{Spec: prowapi.ProwJobSpec{ExtraRefs: []prowapi.Refs{{Org: "my-alternate-org", Repo: "my-second-repo"}}}}, 4566 expectedJobURLPrefix: "https://my-third-prow", 4567 }, 4568 { 4569 name: "Matching org without url returns default JobURLPrefix", 4570 plank: Plank{ 4571 JobURLPrefixConfig: map[string]string{ 4572 "*": "https://my-prow", 4573 "my-alternate-org/my-repo": "https://my-alternate-prow", 4574 }, 4575 }, 4576 prowjob: &prowapi.ProwJob{Spec: prowapi.ProwJobSpec{Refs: &prowapi.Refs{Org: "my-alternate-org", Repo: "my-second-repo"}}}, 4577 expectedJobURLPrefix: "https://my-prow", 4578 }, 4579 } 4580 4581 for _, tc := range testCases { 4582 t.Run(tc.name, func(t *testing.T) { 4583 if tc.prowjob == nil { 4584 tc.prowjob = &prowapi.ProwJob{} 4585 } 4586 if prefix := tc.plank.GetJobURLPrefix(tc.prowjob); prefix != tc.expectedJobURLPrefix { 4587 t.Errorf("expected JobURLPrefix to be %q but was %q", tc.expectedJobURLPrefix, prefix) 4588 } 4589 }) 4590 } 4591 } 4592 4593 func TestValidateComponentConfig(t *testing.T) { 4594 boolTrue := true 4595 boolFalse := false 4596 testCases := []struct { 4597 name string 4598 config *Config 4599 errExpected bool 4600 }{ 4601 { 4602 name: "Valid default URL, no err", 4603 config: &Config{ProwConfig: ProwConfig{Plank: Plank{ 4604 JobURLPrefixConfig: map[string]string{"*": "https://my-prow"}}}}, 4605 errExpected: false, 4606 }, 4607 { 4608 name: "Invalid default URL, err", 4609 config: &Config{ProwConfig: ProwConfig{Plank: Plank{ 4610 JobURLPrefixConfig: map[string]string{"*": "https:// my-prow"}}}}, 4611 errExpected: true, 4612 }, 4613 { 4614 name: "Org config, valid URLs, no err", 4615 config: &Config{ProwConfig: ProwConfig{Plank: Plank{ 4616 JobURLPrefixConfig: map[string]string{ 4617 "*": "https://my-prow", 4618 "my-org": "https://my-alternate-prow", 4619 }, 4620 }}}, 4621 errExpected: false, 4622 }, 4623 { 4624 name: "Org override, invalid default jobURLPrefix URL, err", 4625 config: &Config{ProwConfig: ProwConfig{Plank: Plank{ 4626 JobURLPrefixConfig: map[string]string{ 4627 "*": "https:// my-prow", 4628 "my-org": "https://my-alternate-prow", 4629 }, 4630 }}}, 4631 errExpected: true, 4632 }, 4633 { 4634 name: "Org override, invalid org URL, err", 4635 config: &Config{ProwConfig: ProwConfig{Plank: Plank{ 4636 JobURLPrefixConfig: map[string]string{ 4637 "*": "https://my-prow", 4638 "my-org": "https:// my-alternate-prow", 4639 }, 4640 }}}, 4641 errExpected: true, 4642 }, 4643 { 4644 name: "Org override, invalid URLs, err", 4645 config: &Config{ProwConfig: ProwConfig{Plank: Plank{ 4646 JobURLPrefixConfig: map[string]string{ 4647 "*": "https:// my-prow", 4648 "my-org": "https:// my-alternate-prow", 4649 }, 4650 }}}, 4651 errExpected: true, 4652 }, 4653 { 4654 name: "Repo override, valid URLs, no err", 4655 config: &Config{ProwConfig: ProwConfig{Plank: Plank{ 4656 JobURLPrefixConfig: map[string]string{ 4657 "*": "https://my-prow", 4658 "my-org": "https://my-alternate-prow", 4659 "my-org/my-repo": "https://my-third-prow", 4660 }}}}, 4661 errExpected: false, 4662 }, 4663 { 4664 name: "Repo override, invalid repo URL, err", 4665 config: &Config{ProwConfig: ProwConfig{Plank: Plank{ 4666 JobURLPrefixConfig: map[string]string{ 4667 "*": "https://my-prow", 4668 "my-org": "https://my-alternate-prow", 4669 "my-org/my-repo": "https:// my-third-prow", 4670 }}}}, 4671 errExpected: true, 4672 }, 4673 { 4674 name: "RerunAuthConfigs and not RerunAuthConfig is valid, no err", 4675 config: &Config{ProwConfig: ProwConfig{Deck: Deck{ 4676 RerunAuthConfigs: RerunAuthConfigs{ 4677 "*": prowapi.RerunAuthConfig{AllowAnyone: true}, 4678 "kubernetes": prowapi.RerunAuthConfig{GitHubUsers: []string{"easterbunny"}}, 4679 "kubernetes/kubernetes": prowapi.RerunAuthConfig{GitHubOrgs: []string{"kubernetes", "kubernetes-sigs"}}, 4680 }, 4681 }}}, 4682 errExpected: false, 4683 }, 4684 { 4685 name: "RerunAuthConfigs only and validation fails, err", 4686 config: &Config{ProwConfig: ProwConfig{Deck: Deck{ 4687 RerunAuthConfigs: RerunAuthConfigs{ 4688 "*": prowapi.RerunAuthConfig{AllowAnyone: true}, 4689 "kubernetes": prowapi.RerunAuthConfig{GitHubUsers: []string{"easterbunny"}}, 4690 "kubernetes/kubernetes": prowapi.RerunAuthConfig{AllowAnyone: true, GitHubOrgs: []string{"kubernetes", "kubernetes-sigs"}}, 4691 }, 4692 }}}, 4693 errExpected: true, 4694 }, 4695 { 4696 name: "SkipStoragePathValidation true and AdditionalAllowedBuckets empty, no err", 4697 config: &Config{ProwConfig: ProwConfig{Deck: Deck{ 4698 SkipStoragePathValidation: &boolTrue, 4699 AdditionalAllowedBuckets: []string{}, 4700 }}}, 4701 errExpected: false, 4702 }, 4703 { 4704 name: "SkipStoragePathValidation true and AdditionalAllowedBuckets non-empty, err", 4705 config: &Config{ProwConfig: ProwConfig{Deck: Deck{ 4706 SkipStoragePathValidation: &boolTrue, 4707 AdditionalAllowedBuckets: []string{ 4708 "foo", 4709 "bar", 4710 }, 4711 }}}, 4712 errExpected: true, 4713 }, 4714 { 4715 name: "SkipStoragePathValidation false and AdditionalAllowedBuckets non-empty, no err", 4716 config: &Config{ProwConfig: ProwConfig{Deck: Deck{ 4717 SkipStoragePathValidation: &boolFalse, 4718 AdditionalAllowedBuckets: []string{ 4719 "foo", 4720 "bar", 4721 }, 4722 }}}, 4723 errExpected: false, 4724 }, 4725 } 4726 4727 for _, tc := range testCases { 4728 t.Run(tc.name, func(t *testing.T) { 4729 if hasErr := tc.config.validateComponentConfig() != nil; hasErr != tc.errExpected { 4730 t.Errorf("expected err: %t but was %t", tc.errExpected, hasErr) 4731 } 4732 }) 4733 } 4734 } 4735 4736 func TestSlackReporterValidation(t *testing.T) { 4737 testCases := []struct { 4738 name string 4739 config func() Config 4740 successExpected bool 4741 }{ 4742 { 4743 name: "Valid config w/ wildcard slack_reporter_configs - no error", 4744 config: func() Config { 4745 slackCfg := map[string]SlackReporter{ 4746 "*": { 4747 SlackReporterConfig: prowapi.SlackReporterConfig{ 4748 Channel: "my-channel", 4749 }, 4750 }, 4751 } 4752 return Config{ 4753 ProwConfig: ProwConfig{ 4754 SlackReporterConfigs: slackCfg, 4755 }, 4756 } 4757 }, 4758 successExpected: true, 4759 }, 4760 { 4761 name: "Valid config w/ org/repo slack_reporter_configs - no error", 4762 config: func() Config { 4763 slackCfg := map[string]SlackReporter{ 4764 "istio/proxy": { 4765 SlackReporterConfig: prowapi.SlackReporterConfig{ 4766 Channel: "my-channel", 4767 }, 4768 }, 4769 } 4770 return Config{ 4771 ProwConfig: ProwConfig{ 4772 SlackReporterConfigs: slackCfg, 4773 }, 4774 } 4775 }, 4776 successExpected: true, 4777 }, 4778 { 4779 name: "Valid config w/ repo slack_reporter_configs - no error", 4780 config: func() Config { 4781 slackCfg := map[string]SlackReporter{ 4782 "proxy": { 4783 SlackReporterConfig: prowapi.SlackReporterConfig{ 4784 Channel: "my-channel", 4785 }, 4786 }, 4787 } 4788 return Config{ 4789 ProwConfig: ProwConfig{ 4790 SlackReporterConfigs: slackCfg, 4791 }, 4792 } 4793 }, 4794 successExpected: true, 4795 }, 4796 { 4797 name: "No channel w/ slack_reporter_configs - error", 4798 config: func() Config { 4799 slackCfg := map[string]SlackReporter{ 4800 "*": { 4801 JobTypesToReport: []prowapi.ProwJobType{"presubmit"}, 4802 }, 4803 } 4804 return Config{ 4805 ProwConfig: ProwConfig{ 4806 SlackReporterConfigs: slackCfg, 4807 }, 4808 } 4809 }, 4810 successExpected: false, 4811 }, 4812 { 4813 name: "Empty config - no error", 4814 config: func() Config { 4815 slackCfg := map[string]SlackReporter{} 4816 return Config{ 4817 ProwConfig: ProwConfig{ 4818 SlackReporterConfigs: slackCfg, 4819 }, 4820 } 4821 }, 4822 successExpected: true, 4823 }, 4824 { 4825 name: "Invalid template - error", 4826 config: func() Config { 4827 slackCfg := map[string]SlackReporter{ 4828 "*": { 4829 SlackReporterConfig: prowapi.SlackReporterConfig{ 4830 Channel: "my-channel", 4831 ReportTemplate: "{{ if .Spec.Name}}", 4832 }, 4833 }, 4834 } 4835 return Config{ 4836 ProwConfig: ProwConfig{ 4837 SlackReporterConfigs: slackCfg, 4838 }, 4839 } 4840 }, 4841 successExpected: false, 4842 }, 4843 { 4844 name: "Template accessed invalid property - error", 4845 config: func() Config { 4846 slackCfg := map[string]SlackReporter{ 4847 "*": { 4848 SlackReporterConfig: prowapi.SlackReporterConfig{ 4849 Channel: "my-channel", 4850 ReportTemplate: "{{ .Undef}}", 4851 }, 4852 }, 4853 } 4854 return Config{ 4855 ProwConfig: ProwConfig{ 4856 SlackReporterConfigs: slackCfg, 4857 }, 4858 } 4859 }, 4860 successExpected: false, 4861 }, 4862 } 4863 4864 for _, tc := range testCases { 4865 t.Run(tc.name, func(t *testing.T) { 4866 cfg := tc.config() 4867 if err := cfg.validateComponentConfig(); (err == nil) != tc.successExpected { 4868 t.Errorf("Expected success=%t but got err=%v", tc.successExpected, err) 4869 } 4870 if tc.successExpected { 4871 for _, config := range cfg.SlackReporterConfigs { 4872 if config.ReportTemplate == "" { 4873 t.Errorf("expected default ReportTemplate to be set") 4874 } 4875 if config.Channel == "" { 4876 t.Errorf("expected Channel to be required") 4877 } 4878 } 4879 } 4880 }) 4881 } 4882 } 4883 func TestManagedHmacEntityValidation(t *testing.T) { 4884 testCases := []struct { 4885 name string 4886 prowConfig Config 4887 shouldFail bool 4888 }{ 4889 { 4890 name: "Missing managed HmacEntities", 4891 prowConfig: Config{ProwConfig: ProwConfig{ManagedWebhooks: ManagedWebhooks{}}}, 4892 shouldFail: false, 4893 }, 4894 { 4895 name: "Config with all valid dates", 4896 prowConfig: Config{ProwConfig: ProwConfig{ 4897 ManagedWebhooks: ManagedWebhooks{ 4898 OrgRepoConfig: map[string]ManagedWebhookInfo{ 4899 "foo/bar": {TokenCreatedAfter: time.Now()}, 4900 "foo/baz": {TokenCreatedAfter: time.Now()}, 4901 }, 4902 }, 4903 }}, 4904 shouldFail: false, 4905 }, 4906 { 4907 name: "Config with one invalid dates", 4908 prowConfig: Config{ProwConfig: ProwConfig{ 4909 ManagedWebhooks: ManagedWebhooks{ 4910 OrgRepoConfig: map[string]ManagedWebhookInfo{ 4911 "foo/bar": {TokenCreatedAfter: time.Now()}, 4912 "foo/baz": {TokenCreatedAfter: time.Now().Add(time.Hour)}, 4913 }, 4914 }, 4915 }}, 4916 shouldFail: true, 4917 }, 4918 } 4919 for _, tc := range testCases { 4920 t.Run(tc.name, func(t *testing.T) { 4921 4922 err := tc.prowConfig.validateComponentConfig() 4923 if tc.shouldFail != (err != nil) { 4924 t.Errorf("%s: Unexpected outcome. Error expected %v, Error found %s", tc.name, tc.shouldFail, err) 4925 } 4926 4927 }) 4928 } 4929 } 4930 func TestValidateTriggering(t *testing.T) { 4931 testCases := []struct { 4932 name string 4933 presubmit Presubmit 4934 errExpected bool 4935 }{ 4936 { 4937 name: "Trigger set, rerun command unset, err", 4938 presubmit: Presubmit{ 4939 Trigger: "my-trigger", 4940 Reporter: Reporter{ 4941 Context: "my-context", 4942 }, 4943 }, 4944 errExpected: true, 4945 }, 4946 { 4947 name: "Triger unset, rerun command set, err", 4948 presubmit: Presubmit{ 4949 RerunCommand: "my-rerun-command", 4950 Reporter: Reporter{ 4951 Context: "my-context", 4952 }, 4953 }, 4954 errExpected: true, 4955 }, 4956 { 4957 name: "Both trigger and rerun command set, no err", 4958 presubmit: Presubmit{ 4959 Trigger: "my-trigger", 4960 RerunCommand: "my-rerun-command", 4961 Reporter: Reporter{ 4962 Context: "my-context", 4963 }, 4964 }, 4965 errExpected: false, 4966 }, 4967 } 4968 4969 for _, tc := range testCases { 4970 t.Run(tc.name, func(t *testing.T) { 4971 err := validateTriggering(tc.presubmit) 4972 if err != nil != tc.errExpected { 4973 t.Errorf("Expected err: %t but got err %v", tc.errExpected, err) 4974 } 4975 }) 4976 } 4977 } 4978 4979 func TestValidateAlwaysRunPostsubmit(t *testing.T) { 4980 true_ := true 4981 testCases := []struct { 4982 name string 4983 postsubmit Postsubmit 4984 errExpected bool 4985 }{ 4986 { 4987 name: "both always_run and run_if_changed set, err", 4988 postsubmit: Postsubmit{ 4989 AlwaysRun: &true_, 4990 RegexpChangeMatcher: RegexpChangeMatcher{ 4991 RunIfChanged: `foo`, 4992 }, 4993 }, 4994 errExpected: true, 4995 }, 4996 { 4997 name: "both always_run and skip_if_only_changed set, err", 4998 postsubmit: Postsubmit{ 4999 AlwaysRun: &true_, 5000 RegexpChangeMatcher: RegexpChangeMatcher{ 5001 SkipIfOnlyChanged: `foo`, 5002 }, 5003 }, 5004 errExpected: true, 5005 }, 5006 { 5007 name: "both run_if_changed and skip_if_only_changed set, err", 5008 postsubmit: Postsubmit{ 5009 RegexpChangeMatcher: RegexpChangeMatcher{ 5010 RunIfChanged: `foo`, 5011 SkipIfOnlyChanged: `foo`, 5012 }, 5013 }, 5014 errExpected: true, 5015 }, 5016 } 5017 5018 for _, tc := range testCases { 5019 t.Run(tc.name, func(t *testing.T) { 5020 err := validateAlwaysRun(tc.postsubmit) 5021 if err != nil != tc.errExpected { 5022 t.Errorf("Expected err: %t but got err %v", tc.errExpected, err) 5023 } 5024 }) 5025 } 5026 } 5027 5028 func TestRefGetterForGitHubPullRequest(t *testing.T) { 5029 testCases := []struct { 5030 name string 5031 rg *RefGetterForGitHubPullRequest 5032 verify func(*RefGetterForGitHubPullRequest) error 5033 }{ 5034 { 5035 name: "Existing PullRequest is returned", 5036 rg: &RefGetterForGitHubPullRequest{pr: &github.PullRequest{ID: 123456}}, 5037 verify: func(rg *RefGetterForGitHubPullRequest) error { 5038 if rg.pr == nil || rg.pr.ID != 123456 { 5039 return fmt.Errorf("Expected refGetter to contain pr with id 123456, pr was %v", rg.pr) 5040 } 5041 return nil 5042 }, 5043 }, 5044 { 5045 name: "PullRequest is fetched, stored and returned", 5046 rg: &RefGetterForGitHubPullRequest{ 5047 ghc: &fakegithub.FakeClient{ 5048 PullRequests: map[int]*github.PullRequest{0: {ID: 123456}}}, 5049 }, 5050 verify: func(rg *RefGetterForGitHubPullRequest) error { 5051 pr, err := rg.PullRequest() 5052 if err != nil { 5053 return fmt.Errorf("failed to fetch PullRequest: %w", err) 5054 } 5055 if rg.pr == nil || rg.pr.ID != 123456 { 5056 return fmt.Errorf("expected agent to contain pr with id 123456, pr was %v", rg.pr) 5057 } 5058 if pr.ID != 123456 { 5059 return fmt.Errorf("expected returned pr.ID to be 123456, was %d", pr.ID) 5060 } 5061 return nil 5062 }, 5063 }, 5064 { 5065 name: "Existing baseSHA is returned", 5066 rg: &RefGetterForGitHubPullRequest{baseSHA: "12345", pr: &github.PullRequest{}}, 5067 verify: func(rg *RefGetterForGitHubPullRequest) error { 5068 baseSHA, err := rg.BaseSHA() 5069 if err != nil { 5070 return fmt.Errorf("error calling baseSHA: %w", err) 5071 } 5072 if rg.baseSHA != "12345" { 5073 return fmt.Errorf("expected agent baseSHA to be 12345, was %q", rg.baseSHA) 5074 } 5075 if baseSHA != "12345" { 5076 return fmt.Errorf("expected returned baseSHA to be 12345, was %q", baseSHA) 5077 } 5078 return nil 5079 }, 5080 }, 5081 { 5082 name: "BaseSHA is fetched, stored and returned", 5083 rg: &RefGetterForGitHubPullRequest{ 5084 ghc: &fakegithub.FakeClient{ 5085 PullRequests: map[int]*github.PullRequest{0: {}}, 5086 }, 5087 }, 5088 verify: func(rg *RefGetterForGitHubPullRequest) error { 5089 baseSHA, err := rg.BaseSHA() 5090 if err != nil { 5091 return fmt.Errorf("expected err to be nil, was %w", err) 5092 } 5093 if rg.baseSHA != fakegithub.TestRef { 5094 return fmt.Errorf("expected baseSHA on agent to be %q, was %q", fakegithub.TestRef, rg.baseSHA) 5095 } 5096 if baseSHA != fakegithub.TestRef { 5097 return fmt.Errorf("expected returned baseSHA to be %q, was %q", fakegithub.TestRef, baseSHA) 5098 } 5099 return nil 5100 }, 5101 }, 5102 } 5103 5104 for _, tc := range testCases { 5105 t.Run(tc.name, func(t *testing.T) { 5106 tc.rg.lock = &sync.Mutex{} 5107 if err := tc.verify(tc.rg); err != nil { 5108 t.Fatal(err) 5109 } 5110 }) 5111 } 5112 } 5113 5114 func TestFinalizeDefaultDecorationConfigs(t *testing.T) { 5115 tcs := []struct { 5116 name string 5117 raw string 5118 expected []*DefaultDecorationConfigEntry 5119 expectErr bool 5120 }{ 5121 { 5122 name: "omitted config", 5123 raw: "deck:", 5124 expected: nil, 5125 }, 5126 { 5127 name: "old format; global only", 5128 raw: ` 5129 default_decoration_configs: 5130 '*': 5131 timeout: 2h 5132 grace_period: 15s 5133 utility_images: 5134 clonerefs: "clonerefs:default" 5135 initupload: "initupload:default" 5136 entrypoint: "entrypoint:default" 5137 sidecar: "sidecar:default" 5138 gcs_configuration: 5139 bucket: "default-bucket" 5140 path_strategy: "legacy" 5141 default_org: "kubernetes" 5142 default_repo: "kubernetes" 5143 gcs_credentials_secret: "default-service-account" 5144 `, 5145 expected: []*DefaultDecorationConfigEntry{ 5146 { 5147 OrgRepo: "*", 5148 Cluster: "", 5149 Config: &prowapi.DecorationConfig{ 5150 Timeout: &prowapi.Duration{Duration: 2 * time.Hour}, 5151 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 5152 UtilityImages: &prowapi.UtilityImages{ 5153 CloneRefs: "clonerefs:default", 5154 InitUpload: "initupload:default", 5155 Entrypoint: "entrypoint:default", 5156 Sidecar: "sidecar:default", 5157 }, 5158 GCSConfiguration: &prowapi.GCSConfiguration{ 5159 Bucket: "default-bucket", 5160 PathStrategy: prowapi.PathStrategyLegacy, 5161 DefaultOrg: "kubernetes", 5162 DefaultRepo: "kubernetes", 5163 }, 5164 GCSCredentialsSecret: pStr("default-service-account"), 5165 }, 5166 }, 5167 }, 5168 }, 5169 { 5170 name: "old format; org repo ordered", 5171 raw: ` 5172 default_decoration_configs: 5173 '*': 5174 timeout: 2h 5175 grace_period: 15s 5176 utility_images: 5177 clonerefs: "clonerefs:default" 5178 initupload: "initupload:default" 5179 entrypoint: "entrypoint:default" 5180 sidecar: "sidecar:default" 5181 gcs_configuration: 5182 bucket: "default-bucket" 5183 path_strategy: "legacy" 5184 default_org: "kubernetes" 5185 default_repo: "kubernetes" 5186 gcs_credentials_secret: "default-service-account" 5187 'org/repo': 5188 timeout: 1h 5189 'org': 5190 timeout: 3h 5191 `, 5192 expected: []*DefaultDecorationConfigEntry{ 5193 { 5194 OrgRepo: "*", 5195 Cluster: "", 5196 Config: &prowapi.DecorationConfig{ 5197 Timeout: &prowapi.Duration{Duration: 2 * time.Hour}, 5198 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 5199 UtilityImages: &prowapi.UtilityImages{ 5200 CloneRefs: "clonerefs:default", 5201 InitUpload: "initupload:default", 5202 Entrypoint: "entrypoint:default", 5203 Sidecar: "sidecar:default", 5204 }, 5205 GCSConfiguration: &prowapi.GCSConfiguration{ 5206 Bucket: "default-bucket", 5207 PathStrategy: prowapi.PathStrategyLegacy, 5208 DefaultOrg: "kubernetes", 5209 DefaultRepo: "kubernetes", 5210 }, 5211 GCSCredentialsSecret: pStr("default-service-account"), 5212 }, 5213 }, 5214 { 5215 OrgRepo: "org", 5216 Cluster: "", 5217 Config: &prowapi.DecorationConfig{ 5218 Timeout: &prowapi.Duration{Duration: 3 * time.Hour}, 5219 }, 5220 }, 5221 { 5222 OrgRepo: "org/repo", 5223 Cluster: "", 5224 Config: &prowapi.DecorationConfig{ 5225 Timeout: &prowapi.Duration{Duration: 1 * time.Hour}, 5226 }, 5227 }, 5228 }, 5229 }, 5230 { 5231 name: "new format; global only", 5232 raw: ` 5233 default_decoration_config_entries: 5234 - config: 5235 timeout: 2h 5236 grace_period: 15s 5237 utility_images: 5238 clonerefs: "clonerefs:default" 5239 initupload: "initupload:default" 5240 entrypoint: "entrypoint:default" 5241 sidecar: "sidecar:default" 5242 gcs_configuration: 5243 bucket: "default-bucket" 5244 path_strategy: "legacy" 5245 default_org: "kubernetes" 5246 default_repo: "kubernetes" 5247 gcs_credentials_secret: "default-service-account" 5248 `, 5249 expected: []*DefaultDecorationConfigEntry{ 5250 { 5251 OrgRepo: "", 5252 Cluster: "", 5253 Config: &prowapi.DecorationConfig{ 5254 Timeout: &prowapi.Duration{Duration: 2 * time.Hour}, 5255 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 5256 UtilityImages: &prowapi.UtilityImages{ 5257 CloneRefs: "clonerefs:default", 5258 InitUpload: "initupload:default", 5259 Entrypoint: "entrypoint:default", 5260 Sidecar: "sidecar:default", 5261 }, 5262 GCSConfiguration: &prowapi.GCSConfiguration{ 5263 Bucket: "default-bucket", 5264 PathStrategy: prowapi.PathStrategyLegacy, 5265 DefaultOrg: "kubernetes", 5266 DefaultRepo: "kubernetes", 5267 }, 5268 GCSCredentialsSecret: pStr("default-service-account"), 5269 }, 5270 }, 5271 }, 5272 }, 5273 { 5274 name: "new format; global, org, repo, cluster, org+cluster", 5275 raw: ` 5276 default_decoration_config_entries: 5277 - config: 5278 timeout: 2h 5279 grace_period: 15s 5280 utility_images: 5281 clonerefs: "clonerefs:default" 5282 initupload: "initupload:default" 5283 entrypoint: "entrypoint:default" 5284 sidecar: "sidecar:default" 5285 gcs_configuration: 5286 bucket: "default-bucket" 5287 path_strategy: "legacy" 5288 default_org: "kubernetes" 5289 default_repo: "kubernetes" 5290 gcs_credentials_secret: "default-service-account" 5291 - repo: "org" 5292 cluster: "*" 5293 config: 5294 timeout: 1h 5295 - repo: "org/repo" 5296 config: 5297 timeout: 3h 5298 - cluster: "trusted" 5299 config: 5300 grace_period: 30s 5301 - repo: "org/foo" 5302 cluster: "trusted" 5303 config: 5304 grace_period: 1m 5305 `, 5306 expected: []*DefaultDecorationConfigEntry{ 5307 { 5308 OrgRepo: "", 5309 Cluster: "", 5310 Config: &prowapi.DecorationConfig{ 5311 Timeout: &prowapi.Duration{Duration: 2 * time.Hour}, 5312 GracePeriod: &prowapi.Duration{Duration: 15 * time.Second}, 5313 UtilityImages: &prowapi.UtilityImages{ 5314 CloneRefs: "clonerefs:default", 5315 InitUpload: "initupload:default", 5316 Entrypoint: "entrypoint:default", 5317 Sidecar: "sidecar:default", 5318 }, 5319 GCSConfiguration: &prowapi.GCSConfiguration{ 5320 Bucket: "default-bucket", 5321 PathStrategy: prowapi.PathStrategyLegacy, 5322 DefaultOrg: "kubernetes", 5323 DefaultRepo: "kubernetes", 5324 }, 5325 GCSCredentialsSecret: pStr("default-service-account"), 5326 }, 5327 }, 5328 { 5329 OrgRepo: "org", 5330 Cluster: "*", 5331 Config: &prowapi.DecorationConfig{ 5332 Timeout: &prowapi.Duration{Duration: 1 * time.Hour}, 5333 }, 5334 }, 5335 { 5336 OrgRepo: "org/repo", 5337 Cluster: "", 5338 Config: &prowapi.DecorationConfig{ 5339 Timeout: &prowapi.Duration{Duration: 3 * time.Hour}, 5340 }, 5341 }, 5342 { 5343 OrgRepo: "", 5344 Cluster: "trusted", 5345 Config: &prowapi.DecorationConfig{ 5346 GracePeriod: &prowapi.Duration{Duration: 30 * time.Second}, 5347 }, 5348 }, 5349 { 5350 OrgRepo: "org/foo", 5351 Cluster: "trusted", 5352 Config: &prowapi.DecorationConfig{ 5353 GracePeriod: &prowapi.Duration{Duration: 1 * time.Minute}, 5354 }, 5355 }, 5356 }, 5357 }, 5358 { 5359 name: "org, repo, cluster specific timeouts", 5360 raw: ` 5361 default_decoration_config_entries: 5362 - repo: "org" 5363 config: 5364 pod_running_timeout: 3h 5365 pod_pending_timeout: 2h 5366 pod_unscheduled_timeout: 1h 5367 - repo: "org/repo" 5368 config: 5369 pod_running_timeout: 2h 5370 pod_pending_timeout: 1h 5371 pod_unscheduled_timeout: 3h 5372 - repo: "org/foo" 5373 config: 5374 pod_running_timeout: 1h 5375 pod_pending_timeout: 2h 5376 pod_unscheduled_timeout: 3h 5377 - cluster: "trusted" 5378 config: 5379 pod_running_timeout: 30m 5380 pod_pending_timeout: 45m 5381 pod_unscheduled_timeout: 15m 5382 `, 5383 expected: []*DefaultDecorationConfigEntry{ 5384 { 5385 OrgRepo: "org", 5386 Cluster: "", 5387 Config: &prowapi.DecorationConfig{ 5388 PodRunningTimeout: &metav1.Duration{Duration: 3 * time.Hour}, 5389 PodPendingTimeout: &metav1.Duration{Duration: 2 * time.Hour}, 5390 PodUnscheduledTimeout: &metav1.Duration{Duration: 1 * time.Hour}, 5391 }, 5392 }, 5393 { 5394 OrgRepo: "org/repo", 5395 Cluster: "", 5396 Config: &prowapi.DecorationConfig{ 5397 PodRunningTimeout: &metav1.Duration{Duration: 2 * time.Hour}, 5398 PodPendingTimeout: &metav1.Duration{Duration: 1 * time.Hour}, 5399 PodUnscheduledTimeout: &metav1.Duration{Duration: 3 * time.Hour}, 5400 }, 5401 }, 5402 { 5403 OrgRepo: "org/foo", 5404 Cluster: "", 5405 Config: &prowapi.DecorationConfig{ 5406 PodRunningTimeout: &metav1.Duration{Duration: 1 * time.Hour}, 5407 PodPendingTimeout: &metav1.Duration{Duration: 2 * time.Hour}, 5408 PodUnscheduledTimeout: &metav1.Duration{Duration: 3 * time.Hour}, 5409 }, 5410 }, 5411 { 5412 OrgRepo: "", 5413 Cluster: "trusted", 5414 Config: &prowapi.DecorationConfig{ 5415 PodRunningTimeout: &metav1.Duration{Duration: 30 * time.Minute}, 5416 PodPendingTimeout: &metav1.Duration{Duration: 45 * time.Minute}, 5417 PodUnscheduledTimeout: &metav1.Duration{Duration: 15 * time.Minute}, 5418 }, 5419 }, 5420 }, 5421 }, 5422 { 5423 name: "both formats, expect error", 5424 raw: ` 5425 default_decoration_configs: 5426 "*": 5427 timeout: 1h 5428 grace_period: 15s 5429 utility_images: 5430 clonerefs: "clonerefs:default" 5431 initupload: "initupload:default" 5432 entrypoint: "entrypoint:default" 5433 sidecar: "sidecar:default" 5434 gcs_configuration: 5435 bucket: "default-bucket" 5436 path_strategy: "legacy" 5437 default_org: "kubernetes" 5438 default_repo: "kubernetes" 5439 gcs_credentials_secret: "default-service-account" 5440 5441 default_decoration_config_entries: 5442 - config: 5443 timeout: 2h 5444 grace_period: 15s 5445 utility_images: 5446 clonerefs: "clonerefs:default" 5447 initupload: "initupload:default" 5448 entrypoint: "entrypoint:default" 5449 sidecar: "sidecar:default" 5450 gcs_configuration: 5451 bucket: "default-bucket" 5452 path_strategy: "legacy" 5453 default_org: "kubernetes" 5454 default_repo: "kubernetes" 5455 gcs_credentials_secret: "default-service-account" 5456 `, 5457 expectErr: true, 5458 }, 5459 } 5460 5461 for i := range tcs { 5462 tc := tcs[i] 5463 t.Run(tc.name, func(t *testing.T) { 5464 t.Parallel() 5465 p := Plank{} 5466 if err := yaml.Unmarshal([]byte(tc.raw), &p); err != nil { 5467 t.Errorf("error unmarshaling: %v", err) 5468 } 5469 if err := p.FinalizeDefaultDecorationConfigs(); err != nil && !tc.expectErr { 5470 t.Errorf("unexpected error finalizing DefaultDecorationConfigs: %v", err) 5471 } else if err == nil && tc.expectErr { 5472 t.Error("expected error, but did not receive one") 5473 } 5474 if diff := cmp.Diff(tc.expected, p.DefaultDecorationConfigs, cmpopts.IgnoreUnexported(regexp.Regexp{})); diff != "" { 5475 t.Errorf("expected result diff: %s", diff) 5476 } 5477 }) 5478 } 5479 } 5480 5481 // complexConfig is shared by multiple test cases that test DefaultDecorationConfig 5482 // merging logic. It configures the upload bucket based on the org/repo and 5483 // uses either a GCS secret or k8s SA depending on the cluster. 5484 // A specific 'override' org overrides some fields in the trusted cluster only. 5485 func complexConfig() *Config { 5486 return &Config{ 5487 JobConfig: JobConfig{ 5488 DecorateAllJobs: true, 5489 }, 5490 ProwConfig: ProwConfig{ 5491 Plank: Plank{ 5492 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 5493 { 5494 OrgRepo: "*", 5495 Cluster: "*", 5496 Config: &prowapi.DecorationConfig{ 5497 UtilityImages: &prowapi.UtilityImages{ 5498 CloneRefs: "clonerefs:global", 5499 InitUpload: "initupload:global", 5500 Entrypoint: "entrypoint:global", 5501 Sidecar: "sidecar:global", 5502 }, 5503 GCSConfiguration: &prowapi.GCSConfiguration{ 5504 Bucket: "global", 5505 PathStrategy: "explicit", 5506 }, 5507 }, 5508 }, 5509 { 5510 OrgRepo: "org", 5511 Cluster: "*", 5512 Config: &prowapi.DecorationConfig{ 5513 GCSConfiguration: &prowapi.GCSConfiguration{ 5514 Bucket: "org-specific", 5515 PathStrategy: "explicit", 5516 }, 5517 }, 5518 }, 5519 { 5520 OrgRepo: "org/repo", 5521 Cluster: "*", 5522 Config: &prowapi.DecorationConfig{ 5523 GCSConfiguration: &prowapi.GCSConfiguration{ 5524 Bucket: "repo-specific", 5525 PathStrategy: "explicit", 5526 }, 5527 }, 5528 }, 5529 { 5530 OrgRepo: "*", 5531 Cluster: "default", 5532 Config: &prowapi.DecorationConfig{ 5533 GCSCredentialsSecret: pStr("default-cluster-uses-secret"), 5534 }, 5535 }, 5536 { 5537 OrgRepo: "*", 5538 Cluster: "trusted", 5539 Config: &prowapi.DecorationConfig{ 5540 DefaultServiceAccountName: pStr("trusted-cluster-uses-SA"), 5541 }, 5542 }, 5543 { 5544 OrgRepo: "override", 5545 Cluster: "trusted", 5546 Config: &prowapi.DecorationConfig{ 5547 UtilityImages: &prowapi.UtilityImages{ 5548 CloneRefs: "clonerefs:override", 5549 }, 5550 DefaultServiceAccountName: pStr(""), 5551 GCSCredentialsSecret: pStr("trusted-cluster-override-uses-secret"), 5552 }, 5553 }, 5554 }, 5555 }, 5556 }, 5557 } 5558 } 5559 5560 // TODO(mpherman): Add more detailed unit test when there is more than 1 field in ProwJobDefaults 5561 // Need unit tests for merging more complicated defaults. 5562 func TestSetPeriodicProwJobDefaults(t *testing.T) { 5563 testCases := []struct { 5564 id string 5565 utilityConfig UtilityConfig 5566 cluster string 5567 config *Config 5568 givenDefault *prowapi.ProwJobDefault 5569 expected *prowapi.ProwJobDefault 5570 }{ 5571 { 5572 id: "No ProwJobDefault in job or in config, expect DefaultTenantID", 5573 config: &Config{ProwConfig: ProwConfig{}}, 5574 expected: &prowapi.ProwJobDefault{TenantID: DefaultTenantID}, 5575 }, 5576 { 5577 id: "no default in job or in config's by repo config, expect default entry", 5578 config: &Config{ 5579 ProwConfig: ProwConfig{ 5580 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5581 { 5582 OrgRepo: "*", 5583 Cluster: "", 5584 Config: &prowapi.ProwJobDefault{ 5585 TenantID: "configDefault", 5586 }, 5587 }, 5588 }, 5589 }, 5590 }, 5591 expected: &prowapi.ProwJobDefault{ 5592 TenantID: "configDefault", 5593 }, 5594 }, 5595 { 5596 id: "no default in presubmit, matching by repo config, expect merged by repo config", 5597 utilityConfig: UtilityConfig{ 5598 ExtraRefs: []prowapi.Refs{ 5599 { 5600 Org: "org", 5601 Repo: "repo", 5602 }, 5603 }, 5604 }, 5605 config: &Config{ 5606 ProwConfig: ProwConfig{ 5607 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5608 { 5609 OrgRepo: "*", 5610 Cluster: "", 5611 Config: &prowapi.ProwJobDefault{ 5612 TenantID: "configDefault", 5613 }, 5614 }, 5615 { 5616 OrgRepo: "org/repo", 5617 Cluster: "", 5618 Config: &prowapi.ProwJobDefault{ 5619 TenantID: "org/repo default", 5620 }, 5621 }, 5622 }, 5623 }, 5624 }, 5625 expected: &prowapi.ProwJobDefault{ 5626 TenantID: "org/repo default", 5627 }, 5628 }, 5629 { 5630 id: "default in job and config's defaults, expect job's default", 5631 utilityConfig: UtilityConfig{ 5632 ExtraRefs: []prowapi.Refs{ 5633 { 5634 Org: "org", 5635 Repo: "repo", 5636 }, 5637 }, 5638 }, 5639 givenDefault: &prowapi.ProwJobDefault{ 5640 TenantID: "given default", 5641 }, 5642 config: &Config{ 5643 ProwConfig: ProwConfig{ 5644 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5645 { 5646 OrgRepo: "*", 5647 Cluster: "", 5648 Config: &prowapi.ProwJobDefault{ 5649 TenantID: "config Default", 5650 }, 5651 }, 5652 { 5653 OrgRepo: "org/repo", 5654 Cluster: "", 5655 Config: &prowapi.ProwJobDefault{ 5656 TenantID: "org/repo default", 5657 }, 5658 }, 5659 }, 5660 }, 5661 }, 5662 expected: &prowapi.ProwJobDefault{ 5663 TenantID: "given default", 5664 }, 5665 }, 5666 { 5667 id: "no default in job. config's default by org, expect org's default", 5668 utilityConfig: UtilityConfig{ 5669 ExtraRefs: []prowapi.Refs{ 5670 { 5671 Org: "org", 5672 Repo: "repo", 5673 }, 5674 }, 5675 }, 5676 config: &Config{ 5677 ProwConfig: ProwConfig{ 5678 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5679 { 5680 OrgRepo: "*", 5681 Cluster: "", 5682 Config: &prowapi.ProwJobDefault{ 5683 TenantID: "configDefault", 5684 }, 5685 }, 5686 { 5687 OrgRepo: "org", 5688 Cluster: "", 5689 Config: &prowapi.ProwJobDefault{ 5690 TenantID: "org default", 5691 }, 5692 }, 5693 }, 5694 }, 5695 }, 5696 expected: &prowapi.ProwJobDefault{ 5697 TenantID: "org default", 5698 }, 5699 }, 5700 { 5701 id: "no default in job or in config's by repo config, expect default entry with repo provided", 5702 utilityConfig: UtilityConfig{ 5703 ExtraRefs: []prowapi.Refs{ 5704 { 5705 Org: "org", 5706 Repo: "repo", 5707 }, 5708 }, 5709 }, 5710 config: &Config{ 5711 ProwConfig: ProwConfig{ 5712 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5713 { 5714 OrgRepo: "*", 5715 Cluster: "", 5716 Config: &prowapi.ProwJobDefault{ 5717 TenantID: "configDefault", 5718 }, 5719 }, 5720 }, 5721 }, 5722 }, 5723 expected: &prowapi.ProwJobDefault{ 5724 TenantID: "configDefault", 5725 }, 5726 }, 5727 { 5728 id: "no default in job. config's default by org and org/repo, expect org/repo default", 5729 utilityConfig: UtilityConfig{ 5730 ExtraRefs: []prowapi.Refs{ 5731 { 5732 Org: "org", 5733 Repo: "repo", 5734 }, 5735 }, 5736 }, 5737 config: &Config{ 5738 ProwConfig: ProwConfig{ 5739 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5740 { 5741 OrgRepo: "*", 5742 Cluster: "", 5743 Config: &prowapi.ProwJobDefault{ 5744 TenantID: "configDefault", 5745 }, 5746 }, 5747 { 5748 OrgRepo: "org", 5749 Cluster: "", 5750 Config: &prowapi.ProwJobDefault{ 5751 TenantID: "org default", 5752 }, 5753 }, 5754 { 5755 OrgRepo: "org/repo", 5756 Cluster: "", 5757 Config: &prowapi.ProwJobDefault{ 5758 TenantID: "org/repo default", 5759 }, 5760 }, 5761 }, 5762 }, 5763 }, 5764 expected: &prowapi.ProwJobDefault{ 5765 TenantID: "org/repo default", 5766 }, 5767 }, 5768 { 5769 id: "no default in job. config's default by org and org/repo, unknown repo uses org", 5770 utilityConfig: UtilityConfig{ 5771 ExtraRefs: []prowapi.Refs{ 5772 { 5773 Org: "org", 5774 Repo: "foo", 5775 }, 5776 }, 5777 }, 5778 config: &Config{ 5779 ProwConfig: ProwConfig{ 5780 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5781 { 5782 OrgRepo: "*", 5783 Cluster: "", 5784 Config: &prowapi.ProwJobDefault{ 5785 TenantID: "configDefault", 5786 }, 5787 }, 5788 { 5789 OrgRepo: "org", 5790 Cluster: "", 5791 Config: &prowapi.ProwJobDefault{ 5792 TenantID: "org default", 5793 }, 5794 }, 5795 { 5796 OrgRepo: "org/repo", 5797 Cluster: "", 5798 Config: &prowapi.ProwJobDefault{ 5799 TenantID: "org/repo default", 5800 }, 5801 }, 5802 }, 5803 }, 5804 }, 5805 expected: &prowapi.ProwJobDefault{ 5806 TenantID: "org default", 5807 }, 5808 }, 5809 { 5810 id: "no default in job. * cluster provided config's default by org and org/repo, unknown repo uses org", 5811 utilityConfig: UtilityConfig{ 5812 ExtraRefs: []prowapi.Refs{ 5813 { 5814 Org: "org", 5815 Repo: "foo", 5816 }, 5817 }, 5818 }, 5819 cluster: "default", 5820 config: &Config{ 5821 ProwConfig: ProwConfig{ 5822 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5823 { 5824 OrgRepo: "*", 5825 Cluster: "*", 5826 Config: &prowapi.ProwJobDefault{ 5827 TenantID: "configDefault", 5828 }, 5829 }, 5830 { 5831 OrgRepo: "org", 5832 Cluster: "", 5833 Config: &prowapi.ProwJobDefault{ 5834 TenantID: "org default", 5835 }, 5836 }, 5837 { 5838 OrgRepo: "org/repo", 5839 Cluster: "", 5840 Config: &prowapi.ProwJobDefault{ 5841 TenantID: "org/repo default", 5842 }, 5843 }, 5844 }, 5845 }, 5846 }, 5847 expected: &prowapi.ProwJobDefault{ 5848 TenantID: "org default", 5849 }, 5850 }, 5851 { 5852 id: "override cluster", 5853 utilityConfig: UtilityConfig{ 5854 ExtraRefs: []prowapi.Refs{ 5855 { 5856 Org: "org", 5857 Repo: "foo", 5858 }, 5859 }, 5860 }, 5861 cluster: "override", 5862 config: &Config{ 5863 ProwConfig: ProwConfig{ 5864 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5865 { 5866 OrgRepo: "*", 5867 Cluster: "*", 5868 Config: &prowapi.ProwJobDefault{ 5869 TenantID: "configDefault", 5870 }, 5871 }, 5872 { 5873 OrgRepo: "org", 5874 Cluster: "", 5875 Config: &prowapi.ProwJobDefault{ 5876 TenantID: "org default", 5877 }, 5878 }, 5879 { 5880 OrgRepo: "*", 5881 Cluster: "override", 5882 Config: &prowapi.ProwJobDefault{ 5883 TenantID: "override default", 5884 }, 5885 }, 5886 }, 5887 }, 5888 }, 5889 expected: &prowapi.ProwJobDefault{ 5890 TenantID: "override default", 5891 }, 5892 }, 5893 { 5894 id: "complicated config, but use provided config", 5895 utilityConfig: UtilityConfig{ 5896 ExtraRefs: []prowapi.Refs{ 5897 { 5898 Org: "org", 5899 Repo: "foo", 5900 }, 5901 }, 5902 }, 5903 cluster: "override", 5904 config: &Config{ 5905 ProwConfig: ProwConfig{ 5906 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5907 { 5908 OrgRepo: "*", 5909 Cluster: "*", 5910 Config: &prowapi.ProwJobDefault{ 5911 TenantID: "configDefault", 5912 }, 5913 }, 5914 { 5915 OrgRepo: "org", 5916 Cluster: "", 5917 Config: &prowapi.ProwJobDefault{ 5918 TenantID: "org default", 5919 }, 5920 }, 5921 { 5922 OrgRepo: "*", 5923 Cluster: "override", 5924 Config: &prowapi.ProwJobDefault{ 5925 TenantID: "override default", 5926 }, 5927 }, 5928 }, 5929 }, 5930 }, 5931 givenDefault: &prowapi.ProwJobDefault{ 5932 TenantID: "given default", 5933 }, 5934 expected: &prowapi.ProwJobDefault{ 5935 TenantID: "given default", 5936 }, 5937 }, 5938 } 5939 for _, tc := range testCases { 5940 t.Run(tc.id, func(t *testing.T) { 5941 c := &Config{} 5942 periodic := &Periodic{JobBase: JobBase{Cluster: tc.cluster, UtilityConfig: tc.utilityConfig, ProwJobDefault: tc.givenDefault}} 5943 c.defaultJobBase(&periodic.JobBase) 5944 setPeriodicProwJobDefaults(tc.config, periodic) 5945 if diff := cmp.Diff(periodic.ProwJobDefault, tc.expected, cmpopts.EquateEmpty()); diff != "" { 5946 t.Error(diff) 5947 } 5948 }) 5949 } 5950 } 5951 5952 // TODO(mpherman): Add more detailed unit test when there is more than 1 field in ProwJobDefaults 5953 // Need unit tests for merging more complicated defaults. 5954 func TestSetProwJobDefaults(t *testing.T) { 5955 testCases := []struct { 5956 id string 5957 repo string 5958 cluster string 5959 config *Config 5960 givenDefault *prowapi.ProwJobDefault 5961 expected *prowapi.ProwJobDefault 5962 }{ 5963 { 5964 id: "No ProwJobDefault in job or in config, expect DefaultTenantID", 5965 config: &Config{ProwConfig: ProwConfig{}}, 5966 expected: &prowapi.ProwJobDefault{TenantID: DefaultTenantID}, 5967 }, 5968 { 5969 id: "no default in job or in config's by repo config, expect default entry", 5970 config: &Config{ 5971 ProwConfig: ProwConfig{ 5972 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5973 { 5974 OrgRepo: "*", 5975 Cluster: "", 5976 Config: &prowapi.ProwJobDefault{ 5977 TenantID: "configDefault", 5978 }, 5979 }, 5980 }, 5981 }, 5982 }, 5983 expected: &prowapi.ProwJobDefault{ 5984 TenantID: "configDefault", 5985 }, 5986 }, 5987 { 5988 id: "no default in presubmit, matching by repo config, expect merged by repo config", 5989 repo: "org/repo", 5990 config: &Config{ 5991 ProwConfig: ProwConfig{ 5992 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 5993 { 5994 OrgRepo: "*", 5995 Cluster: "", 5996 Config: &prowapi.ProwJobDefault{ 5997 TenantID: "configDefault", 5998 }, 5999 }, 6000 { 6001 OrgRepo: "org/repo", 6002 Cluster: "", 6003 Config: &prowapi.ProwJobDefault{ 6004 TenantID: "org/repo default", 6005 }, 6006 }, 6007 }, 6008 }, 6009 }, 6010 expected: &prowapi.ProwJobDefault{ 6011 TenantID: "org/repo default", 6012 }, 6013 }, 6014 { 6015 id: "default in job and config's defaults, expect job's default", 6016 repo: "org/repo", 6017 givenDefault: &prowapi.ProwJobDefault{ 6018 TenantID: "given default", 6019 }, 6020 config: &Config{ 6021 ProwConfig: ProwConfig{ 6022 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 6023 { 6024 OrgRepo: "*", 6025 Cluster: "", 6026 Config: &prowapi.ProwJobDefault{ 6027 TenantID: "configDefault", 6028 }, 6029 }, 6030 { 6031 OrgRepo: "org/repo", 6032 Cluster: "", 6033 Config: &prowapi.ProwJobDefault{ 6034 TenantID: "org/repo default", 6035 }, 6036 }, 6037 }, 6038 }, 6039 }, 6040 expected: &prowapi.ProwJobDefault{ 6041 TenantID: "given default", 6042 }, 6043 }, 6044 { 6045 id: "no default in job. config's default by org, expect org's default", 6046 repo: "org/repo", 6047 config: &Config{ 6048 ProwConfig: ProwConfig{ 6049 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 6050 { 6051 OrgRepo: "*", 6052 Cluster: "", 6053 Config: &prowapi.ProwJobDefault{ 6054 TenantID: "configDefault", 6055 }, 6056 }, 6057 { 6058 OrgRepo: "org", 6059 Cluster: "", 6060 Config: &prowapi.ProwJobDefault{ 6061 TenantID: "org default", 6062 }, 6063 }, 6064 }, 6065 }, 6066 }, 6067 expected: &prowapi.ProwJobDefault{ 6068 TenantID: "org default", 6069 }, 6070 }, 6071 { 6072 id: "no default in job or in config's by repo config, expect default entry with repo provided", 6073 repo: "org/repo", 6074 config: &Config{ 6075 ProwConfig: ProwConfig{ 6076 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 6077 { 6078 OrgRepo: "*", 6079 Cluster: "", 6080 Config: &prowapi.ProwJobDefault{ 6081 TenantID: "configDefault", 6082 }, 6083 }, 6084 }, 6085 }, 6086 }, 6087 expected: &prowapi.ProwJobDefault{ 6088 TenantID: "configDefault", 6089 }, 6090 }, 6091 { 6092 id: "no default in job. config's default by org and org/repo, expect org/repo default", 6093 repo: "org/repo", 6094 config: &Config{ 6095 ProwConfig: ProwConfig{ 6096 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 6097 { 6098 OrgRepo: "*", 6099 Cluster: "", 6100 Config: &prowapi.ProwJobDefault{ 6101 TenantID: "configDefault", 6102 }, 6103 }, 6104 { 6105 OrgRepo: "org", 6106 Cluster: "", 6107 Config: &prowapi.ProwJobDefault{ 6108 TenantID: "org default", 6109 }, 6110 }, 6111 { 6112 OrgRepo: "org/repo", 6113 Cluster: "", 6114 Config: &prowapi.ProwJobDefault{ 6115 TenantID: "org/repo default", 6116 }, 6117 }, 6118 }, 6119 }, 6120 }, 6121 expected: &prowapi.ProwJobDefault{ 6122 TenantID: "org/repo default", 6123 }, 6124 }, 6125 { 6126 id: "no default in job. config's default by org and org/repo, unknown repo uses org", 6127 repo: "org/foo", 6128 config: &Config{ 6129 ProwConfig: ProwConfig{ 6130 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 6131 { 6132 OrgRepo: "*", 6133 Cluster: "", 6134 Config: &prowapi.ProwJobDefault{ 6135 TenantID: "configDefault", 6136 }, 6137 }, 6138 { 6139 OrgRepo: "org", 6140 Cluster: "", 6141 Config: &prowapi.ProwJobDefault{ 6142 TenantID: "org default", 6143 }, 6144 }, 6145 { 6146 OrgRepo: "org/repo", 6147 Cluster: "", 6148 Config: &prowapi.ProwJobDefault{ 6149 TenantID: "org/repo default", 6150 }, 6151 }, 6152 }, 6153 }, 6154 }, 6155 expected: &prowapi.ProwJobDefault{ 6156 TenantID: "org default", 6157 }, 6158 }, 6159 { 6160 id: "no default in job. * cluster provided config's default by org and org/repo, unknown repo uses org", 6161 repo: "org/foo", 6162 cluster: "default", 6163 config: &Config{ 6164 ProwConfig: ProwConfig{ 6165 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 6166 { 6167 OrgRepo: "*", 6168 Cluster: "*", 6169 Config: &prowapi.ProwJobDefault{ 6170 TenantID: "configDefault", 6171 }, 6172 }, 6173 { 6174 OrgRepo: "org", 6175 Cluster: "", 6176 Config: &prowapi.ProwJobDefault{ 6177 TenantID: "org default", 6178 }, 6179 }, 6180 { 6181 OrgRepo: "org/repo", 6182 Cluster: "", 6183 Config: &prowapi.ProwJobDefault{ 6184 TenantID: "org/repo default", 6185 }, 6186 }, 6187 }, 6188 }, 6189 }, 6190 expected: &prowapi.ProwJobDefault{ 6191 TenantID: "org default", 6192 }, 6193 }, 6194 { 6195 id: "override cluster", 6196 repo: "org/foo", 6197 cluster: "override", 6198 config: &Config{ 6199 ProwConfig: ProwConfig{ 6200 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 6201 { 6202 OrgRepo: "*", 6203 Cluster: "*", 6204 Config: &prowapi.ProwJobDefault{ 6205 TenantID: "configDefault", 6206 }, 6207 }, 6208 { 6209 OrgRepo: "org", 6210 Cluster: "", 6211 Config: &prowapi.ProwJobDefault{ 6212 TenantID: "org default", 6213 }, 6214 }, 6215 { 6216 OrgRepo: "*", 6217 Cluster: "override", 6218 Config: &prowapi.ProwJobDefault{ 6219 TenantID: "override default", 6220 }, 6221 }, 6222 }, 6223 }, 6224 }, 6225 expected: &prowapi.ProwJobDefault{ 6226 TenantID: "override default", 6227 }, 6228 }, 6229 { 6230 id: "complicated config, but use provided config", 6231 repo: "org/foo", 6232 cluster: "override", 6233 config: &Config{ 6234 ProwConfig: ProwConfig{ 6235 ProwJobDefaultEntries: []*ProwJobDefaultEntry{ 6236 { 6237 OrgRepo: "*", 6238 Cluster: "*", 6239 Config: &prowapi.ProwJobDefault{ 6240 TenantID: "configDefault", 6241 }, 6242 }, 6243 { 6244 OrgRepo: "org", 6245 Cluster: "", 6246 Config: &prowapi.ProwJobDefault{ 6247 TenantID: "org default", 6248 }, 6249 }, 6250 { 6251 OrgRepo: "*", 6252 Cluster: "override", 6253 Config: &prowapi.ProwJobDefault{ 6254 TenantID: "override default", 6255 }, 6256 }, 6257 }, 6258 }, 6259 }, 6260 givenDefault: &prowapi.ProwJobDefault{ 6261 TenantID: "given default", 6262 }, 6263 expected: &prowapi.ProwJobDefault{ 6264 TenantID: "given default", 6265 }, 6266 }, 6267 } 6268 for _, tc := range testCases { 6269 t.Run(tc.id, func(t *testing.T) { 6270 c := &Config{} 6271 jb := &JobBase{Cluster: tc.cluster, ProwJobDefault: tc.givenDefault} 6272 c.defaultJobBase(jb) 6273 presubmit := &Presubmit{JobBase: *jb} 6274 postsubmit := &Postsubmit{JobBase: *jb} 6275 6276 setPresubmitProwJobDefaults(tc.config, presubmit, tc.repo) 6277 if diff := cmp.Diff(presubmit.ProwJobDefault, tc.expected, cmpopts.EquateEmpty()); diff != "" { 6278 t.Errorf("presubmit: %s", diff) 6279 } 6280 6281 setPostsubmitProwJobDefaults(tc.config, postsubmit, tc.repo) 6282 if diff := cmp.Diff(postsubmit.ProwJobDefault, tc.expected, cmpopts.EquateEmpty()); diff != "" { 6283 t.Errorf("postsubmit: %s", diff) 6284 } 6285 }) 6286 } 6287 } 6288 6289 func TestSetDecorationDefaults(t *testing.T) { 6290 yes := true 6291 no := false 6292 6293 testCases := []struct { 6294 id string 6295 repo string 6296 cluster string 6297 config *Config 6298 utilityConfig UtilityConfig 6299 expected *prowapi.DecorationConfig 6300 }{ 6301 { 6302 id: "no dc in presubmit or in plank's config, expect no changes", 6303 utilityConfig: UtilityConfig{Decorate: &yes}, 6304 config: &Config{ProwConfig: ProwConfig{}}, 6305 expected: &prowapi.DecorationConfig{}, 6306 }, 6307 { 6308 id: "no dc in presubmit or in plank's by repo config, expect plank's defaults", 6309 utilityConfig: UtilityConfig{Decorate: &yes}, 6310 config: &Config{ 6311 ProwConfig: ProwConfig{ 6312 Plank: Plank{ 6313 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6314 { 6315 OrgRepo: "*", 6316 Cluster: "", 6317 Config: &prowapi.DecorationConfig{ 6318 UtilityImages: &prowapi.UtilityImages{ 6319 CloneRefs: "clonerefs:test", 6320 InitUpload: "initupload:test", 6321 Entrypoint: "entrypoint:test", 6322 Sidecar: "sidecar:test", 6323 }, 6324 GCSConfiguration: &prowapi.GCSConfiguration{ 6325 Bucket: "test-bucket", 6326 PathStrategy: "single", 6327 DefaultOrg: "org", 6328 DefaultRepo: "repo", 6329 }, 6330 GCSCredentialsSecret: pStr("credentials-gcs"), 6331 }, 6332 }, 6333 }, 6334 }, 6335 }, 6336 }, 6337 expected: &prowapi.DecorationConfig{ 6338 UtilityImages: &prowapi.UtilityImages{ 6339 CloneRefs: "clonerefs:test", 6340 InitUpload: "initupload:test", 6341 Entrypoint: "entrypoint:test", 6342 Sidecar: "sidecar:test", 6343 }, 6344 GCSConfiguration: &prowapi.GCSConfiguration{ 6345 Bucket: "test-bucket", 6346 PathStrategy: "single", 6347 DefaultOrg: "org", 6348 DefaultRepo: "repo", 6349 }, 6350 GCSCredentialsSecret: pStr("credentials-gcs"), 6351 }, 6352 }, 6353 { 6354 id: "no dc in presubmit, part of plank's by repo config, expect merged by repo config and defaults", 6355 utilityConfig: UtilityConfig{Decorate: &yes}, 6356 repo: "org/repo", 6357 config: &Config{ 6358 ProwConfig: ProwConfig{ 6359 Plank: Plank{ 6360 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6361 { 6362 OrgRepo: "*", 6363 Cluster: "", 6364 Config: &prowapi.DecorationConfig{ 6365 UtilityImages: &prowapi.UtilityImages{ 6366 CloneRefs: "clonerefs:test", 6367 InitUpload: "initupload:test", 6368 Entrypoint: "entrypoint:test", 6369 Sidecar: "sidecar:test", 6370 }, 6371 GCSConfiguration: &prowapi.GCSConfiguration{ 6372 Bucket: "test-bucket", 6373 PathStrategy: "single", 6374 DefaultOrg: "org", 6375 DefaultRepo: "repo", 6376 }, 6377 GCSCredentialsSecret: pStr("credentials-gcs"), 6378 }, 6379 }, 6380 { 6381 OrgRepo: "org/repo", 6382 Cluster: "", 6383 Config: &prowapi.DecorationConfig{ 6384 GCSConfiguration: &prowapi.GCSConfiguration{ 6385 Bucket: "test-bucket-by-repo", 6386 PathStrategy: "single-by-repo", 6387 DefaultOrg: "org-by-repo", 6388 DefaultRepo: "repo-by-repo", 6389 }, 6390 }, 6391 }, 6392 }, 6393 }, 6394 }, 6395 }, 6396 expected: &prowapi.DecorationConfig{ 6397 UtilityImages: &prowapi.UtilityImages{ 6398 CloneRefs: "clonerefs:test", 6399 InitUpload: "initupload:test", 6400 Entrypoint: "entrypoint:test", 6401 Sidecar: "sidecar:test", 6402 }, 6403 GCSConfiguration: &prowapi.GCSConfiguration{ 6404 Bucket: "test-bucket-by-repo", 6405 PathStrategy: "single-by-repo", 6406 DefaultOrg: "org-by-repo", 6407 DefaultRepo: "repo-by-repo", 6408 }, 6409 GCSCredentialsSecret: pStr("credentials-gcs"), 6410 }, 6411 }, 6412 { 6413 id: "dc in presubmit and plank's defaults, expect presubmit's dc", 6414 repo: "org/repo", 6415 utilityConfig: UtilityConfig{ 6416 Decorate: &yes, 6417 DecorationConfig: &prowapi.DecorationConfig{ 6418 UtilityImages: &prowapi.UtilityImages{ 6419 CloneRefs: "clonerefs:test-from-ps", 6420 InitUpload: "initupload:test-from-ps", 6421 Entrypoint: "entrypoint:test-from-ps", 6422 Sidecar: "sidecar:test-from-ps", 6423 }, 6424 GCSConfiguration: &prowapi.GCSConfiguration{ 6425 Bucket: "test-bucket-from-ps", 6426 PathStrategy: "single-from-ps", 6427 DefaultOrg: "org-from-ps", 6428 DefaultRepo: "repo-from-ps", 6429 }, 6430 GCSCredentialsSecret: pStr("credentials-gcs-from-ps"), 6431 }, 6432 }, 6433 config: &Config{ 6434 ProwConfig: ProwConfig{ 6435 Plank: Plank{ 6436 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6437 { 6438 OrgRepo: "*", 6439 Cluster: "", 6440 Config: &prowapi.DecorationConfig{ 6441 UtilityImages: &prowapi.UtilityImages{ 6442 CloneRefs: "clonerefs:test", 6443 InitUpload: "initupload:test", 6444 Entrypoint: "entrypoint:test", 6445 Sidecar: "sidecar:test", 6446 }, 6447 GCSConfiguration: &prowapi.GCSConfiguration{ 6448 Bucket: "test-bucket", 6449 PathStrategy: "single", 6450 DefaultOrg: "org", 6451 DefaultRepo: "repo", 6452 }, 6453 GCSCredentialsSecret: pStr("credentials-gcs"), 6454 }, 6455 }, 6456 }, 6457 }, 6458 }, 6459 }, 6460 expected: &prowapi.DecorationConfig{ 6461 UtilityImages: &prowapi.UtilityImages{ 6462 CloneRefs: "clonerefs:test-from-ps", 6463 InitUpload: "initupload:test-from-ps", 6464 Entrypoint: "entrypoint:test-from-ps", 6465 Sidecar: "sidecar:test-from-ps", 6466 }, 6467 GCSConfiguration: &prowapi.GCSConfiguration{ 6468 Bucket: "test-bucket-from-ps", 6469 PathStrategy: "single-from-ps", 6470 DefaultOrg: "org-from-ps", 6471 DefaultRepo: "repo-from-ps", 6472 }, 6473 GCSCredentialsSecret: pStr("credentials-gcs-from-ps"), 6474 }, 6475 }, 6476 { 6477 id: "dc in presubmit, plank's by repo config and defaults, expected presubmit's dc", 6478 repo: "org/repo", 6479 utilityConfig: UtilityConfig{ 6480 Decorate: &yes, 6481 DecorationConfig: &prowapi.DecorationConfig{ 6482 UtilityImages: &prowapi.UtilityImages{ 6483 CloneRefs: "clonerefs:test-from-ps", 6484 InitUpload: "initupload:test-from-ps", 6485 Entrypoint: "entrypoint:test-from-ps", 6486 Sidecar: "sidecar:test-from-ps", 6487 }, 6488 GCSConfiguration: &prowapi.GCSConfiguration{ 6489 Bucket: "test-bucket-from-ps", 6490 PathStrategy: "single-from-ps", 6491 DefaultOrg: "org-from-ps", 6492 DefaultRepo: "repo-from-ps", 6493 }, 6494 GCSCredentialsSecret: pStr("credentials-gcs-from-ps"), 6495 }, 6496 }, 6497 config: &Config{ 6498 ProwConfig: ProwConfig{ 6499 Plank: Plank{ 6500 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6501 { 6502 OrgRepo: "*", 6503 Cluster: "", 6504 Config: &prowapi.DecorationConfig{ 6505 UtilityImages: &prowapi.UtilityImages{ 6506 CloneRefs: "clonerefs:test", 6507 InitUpload: "initupload:test", 6508 Entrypoint: "entrypoint:test", 6509 Sidecar: "sidecar:test", 6510 }, 6511 GCSConfiguration: &prowapi.GCSConfiguration{ 6512 Bucket: "test-bucket", 6513 PathStrategy: "single", 6514 DefaultOrg: "org", 6515 DefaultRepo: "repo", 6516 }, 6517 GCSCredentialsSecret: pStr("credentials-gcs"), 6518 }, 6519 }, 6520 { 6521 OrgRepo: "org/repo", 6522 Cluster: "", 6523 Config: &prowapi.DecorationConfig{ 6524 UtilityImages: &prowapi.UtilityImages{ 6525 CloneRefs: "clonerefs:test-by-repo", 6526 InitUpload: "initupload:test-by-repo", 6527 Entrypoint: "entrypoint:test-by-repo", 6528 Sidecar: "sidecar:test-by-repo", 6529 }, 6530 GCSConfiguration: &prowapi.GCSConfiguration{ 6531 Bucket: "test-bucket-by-repo", 6532 PathStrategy: "single", 6533 DefaultOrg: "org-test", 6534 DefaultRepo: "repo-test", 6535 }, 6536 GCSCredentialsSecret: pStr("credentials-gcs"), 6537 }, 6538 }, 6539 }, 6540 }, 6541 }, 6542 }, 6543 expected: &prowapi.DecorationConfig{ 6544 UtilityImages: &prowapi.UtilityImages{ 6545 CloneRefs: "clonerefs:test-from-ps", 6546 InitUpload: "initupload:test-from-ps", 6547 Entrypoint: "entrypoint:test-from-ps", 6548 Sidecar: "sidecar:test-from-ps", 6549 }, 6550 GCSConfiguration: &prowapi.GCSConfiguration{ 6551 Bucket: "test-bucket-from-ps", 6552 PathStrategy: "single-from-ps", 6553 DefaultOrg: "org-from-ps", 6554 DefaultRepo: "repo-from-ps", 6555 }, 6556 GCSCredentialsSecret: pStr("credentials-gcs-from-ps"), 6557 }, 6558 }, 6559 { 6560 id: "no dc in presubmit, dc in plank's by repo config and defaults, expect by repo config's dc", 6561 repo: "org/repo", 6562 utilityConfig: UtilityConfig{Decorate: &yes}, 6563 config: &Config{ 6564 ProwConfig: ProwConfig{ 6565 Plank: Plank{ 6566 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6567 { 6568 OrgRepo: "*", 6569 Cluster: "", 6570 Config: &prowapi.DecorationConfig{ 6571 UtilityImages: &prowapi.UtilityImages{ 6572 CloneRefs: "clonerefs:test", 6573 InitUpload: "initupload:test", 6574 Entrypoint: "entrypoint:test", 6575 Sidecar: "sidecar:test", 6576 }, 6577 GCSConfiguration: &prowapi.GCSConfiguration{ 6578 Bucket: "test-bucket", 6579 PathStrategy: "single", 6580 DefaultOrg: "org", 6581 DefaultRepo: "repo", 6582 }, 6583 GCSCredentialsSecret: pStr("credentials-gcs"), 6584 }, 6585 }, 6586 { 6587 OrgRepo: "org/repo", 6588 Cluster: "", 6589 Config: &prowapi.DecorationConfig{ 6590 UtilityImages: &prowapi.UtilityImages{ 6591 CloneRefs: "clonerefs:test-by-repo", 6592 InitUpload: "initupload:test-by-repo", 6593 Entrypoint: "entrypoint:test-by-repo", 6594 Sidecar: "sidecar:test-by-repo", 6595 }, 6596 GCSConfiguration: &prowapi.GCSConfiguration{ 6597 Bucket: "test-bucket-by-repo", 6598 PathStrategy: "single-by-repo", 6599 DefaultOrg: "org-by-repo", 6600 DefaultRepo: "repo-by-repo", 6601 }, 6602 GCSCredentialsSecret: pStr("credentials-gcs-by-repo"), 6603 }, 6604 }, 6605 }, 6606 }, 6607 }, 6608 }, 6609 expected: &prowapi.DecorationConfig{ 6610 UtilityImages: &prowapi.UtilityImages{ 6611 CloneRefs: "clonerefs:test-by-repo", 6612 InitUpload: "initupload:test-by-repo", 6613 Entrypoint: "entrypoint:test-by-repo", 6614 Sidecar: "sidecar:test-by-repo", 6615 }, 6616 GCSConfiguration: &prowapi.GCSConfiguration{ 6617 Bucket: "test-bucket-by-repo", 6618 PathStrategy: "single-by-repo", 6619 DefaultOrg: "org-by-repo", 6620 DefaultRepo: "repo-by-repo", 6621 }, 6622 GCSCredentialsSecret: pStr("credentials-gcs-by-repo"), 6623 }, 6624 }, 6625 { 6626 id: "no dc in presubmit, dc in plank's by repo config and defaults, expect by org config's dc", 6627 repo: "org/repo", 6628 utilityConfig: UtilityConfig{Decorate: &yes}, 6629 config: &Config{ 6630 ProwConfig: ProwConfig{ 6631 Plank: Plank{ 6632 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6633 { 6634 OrgRepo: "*", 6635 Cluster: "", 6636 Config: &prowapi.DecorationConfig{ 6637 UtilityImages: &prowapi.UtilityImages{ 6638 CloneRefs: "clonerefs:test", 6639 InitUpload: "initupload:test", 6640 Entrypoint: "entrypoint:test", 6641 Sidecar: "sidecar:test", 6642 }, 6643 GCSConfiguration: &prowapi.GCSConfiguration{ 6644 Bucket: "test-bucket", 6645 PathStrategy: "single", 6646 DefaultOrg: "org", 6647 DefaultRepo: "repo", 6648 }, 6649 GCSCredentialsSecret: pStr("credentials-gcs"), 6650 }, 6651 }, 6652 { 6653 OrgRepo: "org", 6654 Cluster: "", 6655 Config: &prowapi.DecorationConfig{ 6656 UtilityImages: &prowapi.UtilityImages{ 6657 CloneRefs: "clonerefs:test-by-org", 6658 InitUpload: "initupload:test-by-org", 6659 Entrypoint: "entrypoint:test-by-org", 6660 Sidecar: "sidecar:test-by-org", 6661 }, 6662 GCSConfiguration: &prowapi.GCSConfiguration{ 6663 Bucket: "test-bucket-by-org", 6664 PathStrategy: "single-by-org", 6665 DefaultOrg: "org-by-org", 6666 DefaultRepo: "repo-by-org", 6667 }, 6668 GCSCredentialsSecret: pStr("credentials-gcs-by-org"), 6669 }, 6670 }, 6671 }, 6672 }, 6673 }, 6674 }, 6675 expected: &prowapi.DecorationConfig{ 6676 UtilityImages: &prowapi.UtilityImages{ 6677 CloneRefs: "clonerefs:test-by-org", 6678 InitUpload: "initupload:test-by-org", 6679 Entrypoint: "entrypoint:test-by-org", 6680 Sidecar: "sidecar:test-by-org", 6681 }, 6682 GCSConfiguration: &prowapi.GCSConfiguration{ 6683 Bucket: "test-bucket-by-org", 6684 PathStrategy: "single-by-org", 6685 DefaultOrg: "org-by-org", 6686 DefaultRepo: "repo-by-org", 6687 }, 6688 GCSCredentialsSecret: pStr("credentials-gcs-by-org"), 6689 }, 6690 }, 6691 { 6692 id: "no dc in presubmit, dc in plank's by repo config and defaults, expect by * config's dc", 6693 repo: "org/repo", 6694 utilityConfig: UtilityConfig{Decorate: &yes}, 6695 config: &Config{ 6696 ProwConfig: ProwConfig{ 6697 Plank: Plank{ 6698 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6699 { 6700 OrgRepo: "*", 6701 Cluster: "", 6702 Config: &prowapi.DecorationConfig{ 6703 UtilityImages: &prowapi.UtilityImages{ 6704 CloneRefs: "clonerefs:test-by-*", 6705 InitUpload: "initupload:test-by-*", 6706 Entrypoint: "entrypoint:test-by-*", 6707 Sidecar: "sidecar:test-by-*", 6708 }, 6709 GCSConfiguration: &prowapi.GCSConfiguration{ 6710 Bucket: "test-bucket-by-*", 6711 PathStrategy: "single-by-*", 6712 DefaultOrg: "org-by-*", 6713 DefaultRepo: "repo-by-*", 6714 }, 6715 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 6716 }, 6717 }, 6718 }, 6719 }, 6720 }, 6721 }, 6722 expected: &prowapi.DecorationConfig{ 6723 UtilityImages: &prowapi.UtilityImages{ 6724 CloneRefs: "clonerefs:test-by-*", 6725 InitUpload: "initupload:test-by-*", 6726 Entrypoint: "entrypoint:test-by-*", 6727 Sidecar: "sidecar:test-by-*", 6728 }, 6729 GCSConfiguration: &prowapi.GCSConfiguration{ 6730 Bucket: "test-bucket-by-*", 6731 PathStrategy: "single-by-*", 6732 DefaultOrg: "org-by-*", 6733 DefaultRepo: "repo-by-*", 6734 }, 6735 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 6736 }, 6737 }, 6738 6739 { 6740 id: "no dc in presubmit, dc in plank's by repo config org and org/repo co-exists, expect by org/repo config's dc", 6741 repo: "org/repo", 6742 utilityConfig: UtilityConfig{Decorate: &yes}, 6743 config: &Config{ 6744 ProwConfig: ProwConfig{ 6745 Plank: Plank{ 6746 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6747 { 6748 OrgRepo: "*", 6749 Cluster: "", 6750 Config: &prowapi.DecorationConfig{ 6751 UtilityImages: &prowapi.UtilityImages{ 6752 CloneRefs: "clonerefs:test-by-*", 6753 InitUpload: "initupload:test-by-*", 6754 Entrypoint: "entrypoint:test-by-*", 6755 Sidecar: "sidecar:test-by-*", 6756 }, 6757 GCSConfiguration: &prowapi.GCSConfiguration{ 6758 Bucket: "test-bucket-by-*", 6759 PathStrategy: "single-by-*", 6760 DefaultOrg: "org-by-*", 6761 DefaultRepo: "repo-by-*", 6762 }, 6763 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 6764 }, 6765 }, 6766 { 6767 OrgRepo: "org", 6768 Cluster: "", 6769 Config: &prowapi.DecorationConfig{ 6770 UtilityImages: &prowapi.UtilityImages{ 6771 CloneRefs: "clonerefs:test-by-org", 6772 InitUpload: "initupload:test-by-org", 6773 Entrypoint: "entrypoint:test-by-org", 6774 Sidecar: "sidecar:test-by-org", 6775 }, 6776 GCSConfiguration: &prowapi.GCSConfiguration{ 6777 Bucket: "test-bucket-by-org", 6778 PathStrategy: "single-by-org", 6779 DefaultOrg: "org-by-org", 6780 DefaultRepo: "repo-by-org", 6781 }, 6782 GCSCredentialsSecret: pStr("credentials-gcs-by-org"), 6783 }, 6784 }, 6785 { 6786 OrgRepo: "org/repo", 6787 Cluster: "", 6788 Config: &prowapi.DecorationConfig{ 6789 UtilityImages: &prowapi.UtilityImages{ 6790 CloneRefs: "clonerefs:test-by-org-repo", 6791 InitUpload: "initupload:test-by-org-repo", 6792 Entrypoint: "entrypoint:test-by-org-repo", 6793 Sidecar: "sidecar:test-by-org-repo", 6794 }, 6795 GCSConfiguration: &prowapi.GCSConfiguration{ 6796 Bucket: "test-bucket-by-org-repo", 6797 PathStrategy: "single-by-org-repo", 6798 DefaultOrg: "org-by-org-repo", 6799 DefaultRepo: "repo-by-org-repo", 6800 }, 6801 GCSCredentialsSecret: pStr("credentials-gcs-by-org-repo"), 6802 }, 6803 }, 6804 }, 6805 }, 6806 }, 6807 }, 6808 expected: &prowapi.DecorationConfig{ 6809 UtilityImages: &prowapi.UtilityImages{ 6810 CloneRefs: "clonerefs:test-by-org-repo", 6811 InitUpload: "initupload:test-by-org-repo", 6812 Entrypoint: "entrypoint:test-by-org-repo", 6813 Sidecar: "sidecar:test-by-org-repo", 6814 }, 6815 GCSConfiguration: &prowapi.GCSConfiguration{ 6816 Bucket: "test-bucket-by-org-repo", 6817 PathStrategy: "single-by-org-repo", 6818 DefaultOrg: "org-by-org-repo", 6819 DefaultRepo: "repo-by-org-repo", 6820 }, 6821 GCSCredentialsSecret: pStr("credentials-gcs-by-org-repo"), 6822 }, 6823 }, 6824 6825 { 6826 id: "no dc in presubmit, dc in plank's by repo config with org and * to co-exists, expect by 'org' config's dc", 6827 repo: "org/repo", 6828 utilityConfig: UtilityConfig{Decorate: &yes}, 6829 config: &Config{ 6830 ProwConfig: ProwConfig{ 6831 Plank: Plank{ 6832 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6833 { 6834 OrgRepo: "*", 6835 Cluster: "", 6836 Config: &prowapi.DecorationConfig{ 6837 UtilityImages: &prowapi.UtilityImages{ 6838 CloneRefs: "clonerefs:test-by-*", 6839 InitUpload: "initupload:test-by-*", 6840 Entrypoint: "entrypoint:test-by-*", 6841 Sidecar: "sidecar:test-by-*", 6842 }, 6843 GCSConfiguration: &prowapi.GCSConfiguration{ 6844 Bucket: "test-bucket-by-*", 6845 PathStrategy: "single-by-*", 6846 DefaultOrg: "org-by-*", 6847 DefaultRepo: "repo-by-*", 6848 }, 6849 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 6850 }, 6851 }, 6852 { 6853 OrgRepo: "org", 6854 Cluster: "", 6855 Config: &prowapi.DecorationConfig{ 6856 UtilityImages: &prowapi.UtilityImages{ 6857 CloneRefs: "clonerefs:test-by-org", 6858 InitUpload: "initupload:test-by-org", 6859 Entrypoint: "entrypoint:test-by-org", 6860 Sidecar: "sidecar:test-by-org", 6861 }, 6862 GCSConfiguration: &prowapi.GCSConfiguration{ 6863 Bucket: "test-bucket-by-org", 6864 PathStrategy: "single-by-org", 6865 DefaultOrg: "org-by-org", 6866 DefaultRepo: "repo-by-org", 6867 }, 6868 GCSCredentialsSecret: pStr("credentials-gcs-by-org"), 6869 }, 6870 }, 6871 }, 6872 }, 6873 }, 6874 }, 6875 expected: &prowapi.DecorationConfig{ 6876 UtilityImages: &prowapi.UtilityImages{ 6877 CloneRefs: "clonerefs:test-by-org", 6878 InitUpload: "initupload:test-by-org", 6879 Entrypoint: "entrypoint:test-by-org", 6880 Sidecar: "sidecar:test-by-org", 6881 }, 6882 GCSConfiguration: &prowapi.GCSConfiguration{ 6883 Bucket: "test-bucket-by-org", 6884 PathStrategy: "single-by-org", 6885 DefaultOrg: "org-by-org", 6886 DefaultRepo: "repo-by-org", 6887 }, 6888 GCSCredentialsSecret: pStr("credentials-gcs-by-org"), 6889 }, 6890 }, 6891 { 6892 id: "decorate_all_jobs set, no dc in presubmit or in plank's by repo config, expect plank's defaults", 6893 config: &Config{ 6894 JobConfig: JobConfig{ 6895 DecorateAllJobs: true, 6896 }, 6897 ProwConfig: ProwConfig{ 6898 Plank: Plank{ 6899 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6900 { 6901 OrgRepo: "*", 6902 Cluster: "", 6903 Config: &prowapi.DecorationConfig{ 6904 UtilityImages: &prowapi.UtilityImages{ 6905 CloneRefs: "clonerefs:test", 6906 InitUpload: "initupload:test", 6907 Entrypoint: "entrypoint:test", 6908 Sidecar: "sidecar:test", 6909 }, 6910 GCSConfiguration: &prowapi.GCSConfiguration{ 6911 Bucket: "test-bucket", 6912 PathStrategy: "single", 6913 DefaultOrg: "org", 6914 DefaultRepo: "repo", 6915 }, 6916 GCSCredentialsSecret: pStr("credentials-gcs"), 6917 }, 6918 }, 6919 }, 6920 }, 6921 }, 6922 }, 6923 expected: &prowapi.DecorationConfig{ 6924 UtilityImages: &prowapi.UtilityImages{ 6925 CloneRefs: "clonerefs:test", 6926 InitUpload: "initupload:test", 6927 Entrypoint: "entrypoint:test", 6928 Sidecar: "sidecar:test", 6929 }, 6930 GCSConfiguration: &prowapi.GCSConfiguration{ 6931 Bucket: "test-bucket", 6932 PathStrategy: "single", 6933 DefaultOrg: "org", 6934 DefaultRepo: "repo", 6935 }, 6936 GCSCredentialsSecret: pStr("credentials-gcs"), 6937 }, 6938 }, 6939 { 6940 id: "opt out of decorate_all_jobs by setting decorated to false", 6941 utilityConfig: UtilityConfig{Decorate: &no}, 6942 config: &Config{ 6943 JobConfig: JobConfig{ 6944 DecorateAllJobs: true, 6945 }, 6946 ProwConfig: ProwConfig{ 6947 Plank: Plank{ 6948 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 6949 { 6950 OrgRepo: "*", 6951 Cluster: "", 6952 Config: &prowapi.DecorationConfig{ 6953 UtilityImages: &prowapi.UtilityImages{ 6954 CloneRefs: "clonerefs:test", 6955 InitUpload: "initupload:test", 6956 Entrypoint: "entrypoint:test", 6957 Sidecar: "sidecar:test", 6958 }, 6959 GCSConfiguration: &prowapi.GCSConfiguration{ 6960 Bucket: "test-bucket", 6961 PathStrategy: "single", 6962 DefaultOrg: "org", 6963 DefaultRepo: "repo", 6964 }, 6965 GCSCredentialsSecret: pStr("credentials-gcs"), 6966 }, 6967 }, 6968 }, 6969 }, 6970 }, 6971 }, 6972 }, 6973 { 6974 id: "unrecognized org, no cluster => use global + default cluster configs", 6975 config: complexConfig(), 6976 expected: &prowapi.DecorationConfig{ 6977 UtilityImages: &prowapi.UtilityImages{ 6978 CloneRefs: "clonerefs:global", 6979 InitUpload: "initupload:global", 6980 Entrypoint: "entrypoint:global", 6981 Sidecar: "sidecar:global", 6982 }, 6983 GCSConfiguration: &prowapi.GCSConfiguration{ 6984 Bucket: "global", 6985 PathStrategy: "explicit", 6986 }, 6987 GCSCredentialsSecret: pStr("default-cluster-uses-secret"), 6988 }, 6989 }, 6990 { 6991 id: "unrecognized repo and explicit 'default' cluster => use global + org + default cluster configs", 6992 config: complexConfig(), 6993 cluster: "default", 6994 repo: "org/foo", 6995 expected: &prowapi.DecorationConfig{ 6996 UtilityImages: &prowapi.UtilityImages{ 6997 CloneRefs: "clonerefs:global", 6998 InitUpload: "initupload:global", 6999 Entrypoint: "entrypoint:global", 7000 Sidecar: "sidecar:global", 7001 }, 7002 GCSConfiguration: &prowapi.GCSConfiguration{ 7003 Bucket: "org-specific", 7004 PathStrategy: "explicit", 7005 }, 7006 GCSCredentialsSecret: pStr("default-cluster-uses-secret"), 7007 }, 7008 }, 7009 { 7010 id: "recognized repo and explicit 'trusted' cluster => use global + org + repo + trusted cluster configs", 7011 config: complexConfig(), 7012 cluster: "trusted", 7013 repo: "org/repo", 7014 expected: &prowapi.DecorationConfig{ 7015 UtilityImages: &prowapi.UtilityImages{ 7016 CloneRefs: "clonerefs:global", 7017 InitUpload: "initupload:global", 7018 Entrypoint: "entrypoint:global", 7019 Sidecar: "sidecar:global", 7020 }, 7021 GCSConfiguration: &prowapi.GCSConfiguration{ 7022 Bucket: "repo-specific", 7023 PathStrategy: "explicit", 7024 }, 7025 DefaultServiceAccountName: pStr("trusted-cluster-uses-SA"), 7026 }, 7027 }, 7028 { 7029 id: "override org and in trusted cluster => use global + trusted cluster + override configs", 7030 config: complexConfig(), 7031 cluster: "trusted", 7032 repo: "override/foo", 7033 expected: &prowapi.DecorationConfig{ 7034 UtilityImages: &prowapi.UtilityImages{ 7035 CloneRefs: "clonerefs:override", 7036 InitUpload: "initupload:global", 7037 Entrypoint: "entrypoint:global", 7038 Sidecar: "sidecar:global", 7039 }, 7040 GCSConfiguration: &prowapi.GCSConfiguration{ 7041 Bucket: "global", 7042 PathStrategy: "explicit", 7043 }, 7044 DefaultServiceAccountName: pStr(""), 7045 GCSCredentialsSecret: pStr("trusted-cluster-override-uses-secret"), 7046 }, 7047 }, 7048 { 7049 id: "override org and in default cluster => use global + default cluster configs", 7050 config: complexConfig(), 7051 cluster: "default", 7052 repo: "override/foo", 7053 expected: &prowapi.DecorationConfig{ 7054 UtilityImages: &prowapi.UtilityImages{ 7055 CloneRefs: "clonerefs:global", 7056 InitUpload: "initupload:global", 7057 Entrypoint: "entrypoint:global", 7058 Sidecar: "sidecar:global", 7059 }, 7060 GCSConfiguration: &prowapi.GCSConfiguration{ 7061 Bucket: "global", 7062 PathStrategy: "explicit", 7063 }, 7064 GCSCredentialsSecret: pStr("default-cluster-uses-secret"), 7065 }, 7066 }, 7067 } 7068 7069 for _, tc := range testCases { 7070 t.Run(tc.id, func(t *testing.T) { 7071 c := &Config{} 7072 jb := &JobBase{Cluster: tc.cluster, UtilityConfig: tc.utilityConfig} 7073 c.defaultJobBase(jb) 7074 presubmit := &Presubmit{JobBase: *jb} 7075 postsubmit := &Postsubmit{JobBase: *jb} 7076 7077 setPresubmitDecorationDefaults(tc.config, presubmit, tc.repo) 7078 if diff := cmp.Diff(presubmit.DecorationConfig, tc.expected, cmpopts.EquateEmpty()); diff != "" { 7079 t.Errorf("presubmit: %s", diff) 7080 } 7081 7082 setPostsubmitDecorationDefaults(tc.config, postsubmit, tc.repo) 7083 if diff := cmp.Diff(postsubmit.DecorationConfig, tc.expected, cmpopts.EquateEmpty()); diff != "" { 7084 t.Errorf("postsubmit: %s", diff) 7085 } 7086 }) 7087 } 7088 } 7089 7090 func TestSetPeriodicDecorationDefaults(t *testing.T) { 7091 yes := true 7092 no := false 7093 testCases := []struct { 7094 id string 7095 cluster string 7096 config *Config 7097 utilityConfig UtilityConfig 7098 expected *prowapi.DecorationConfig 7099 }{ 7100 { 7101 id: "extraRefs[0] not defined, changes from defaultDecorationConfigs[*] expected", 7102 config: &Config{ 7103 ProwConfig: ProwConfig{ 7104 Plank: Plank{ 7105 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 7106 { 7107 OrgRepo: "*", 7108 Cluster: "*", 7109 Config: &prowapi.DecorationConfig{ 7110 UtilityImages: &prowapi.UtilityImages{ 7111 CloneRefs: "clonerefs:test-by-*", 7112 InitUpload: "initupload:test-by-*", 7113 Entrypoint: "entrypoint:test-by-*", 7114 Sidecar: "sidecar:test-by-*", 7115 }, 7116 GCSConfiguration: &prowapi.GCSConfiguration{ 7117 Bucket: "test-bucket-by-*", 7118 PathStrategy: "single-by-*", 7119 DefaultOrg: "org-by-*", 7120 DefaultRepo: "repo-by-*", 7121 }, 7122 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 7123 }, 7124 }, 7125 }, 7126 }, 7127 }, 7128 }, 7129 utilityConfig: UtilityConfig{Decorate: &yes}, 7130 expected: &prowapi.DecorationConfig{ 7131 UtilityImages: &prowapi.UtilityImages{ 7132 CloneRefs: "clonerefs:test-by-*", 7133 InitUpload: "initupload:test-by-*", 7134 Entrypoint: "entrypoint:test-by-*", 7135 Sidecar: "sidecar:test-by-*", 7136 }, 7137 GCSConfiguration: &prowapi.GCSConfiguration{ 7138 Bucket: "test-bucket-by-*", 7139 PathStrategy: "single-by-*", 7140 DefaultOrg: "org-by-*", 7141 DefaultRepo: "repo-by-*", 7142 }, 7143 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 7144 }, 7145 }, 7146 { 7147 id: "extraRefs[0] defined, only 'org` exists in config, changes from defaultDecorationConfigs[org] expected", 7148 config: &Config{ 7149 ProwConfig: ProwConfig{ 7150 Plank: Plank{ 7151 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 7152 { 7153 OrgRepo: "*", 7154 Cluster: "", 7155 Config: &prowapi.DecorationConfig{ 7156 UtilityImages: &prowapi.UtilityImages{ 7157 CloneRefs: "clonerefs:test-by-*", 7158 InitUpload: "initupload:test-by-*", 7159 Entrypoint: "entrypoint:test-by-*", 7160 Sidecar: "sidecar:test-by-*", 7161 }, 7162 GCSConfiguration: &prowapi.GCSConfiguration{ 7163 Bucket: "test-bucket-by-*", 7164 PathStrategy: "single-by-*", 7165 DefaultOrg: "org-by-*", 7166 DefaultRepo: "repo-by-*", 7167 }, 7168 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 7169 }, 7170 }, 7171 { 7172 OrgRepo: "org", 7173 Cluster: "", 7174 Config: &prowapi.DecorationConfig{ 7175 UtilityImages: &prowapi.UtilityImages{ 7176 CloneRefs: "clonerefs:test-by-org", 7177 InitUpload: "initupload:test-by-org", 7178 Entrypoint: "entrypoint:test-by-org", 7179 Sidecar: "sidecar:test-by-org", 7180 }, 7181 GCSConfiguration: &prowapi.GCSConfiguration{ 7182 Bucket: "test-bucket-by-org", 7183 PathStrategy: "single-by-org", 7184 DefaultOrg: "org-by-org", 7185 DefaultRepo: "repo-by-org", 7186 }, 7187 GCSCredentialsSecret: pStr("credentials-gcs-by-org"), 7188 }, 7189 }, 7190 }, 7191 }, 7192 }, 7193 }, 7194 utilityConfig: UtilityConfig{ 7195 Decorate: &yes, 7196 ExtraRefs: []prowapi.Refs{ 7197 { 7198 Org: "org", 7199 Repo: "repo", 7200 }, 7201 }, 7202 }, 7203 expected: &prowapi.DecorationConfig{ 7204 UtilityImages: &prowapi.UtilityImages{ 7205 CloneRefs: "clonerefs:test-by-org", 7206 InitUpload: "initupload:test-by-org", 7207 Entrypoint: "entrypoint:test-by-org", 7208 Sidecar: "sidecar:test-by-org", 7209 }, 7210 GCSConfiguration: &prowapi.GCSConfiguration{ 7211 Bucket: "test-bucket-by-org", 7212 PathStrategy: "single-by-org", 7213 DefaultOrg: "org-by-org", 7214 DefaultRepo: "repo-by-org", 7215 }, 7216 GCSCredentialsSecret: pStr("credentials-gcs-by-org"), 7217 }, 7218 }, 7219 { 7220 id: "extraRefs[0] defined and org/repo of defaultDecorationConfigs exists, changes from defaultDecorationConfigs[org/repo] expected", 7221 config: &Config{ 7222 ProwConfig: ProwConfig{ 7223 Plank: Plank{ 7224 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 7225 { 7226 OrgRepo: "*", 7227 Cluster: "", 7228 Config: &prowapi.DecorationConfig{ 7229 UtilityImages: &prowapi.UtilityImages{ 7230 CloneRefs: "clonerefs:test-by-*", 7231 InitUpload: "initupload:test-by-*", 7232 Entrypoint: "entrypoint:test-by-*", 7233 Sidecar: "sidecar:test-by-*", 7234 }, 7235 GCSConfiguration: &prowapi.GCSConfiguration{ 7236 Bucket: "test-bucket-by-*", 7237 PathStrategy: "single-by-*", 7238 DefaultOrg: "org-by-*", 7239 DefaultRepo: "repo-by-*", 7240 }, 7241 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 7242 }, 7243 }, 7244 { 7245 OrgRepo: "org/repo", 7246 Cluster: "", 7247 Config: &prowapi.DecorationConfig{ 7248 UtilityImages: &prowapi.UtilityImages{ 7249 CloneRefs: "clonerefs:test-by-org-repo", 7250 InitUpload: "initupload:test-by-org-repo", 7251 Entrypoint: "entrypoint:test-by-org-repo", 7252 Sidecar: "sidecar:test-by-org-repo", 7253 }, 7254 GCSConfiguration: &prowapi.GCSConfiguration{ 7255 Bucket: "test-bucket-by-org-repo", 7256 PathStrategy: "single-by-org-repo", 7257 DefaultOrg: "org-by-org-repo", 7258 DefaultRepo: "repo-by-org-repo", 7259 }, 7260 GCSCredentialsSecret: pStr("credentials-gcs-by-org-repo"), 7261 }, 7262 }, 7263 }, 7264 }, 7265 }, 7266 }, 7267 utilityConfig: UtilityConfig{ 7268 Decorate: &yes, 7269 ExtraRefs: []prowapi.Refs{ 7270 { 7271 Org: "org", 7272 Repo: "repo", 7273 }, 7274 }, 7275 }, 7276 expected: &prowapi.DecorationConfig{ 7277 UtilityImages: &prowapi.UtilityImages{ 7278 CloneRefs: "clonerefs:test-by-org-repo", 7279 InitUpload: "initupload:test-by-org-repo", 7280 Entrypoint: "entrypoint:test-by-org-repo", 7281 Sidecar: "sidecar:test-by-org-repo", 7282 }, 7283 GCSConfiguration: &prowapi.GCSConfiguration{ 7284 Bucket: "test-bucket-by-org-repo", 7285 PathStrategy: "single-by-org-repo", 7286 DefaultOrg: "org-by-org-repo", 7287 DefaultRepo: "repo-by-org-repo", 7288 }, 7289 GCSCredentialsSecret: pStr("credentials-gcs-by-org-repo"), 7290 }, 7291 }, 7292 { 7293 id: "decorate_all_jobs set, plank's default decoration config expected", 7294 config: &Config{ 7295 JobConfig: JobConfig{ 7296 DecorateAllJobs: true, 7297 }, 7298 ProwConfig: ProwConfig{ 7299 Plank: Plank{ 7300 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 7301 { 7302 OrgRepo: "*", 7303 Cluster: "", 7304 Config: &prowapi.DecorationConfig{ 7305 UtilityImages: &prowapi.UtilityImages{ 7306 CloneRefs: "clonerefs:test-by-*", 7307 InitUpload: "initupload:test-by-*", 7308 Entrypoint: "entrypoint:test-by-*", 7309 Sidecar: "sidecar:test-by-*", 7310 }, 7311 GCSConfiguration: &prowapi.GCSConfiguration{ 7312 Bucket: "test-bucket-by-*", 7313 PathStrategy: "single-by-*", 7314 DefaultOrg: "org-by-*", 7315 DefaultRepo: "repo-by-*", 7316 }, 7317 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 7318 }, 7319 }, 7320 }, 7321 }, 7322 }, 7323 }, 7324 expected: &prowapi.DecorationConfig{ 7325 UtilityImages: &prowapi.UtilityImages{ 7326 CloneRefs: "clonerefs:test-by-*", 7327 InitUpload: "initupload:test-by-*", 7328 Entrypoint: "entrypoint:test-by-*", 7329 Sidecar: "sidecar:test-by-*", 7330 }, 7331 GCSConfiguration: &prowapi.GCSConfiguration{ 7332 Bucket: "test-bucket-by-*", 7333 PathStrategy: "single-by-*", 7334 DefaultOrg: "org-by-*", 7335 DefaultRepo: "repo-by-*", 7336 }, 7337 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 7338 }, 7339 }, 7340 { 7341 id: "opt out of decorate_all_jobs by specifying undecorated", 7342 utilityConfig: UtilityConfig{Decorate: &no}, 7343 config: &Config{ 7344 JobConfig: JobConfig{ 7345 DecorateAllJobs: true, 7346 }, 7347 ProwConfig: ProwConfig{ 7348 Plank: Plank{ 7349 DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 7350 { 7351 OrgRepo: "*", 7352 Cluster: "", 7353 Config: &prowapi.DecorationConfig{ 7354 UtilityImages: &prowapi.UtilityImages{ 7355 CloneRefs: "clonerefs:test-by-*", 7356 InitUpload: "initupload:test-by-*", 7357 Entrypoint: "entrypoint:test-by-*", 7358 Sidecar: "sidecar:test-by-*", 7359 }, 7360 GCSConfiguration: &prowapi.GCSConfiguration{ 7361 Bucket: "test-bucket-by-*", 7362 PathStrategy: "single-by-*", 7363 DefaultOrg: "org-by-*", 7364 DefaultRepo: "repo-by-*", 7365 }, 7366 GCSCredentialsSecret: pStr("credentials-gcs-by-*"), 7367 }, 7368 }, 7369 }, 7370 }, 7371 }, 7372 }, 7373 }, 7374 { 7375 id: "no extraRefs[0] or cluster => use global + default cluster configs", 7376 config: complexConfig(), 7377 expected: &prowapi.DecorationConfig{ 7378 UtilityImages: &prowapi.UtilityImages{ 7379 CloneRefs: "clonerefs:global", 7380 InitUpload: "initupload:global", 7381 Entrypoint: "entrypoint:global", 7382 Sidecar: "sidecar:global", 7383 }, 7384 GCSConfiguration: &prowapi.GCSConfiguration{ 7385 Bucket: "global", 7386 PathStrategy: "explicit", 7387 }, 7388 GCSCredentialsSecret: pStr("default-cluster-uses-secret"), 7389 }, 7390 }, 7391 { 7392 id: "extraRefs[0] has org and explicit 'default' cluster => use global + org + default cluster configs", 7393 config: complexConfig(), 7394 cluster: "default", 7395 utilityConfig: UtilityConfig{ 7396 ExtraRefs: []prowapi.Refs{ 7397 { 7398 Org: "org", 7399 Repo: "foo", 7400 }, 7401 }, 7402 }, 7403 expected: &prowapi.DecorationConfig{ 7404 UtilityImages: &prowapi.UtilityImages{ 7405 CloneRefs: "clonerefs:global", 7406 InitUpload: "initupload:global", 7407 Entrypoint: "entrypoint:global", 7408 Sidecar: "sidecar:global", 7409 }, 7410 GCSConfiguration: &prowapi.GCSConfiguration{ 7411 Bucket: "org-specific", 7412 PathStrategy: "explicit", 7413 }, 7414 GCSCredentialsSecret: pStr("default-cluster-uses-secret"), 7415 }, 7416 }, 7417 { 7418 id: "extraRefs[0] has repo and explicit 'trusted' cluster => use global + org + repo + trusted cluster configs", 7419 config: complexConfig(), 7420 cluster: "trusted", 7421 utilityConfig: UtilityConfig{ 7422 ExtraRefs: []prowapi.Refs{ 7423 { 7424 Org: "org", 7425 Repo: "repo", 7426 }, 7427 }, 7428 }, 7429 expected: &prowapi.DecorationConfig{ 7430 UtilityImages: &prowapi.UtilityImages{ 7431 CloneRefs: "clonerefs:global", 7432 InitUpload: "initupload:global", 7433 Entrypoint: "entrypoint:global", 7434 Sidecar: "sidecar:global", 7435 }, 7436 GCSConfiguration: &prowapi.GCSConfiguration{ 7437 Bucket: "repo-specific", 7438 PathStrategy: "explicit", 7439 }, 7440 DefaultServiceAccountName: pStr("trusted-cluster-uses-SA"), 7441 }, 7442 }, 7443 { 7444 id: "extraRefs[0] has override org and explicit 'trusted' cluster => use global + trusted cluster + override configs", 7445 config: complexConfig(), 7446 cluster: "trusted", 7447 utilityConfig: UtilityConfig{ 7448 ExtraRefs: []prowapi.Refs{ 7449 { 7450 Org: "override", 7451 Repo: "foo", 7452 }, 7453 }, 7454 }, 7455 expected: &prowapi.DecorationConfig{ 7456 UtilityImages: &prowapi.UtilityImages{ 7457 CloneRefs: "clonerefs:override", 7458 InitUpload: "initupload:global", 7459 Entrypoint: "entrypoint:global", 7460 Sidecar: "sidecar:global", 7461 }, 7462 GCSConfiguration: &prowapi.GCSConfiguration{ 7463 Bucket: "global", 7464 PathStrategy: "explicit", 7465 }, 7466 DefaultServiceAccountName: pStr(""), 7467 GCSCredentialsSecret: pStr("trusted-cluster-override-uses-secret"), 7468 }, 7469 }, 7470 { 7471 id: "extraRefs[0] has override org and no cluster => use global + default cluster configs", 7472 config: complexConfig(), 7473 utilityConfig: UtilityConfig{ 7474 ExtraRefs: []prowapi.Refs{ 7475 { 7476 Org: "override", 7477 Repo: "foo", 7478 }, 7479 }, 7480 }, 7481 expected: &prowapi.DecorationConfig{ 7482 UtilityImages: &prowapi.UtilityImages{ 7483 CloneRefs: "clonerefs:global", 7484 InitUpload: "initupload:global", 7485 Entrypoint: "entrypoint:global", 7486 Sidecar: "sidecar:global", 7487 }, 7488 GCSConfiguration: &prowapi.GCSConfiguration{ 7489 Bucket: "global", 7490 PathStrategy: "explicit", 7491 }, 7492 GCSCredentialsSecret: pStr("default-cluster-uses-secret"), 7493 }, 7494 }, 7495 } 7496 7497 for _, tc := range testCases { 7498 t.Run(tc.id, func(t *testing.T) { 7499 c := &Config{} 7500 periodic := &Periodic{JobBase: JobBase{Cluster: tc.cluster, UtilityConfig: tc.utilityConfig}} 7501 c.defaultJobBase(&periodic.JobBase) 7502 setPeriodicDecorationDefaults(tc.config, periodic) 7503 if diff := cmp.Diff(periodic.DecorationConfig, tc.expected, cmpopts.EquateEmpty()); diff != "" { 7504 t.Error(diff) 7505 } 7506 }) 7507 } 7508 } 7509 7510 func TestInRepoConfigEnabled(t *testing.T) { 7511 testCases := []struct { 7512 name string 7513 config Config 7514 expected bool 7515 testing string 7516 }{ 7517 { 7518 name: "Exact match", 7519 config: Config{ 7520 ProwConfig: ProwConfig{ 7521 InRepoConfig: InRepoConfig{ 7522 Enabled: map[string]*bool{ 7523 "org/repo": utilpointer.Bool(true), 7524 }, 7525 }, 7526 }, 7527 }, 7528 expected: true, 7529 testing: "org/repo", 7530 }, 7531 { 7532 name: "Orgname matches", 7533 config: Config{ 7534 ProwConfig: ProwConfig{ 7535 InRepoConfig: InRepoConfig{ 7536 Enabled: map[string]*bool{ 7537 "org": utilpointer.Bool(true), 7538 }, 7539 }, 7540 }, 7541 }, 7542 expected: true, 7543 testing: "org/repo", 7544 }, 7545 { 7546 name: "Globally enabled", 7547 config: Config{ 7548 ProwConfig: ProwConfig{ 7549 InRepoConfig: InRepoConfig{ 7550 Enabled: map[string]*bool{ 7551 "*": utilpointer.Bool(true), 7552 }, 7553 }, 7554 }, 7555 }, 7556 expected: true, 7557 testing: "org/repo", 7558 }, 7559 { 7560 name: "Disabled by default", 7561 expected: false, 7562 testing: "org/repo", 7563 }, 7564 { 7565 name: "Gerrit format org Hostname matches", 7566 config: Config{ 7567 ProwConfig: ProwConfig{ 7568 InRepoConfig: InRepoConfig{ 7569 Enabled: map[string]*bool{ 7570 "host-name": utilpointer.Bool(true), 7571 }, 7572 }, 7573 }, 7574 }, 7575 expected: true, 7576 testing: "host-name/extra/repo", 7577 }, 7578 { 7579 name: "Gerrit format org Hostname matches with http", 7580 config: Config{ 7581 ProwConfig: ProwConfig{ 7582 InRepoConfig: InRepoConfig{ 7583 Enabled: map[string]*bool{ 7584 "host-name": utilpointer.Bool(true), 7585 }, 7586 }, 7587 }, 7588 }, 7589 expected: true, 7590 testing: "http://host-name/extra/repo", 7591 }, 7592 { 7593 name: "Gerrit format Just org Hostname matches", 7594 config: Config{ 7595 ProwConfig: ProwConfig{ 7596 InRepoConfig: InRepoConfig{ 7597 Enabled: map[string]*bool{ 7598 "host-name": utilpointer.Bool(true), 7599 }, 7600 }, 7601 }, 7602 }, 7603 expected: true, 7604 testing: "host-name", 7605 }, 7606 { 7607 name: "Gerrit format Just org Hostname matches with http", 7608 config: Config{ 7609 ProwConfig: ProwConfig{ 7610 InRepoConfig: InRepoConfig{ 7611 Enabled: map[string]*bool{ 7612 "host-name": utilpointer.Bool(true), 7613 }, 7614 }, 7615 }, 7616 }, 7617 expected: true, 7618 testing: "http://host-name", 7619 }, 7620 { 7621 name: "Gerrit format Just repo Hostname matches", 7622 config: Config{ 7623 ProwConfig: ProwConfig{ 7624 InRepoConfig: InRepoConfig{ 7625 Enabled: map[string]*bool{ 7626 "host-name/repo/name": utilpointer.Bool(true), 7627 }, 7628 }, 7629 }, 7630 }, 7631 expected: true, 7632 testing: "host-name/repo/name", 7633 }, 7634 { 7635 name: "Gerrit format Just org Hostname matches with http", 7636 config: Config{ 7637 ProwConfig: ProwConfig{ 7638 InRepoConfig: InRepoConfig{ 7639 Enabled: map[string]*bool{ 7640 "host-name/repo/name": utilpointer.Bool(true), 7641 }, 7642 }, 7643 }, 7644 }, 7645 expected: true, 7646 testing: "http://host-name/repo/name", 7647 }, 7648 } 7649 7650 for idx := range testCases { 7651 tc := testCases[idx] 7652 t.Run(tc.name, func(t *testing.T) { 7653 t.Parallel() 7654 7655 if result := tc.config.InRepoConfigEnabled(tc.testing); result != tc.expected { 7656 t.Errorf("Expected %t, got %t", tc.expected, result) 7657 } 7658 }) 7659 } 7660 } 7661 7662 func TestGetProwYAMLDoesNotCallRefGettersWhenInrepoconfigIsDisabled(t *testing.T) { 7663 t.Parallel() 7664 7665 var baseSHAGetterCalled, headSHAGetterCalled bool 7666 baseSHAGetter := func() (string, error) { 7667 baseSHAGetterCalled = true 7668 return "", nil 7669 } 7670 headSHAGetter := func() (string, error) { 7671 headSHAGetterCalled = true 7672 return "", nil 7673 } 7674 7675 c := &Config{} 7676 if _, err := c.getProwYAMLWithDefaults(nil, "test", "main", baseSHAGetter, headSHAGetter); err != nil { 7677 t.Fatalf("error calling GetProwYAML: %v", err) 7678 } 7679 if baseSHAGetterCalled { 7680 t.Error("baseSHAGetter got called") 7681 } 7682 if headSHAGetterCalled { 7683 t.Error("headSHAGetter got called") 7684 } 7685 } 7686 7687 func TestGetPresubmitsReturnsStaticAndInrepoconfigPresubmits(t *testing.T) { 7688 t.Parallel() 7689 7690 org, repo := "org", "repo" 7691 c := &Config{ 7692 ProwConfig: ProwConfig{ 7693 InRepoConfig: InRepoConfig{Enabled: map[string]*bool{"*": utilpointer.Bool(true)}}, 7694 }, 7695 JobConfig: JobConfig{ 7696 PresubmitsStatic: map[string][]Presubmit{ 7697 org + "/" + repo: {{ 7698 JobBase: JobBase{Name: "my-static-presubmit"}, 7699 Reporter: Reporter{Context: "my-static-presubmit"}, 7700 }}, 7701 }, 7702 ProwYAMLGetterWithDefaults: fakeProwYAMLGetterFactory( 7703 []Presubmit{ 7704 { 7705 JobBase: JobBase{Name: "hans"}, 7706 }, 7707 }, 7708 nil, 7709 ), 7710 }, 7711 } 7712 7713 presubmits, err := c.GetPresubmits(nil, org+"/"+repo, "main", func() (string, error) { return "", nil }) 7714 if err != nil { 7715 t.Fatalf("Error calling GetPresubmits: %v", err) 7716 } 7717 7718 if n := len(presubmits); n != 2 || 7719 presubmits[0].Name != "my-static-presubmit" || 7720 presubmits[1].Name != "hans" { 7721 t.Errorf(`expected exactly two presubmits named "my-static-presubmit" and "hans", got %d (%v)`, n, presubmits) 7722 } 7723 } 7724 7725 func TestGetPostsubmitsReturnsStaticAndInrepoconfigPostsubmits(t *testing.T) { 7726 t.Parallel() 7727 7728 org, repo := "org", "repo" 7729 c := &Config{ 7730 ProwConfig: ProwConfig{ 7731 InRepoConfig: InRepoConfig{Enabled: map[string]*bool{"*": utilpointer.Bool(true)}}, 7732 }, 7733 JobConfig: JobConfig{ 7734 PostsubmitsStatic: map[string][]Postsubmit{ 7735 org + "/" + repo: {{ 7736 JobBase: JobBase{Name: "my-static-postsubmits"}, 7737 Reporter: Reporter{Context: "my-static-postsubmits"}, 7738 }}, 7739 }, 7740 ProwYAMLGetterWithDefaults: fakeProwYAMLGetterFactory( 7741 nil, 7742 []Postsubmit{ 7743 { 7744 JobBase: JobBase{Name: "hans"}, 7745 }, 7746 }, 7747 ), 7748 }, 7749 } 7750 7751 postsubmits, err := c.GetPostsubmits(nil, org+"/"+repo, "main", func() (string, error) { return "", nil }) 7752 if err != nil { 7753 t.Fatalf("Error calling GetPostsubmits: %v", err) 7754 } 7755 7756 if n := len(postsubmits); n != 2 || 7757 postsubmits[0].Name != "my-static-postsubmits" || 7758 postsubmits[1].Name != "hans" { 7759 t.Errorf(`expected exactly two postsubmits named "my-static-postsubmits" and "hans", got %d (%v)`, n, postsubmits) 7760 } 7761 } 7762 7763 func TestInRepoConfigAllowsCluster(t *testing.T) { 7764 const clusterName = "that-cluster" 7765 7766 testCases := []struct { 7767 name string 7768 repoIdentifier string 7769 allowedClusters map[string][]string 7770 7771 expectedResult bool 7772 }{ 7773 { 7774 name: "Nothing configured, nothing allowed", 7775 repoIdentifier: "foo", 7776 expectedResult: false, 7777 }, 7778 { 7779 name: "Allowed on repolevel", 7780 repoIdentifier: "foo/repo", 7781 allowedClusters: map[string][]string{"foo/repo": {clusterName}}, 7782 expectedResult: true, 7783 }, 7784 { 7785 name: "Not allowed on repolevel", 7786 repoIdentifier: "foo/repo", 7787 allowedClusters: map[string][]string{"foo/repo": {"different-cluster"}}, 7788 expectedResult: false, 7789 }, 7790 { 7791 name: "Allowed for different repo", 7792 repoIdentifier: "foo/repo", 7793 allowedClusters: map[string][]string{"bar/repo": {clusterName}}, 7794 expectedResult: false, 7795 }, 7796 { 7797 name: "Allowed on orglevel", 7798 repoIdentifier: "foo/repo", 7799 allowedClusters: map[string][]string{"foo": {clusterName}}, 7800 expectedResult: true, 7801 }, 7802 { 7803 name: "Not allowed on orglevel", 7804 repoIdentifier: "foo/repo", 7805 allowedClusters: map[string][]string{"foo": {"different-cluster"}}, 7806 expectedResult: false, 7807 }, 7808 { 7809 name: "Allowed on for different org", 7810 repoIdentifier: "foo/repo", 7811 allowedClusters: map[string][]string{"bar": {clusterName}}, 7812 expectedResult: false, 7813 }, 7814 { 7815 name: "Allowed globally", 7816 repoIdentifier: "foo/repo", 7817 allowedClusters: map[string][]string{"*": {clusterName}}, 7818 expectedResult: true, 7819 }, 7820 { 7821 name: "Allowed for gerrit host", 7822 repoIdentifier: "https://host/repo/name", 7823 allowedClusters: map[string][]string{"host": {clusterName}}, 7824 expectedResult: true, 7825 }, 7826 { 7827 name: "Allowed for gerrit repo", 7828 repoIdentifier: "https://host/repo/name", 7829 allowedClusters: map[string][]string{"host/repo/name": {clusterName}}, 7830 expectedResult: true, 7831 }, 7832 { 7833 name: "Allowed for gerrit repo", 7834 repoIdentifier: "host", 7835 allowedClusters: map[string][]string{"host": {clusterName}}, 7836 expectedResult: true, 7837 }, 7838 { 7839 name: "Allowed for gerrit repo", 7840 repoIdentifier: "host/repo/name", 7841 allowedClusters: map[string][]string{"host": {clusterName}}, 7842 expectedResult: true, 7843 }, 7844 } 7845 7846 for idx := range testCases { 7847 tc := testCases[idx] 7848 t.Run(tc.name, func(t *testing.T) { 7849 t.Parallel() 7850 7851 cfg := &Config{ 7852 ProwConfig: ProwConfig{InRepoConfig: InRepoConfig{AllowedClusters: tc.allowedClusters}}, 7853 } 7854 7855 if actual := cfg.InRepoConfigAllowsCluster(clusterName, tc.repoIdentifier); actual != tc.expectedResult { 7856 t.Errorf("expected result %t, got result %t", tc.expectedResult, actual) 7857 } 7858 }) 7859 } 7860 } 7861 7862 func TestMergeDefaultDecorationConfigThreadSafety(t *testing.T) { 7863 const repo = "org/repo" 7864 const cluster = "default" 7865 p := Plank{DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{ 7866 { 7867 OrgRepo: "*", 7868 Cluster: "*", 7869 Config: &prowapi.DecorationConfig{ 7870 GCSConfiguration: &prowapi.GCSConfiguration{ 7871 MediaTypes: map[string]string{"text": "text"}, 7872 }, 7873 GCSCredentialsSecret: pStr("service-account-secret"), 7874 }, 7875 }, 7876 { 7877 OrgRepo: repo, 7878 Cluster: "*", 7879 Config: &prowapi.DecorationConfig{ 7880 GCSConfiguration: &prowapi.GCSConfiguration{ 7881 MediaTypes: map[string]string{"text": "text2"}, 7882 }, 7883 }, 7884 }, 7885 { 7886 OrgRepo: "*", 7887 Cluster: cluster, 7888 Config: &prowapi.DecorationConfig{ 7889 DefaultServiceAccountName: pStr("service-account-name"), 7890 GCSCredentialsSecret: pStr(""), 7891 }, 7892 }, 7893 }} 7894 jobDC := &prowapi.DecorationConfig{ 7895 GCSConfiguration: &prowapi.GCSConfiguration{ 7896 Bucket: "special-bucket", 7897 }, 7898 } 7899 7900 s1 := make(chan struct{}) 7901 s2 := make(chan struct{}) 7902 7903 go func() { 7904 _ = p.mergeDefaultDecorationConfig(repo, cluster, jobDC) 7905 close(s1) 7906 }() 7907 go func() { 7908 _ = p.mergeDefaultDecorationConfig(repo, cluster, jobDC) 7909 close(s2) 7910 }() 7911 7912 <-s1 7913 <-s2 7914 } 7915 7916 func TestDefaultAndValidateReportTemplate(t *testing.T) { 7917 testCases := []struct { 7918 id string 7919 controller *Controller 7920 expected *Controller 7921 expectedErr bool 7922 }{ 7923 7924 { 7925 id: "no report_template or report_templates specified, no changes expected", 7926 controller: &Controller{}, 7927 expected: &Controller{}, 7928 }, 7929 7930 { 7931 id: "only report_template specified, expected report_template[*]=report_template", 7932 controller: &Controller{ReportTemplateString: "test template"}, 7933 expected: &Controller{ 7934 ReportTemplateString: "test template", 7935 ReportTemplateStrings: map[string]string{"*": "test template"}, 7936 ReportTemplates: map[string]*template.Template{ 7937 "*": func() *template.Template { 7938 reportTmpl, _ := template.New("Report").Parse("test template") 7939 return reportTmpl 7940 }(), 7941 }, 7942 }, 7943 }, 7944 7945 { 7946 id: "only report_templates specified, expected direct conversion", 7947 controller: &Controller{ReportTemplateStrings: map[string]string{"*": "test template"}}, 7948 expected: &Controller{ 7949 ReportTemplateStrings: map[string]string{"*": "test template"}, 7950 ReportTemplates: map[string]*template.Template{ 7951 "*": func() *template.Template { 7952 reportTmpl, _ := template.New("Report").Parse("test template") 7953 return reportTmpl 7954 }(), 7955 }, 7956 }, 7957 }, 7958 7959 { 7960 id: "no '*' in report_templates specified, expected error", 7961 controller: &Controller{ReportTemplateStrings: map[string]string{"org": "test template"}}, 7962 expectedErr: true, 7963 }, 7964 } 7965 7966 for _, tc := range testCases { 7967 t.Run(tc.id, func(t *testing.T) { 7968 if err := defaultAndValidateReportTemplate(tc.controller); err != nil && !tc.expectedErr { 7969 t.Fatalf("error not expected: %v", err) 7970 } 7971 7972 if !reflect.DeepEqual(tc.controller, tc.expected) && !tc.expectedErr { 7973 t.Fatalf("\nGot: %#v\nExpected: %#v", tc.controller, tc.expected) 7974 } 7975 }) 7976 } 7977 } 7978 7979 func TestValidatePresubmits(t *testing.T) { 7980 t.Parallel() 7981 testCases := []struct { 7982 name string 7983 presubmits []Presubmit 7984 expectedError string 7985 }{ 7986 { 7987 name: "Duplicate context causes error", 7988 presubmits: []Presubmit{ 7989 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "repeated"}}, 7990 {JobBase: JobBase{Name: "b"}, Reporter: Reporter{Context: "repeated"}}, 7991 }, 7992 expectedError: `[jobs b and a report to the same GitHub context "repeated", jobs a and b report to the same GitHub context "repeated"]`, 7993 }, 7994 { 7995 name: "Duplicate context on different branch doesn't cause error", 7996 presubmits: []Presubmit{ 7997 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "repeated"}, Brancher: Brancher{Branches: []string{"master"}}}, 7998 {JobBase: JobBase{Name: "b"}, Reporter: Reporter{Context: "repeated"}, Brancher: Brancher{Branches: []string{"next"}}}, 7999 }, 8000 }, 8001 { 8002 name: "Duplicate jobname causes error", 8003 presubmits: []Presubmit{ 8004 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "foo"}}, 8005 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "bar"}}, 8006 }, 8007 expectedError: "duplicated presubmit jobs (consider both inrepo and central config): [a]", 8008 }, 8009 { 8010 name: "Duplicate jobname on different branches doesn't cause error", 8011 presubmits: []Presubmit{ 8012 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "foo"}, Brancher: Brancher{Branches: []string{"master"}}}, 8013 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "foo"}, Brancher: Brancher{Branches: []string{"next"}}}, 8014 }, 8015 }, 8016 { 8017 name: "Invalid JobBase causes error", 8018 presubmits: []Presubmit{{Reporter: Reporter{Context: "foo"}}}, 8019 expectedError: `invalid presubmit job : name: must match regex "^[A-Za-z0-9-._]+$"`, 8020 }, 8021 { 8022 name: "Invalid triggering config causes error", 8023 presubmits: []Presubmit{{Trigger: "some-trigger", JobBase: JobBase{Name: "my-job"}, Reporter: Reporter{Context: "foo"}}}, 8024 expectedError: `either both of job.Trigger and job.RerunCommand must be set, wasnt the case for job "my-job"`, 8025 }, 8026 { 8027 name: "Invalid reporting config causes error", 8028 presubmits: []Presubmit{{JobBase: JobBase{Name: "my-job"}}}, 8029 expectedError: "invalid presubmit job my-job: job is set to report but has no context configured", 8030 }, 8031 { 8032 name: "Mutually exclusive settings: always_run and run_if_changed", 8033 presubmits: []Presubmit{{ 8034 JobBase: JobBase{Name: "a"}, 8035 Reporter: Reporter{Context: "foo"}, 8036 AlwaysRun: true, 8037 RegexpChangeMatcher: RegexpChangeMatcher{RunIfChanged: `\.go$`}, 8038 }}, 8039 expectedError: "job a is set to always run but also declares run_if_changed targets, which are mutually exclusive", 8040 }, 8041 { 8042 name: "Mutually exclusive settings: always_run and skip_if_only_changed", 8043 presubmits: []Presubmit{{ 8044 JobBase: JobBase{Name: "a"}, 8045 Reporter: Reporter{Context: "foo"}, 8046 AlwaysRun: true, 8047 RegexpChangeMatcher: RegexpChangeMatcher{SkipIfOnlyChanged: `\.go$`}, 8048 }}, 8049 expectedError: "job a is set to always run but also declares skip_if_only_changed targets, which are mutually exclusive", 8050 }, 8051 { 8052 name: "Mutually exclusive settings: run_if_changed and skip_if_only_changed", 8053 presubmits: []Presubmit{{ 8054 JobBase: JobBase{Name: "a"}, 8055 Reporter: Reporter{Context: "foo"}, 8056 RegexpChangeMatcher: RegexpChangeMatcher{ 8057 RunIfChanged: `\.go$`, 8058 SkipIfOnlyChanged: `\.md`, 8059 }, 8060 }}, 8061 expectedError: "job a declares run_if_changed and skip_if_only_changed, which are mutually exclusive", 8062 }, 8063 } 8064 8065 for _, tc := range testCases { 8066 var errMsg string 8067 err := Config{}.validatePresubmits(tc.presubmits) 8068 if err != nil { 8069 errMsg = err.Error() 8070 } 8071 if errMsg != tc.expectedError { 8072 t.Errorf("expected error '%s', got error '%s'", tc.expectedError, errMsg) 8073 } 8074 } 8075 } 8076 8077 func TestValidatePostsubmits(t *testing.T) { 8078 t.Parallel() 8079 true_ := true 8080 testCases := []struct { 8081 name string 8082 postsubmits []Postsubmit 8083 expectedError string 8084 }{ 8085 { 8086 name: "Duplicate context causes error", 8087 postsubmits: []Postsubmit{ 8088 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "repeated"}}, 8089 {JobBase: JobBase{Name: "b"}, Reporter: Reporter{Context: "repeated"}}, 8090 }, 8091 expectedError: `[jobs b and a report to the same GitHub context "repeated", jobs a and b report to the same GitHub context "repeated"]`, 8092 }, 8093 { 8094 name: "Duplicate context on different branch doesn't cause error", 8095 postsubmits: []Postsubmit{ 8096 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "repeated"}, Brancher: Brancher{Branches: []string{"master"}}}, 8097 {JobBase: JobBase{Name: "b"}, Reporter: Reporter{Context: "repeated"}, Brancher: Brancher{Branches: []string{"next"}}}, 8098 }, 8099 }, 8100 { 8101 name: "Duplicate jobname causes error", 8102 postsubmits: []Postsubmit{ 8103 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "foo"}}, 8104 {JobBase: JobBase{Name: "a"}, Reporter: Reporter{Context: "bar"}}, 8105 }, 8106 expectedError: "duplicated postsubmit jobs (consider both inrepo and central config): [a]", 8107 }, 8108 { 8109 name: "Invalid JobBase causes error", 8110 postsubmits: []Postsubmit{{Reporter: Reporter{Context: "foo"}}}, 8111 expectedError: `invalid postsubmit job : name: must match regex "^[A-Za-z0-9-._]+$"`, 8112 }, 8113 { 8114 name: "Invalid reporting config causes error", 8115 postsubmits: []Postsubmit{{JobBase: JobBase{Name: "my-job"}}}, 8116 expectedError: "invalid postsubmit job my-job: job is set to report but has no context configured", 8117 }, 8118 { 8119 name: "Mutually exclusive settings: always_run and run_if_changed", 8120 postsubmits: []Postsubmit{{ 8121 JobBase: JobBase{Name: "a"}, 8122 Reporter: Reporter{Context: "foo"}, 8123 AlwaysRun: &true_, 8124 RegexpChangeMatcher: RegexpChangeMatcher{RunIfChanged: `\.go$`}, 8125 }}, 8126 expectedError: "job a is set to always run but also declares run_if_changed targets, which are mutually exclusive", 8127 }, 8128 { 8129 name: "Mutually exclusive settings: always_run and skip_if_only_changed", 8130 postsubmits: []Postsubmit{{ 8131 JobBase: JobBase{Name: "a"}, 8132 Reporter: Reporter{Context: "foo"}, 8133 AlwaysRun: &true_, 8134 RegexpChangeMatcher: RegexpChangeMatcher{SkipIfOnlyChanged: `\.go$`}, 8135 }}, 8136 expectedError: "job a is set to always run but also declares skip_if_only_changed targets, which are mutually exclusive", 8137 }, 8138 { 8139 name: "Mutually exclusive settings: run_if_changed and skip_if_only_changed", 8140 postsubmits: []Postsubmit{{ 8141 JobBase: JobBase{Name: "a"}, 8142 Reporter: Reporter{Context: "foo"}, 8143 RegexpChangeMatcher: RegexpChangeMatcher{ 8144 RunIfChanged: `\.go$`, 8145 SkipIfOnlyChanged: `\.md`, 8146 }, 8147 }}, 8148 expectedError: "job a declares run_if_changed and skip_if_only_changed, which are mutually exclusive", 8149 }, 8150 } 8151 8152 for _, tc := range testCases { 8153 var errMsg string 8154 err := Config{}.validatePostsubmits(tc.postsubmits) 8155 if err != nil { 8156 errMsg = err.Error() 8157 } 8158 if errMsg != tc.expectedError { 8159 t.Errorf("expected error '%s', got error '%s'", tc.expectedError, errMsg) 8160 } 8161 } 8162 } 8163 8164 func TestValidatePeriodics(t *testing.T) { 8165 t.Parallel() 8166 testCases := []struct { 8167 name string 8168 periodics []Periodic 8169 expected []Periodic 8170 expectedError string 8171 }{ 8172 { 8173 name: "Duplicate jobname causes error", 8174 periodics: []Periodic{ 8175 {JobBase: JobBase{Name: "a"}, Interval: "6h"}, 8176 {JobBase: JobBase{Name: "a"}, Interval: "12h"}, 8177 }, 8178 expectedError: "duplicated periodic job: a", 8179 }, 8180 { 8181 name: "Mutually exclusive settings: cron, interval, and minimal_interval", 8182 periodics: []Periodic{ 8183 {JobBase: JobBase{Name: "a"}, Interval: "6h", MinimumInterval: "6h"}, 8184 }, 8185 expectedError: "cron, interval, and minimum_interval are mutually exclusive in periodic a", 8186 }, 8187 { 8188 name: "Required settings: cron, interval, or minimal_interval", 8189 periodics: []Periodic{ 8190 {JobBase: JobBase{Name: "a"}}, 8191 }, 8192 expectedError: "at least one of cron, interval, or minimum_interval must be set in periodic a", 8193 }, 8194 { 8195 name: "Invalid cron string", 8196 periodics: []Periodic{ 8197 {JobBase: JobBase{Name: "a"}, Cron: "hello"}, 8198 }, 8199 expectedError: "invalid cron string hello in periodic a: Expected 5 or 6 fields, found 1: hello", 8200 }, 8201 { 8202 name: "Invalid interval", 8203 periodics: []Periodic{ 8204 {JobBase: JobBase{Name: "a"}, Interval: "hello"}, 8205 }, 8206 expectedError: "cannot parse duration for a: time: invalid duration \"hello\"", 8207 }, 8208 { 8209 name: "Invalid minimum_interval", 8210 periodics: []Periodic{ 8211 {JobBase: JobBase{Name: "a"}, MinimumInterval: "hello"}, 8212 }, 8213 expectedError: "cannot parse duration for a: time: invalid duration \"hello\"", 8214 }, 8215 { 8216 name: "Sets interval", 8217 periodics: []Periodic{ 8218 {JobBase: JobBase{Name: "a"}, Interval: "10ns"}, 8219 }, 8220 expected: []Periodic{ 8221 {JobBase: JobBase{Name: "a"}, Interval: "10ns", interval: time.Duration(10)}, 8222 }, 8223 }, 8224 { 8225 name: "Sets minimum_interval", 8226 periodics: []Periodic{ 8227 {JobBase: JobBase{Name: "a"}, MinimumInterval: "10ns"}, 8228 }, 8229 expected: []Periodic{ 8230 {JobBase: JobBase{Name: "a"}, MinimumInterval: "10ns", minimum_interval: time.Duration(10)}, 8231 }, 8232 }, 8233 } 8234 8235 for _, tc := range testCases { 8236 var errMsg string 8237 err := Config{}.validatePeriodics(tc.periodics) 8238 if err != nil { 8239 errMsg = err.Error() 8240 } 8241 if len(tc.expected) > 0 && !reflect.DeepEqual(tc.periodics, tc.expected) { 8242 t.Errorf("expected '%v', got '%v'", tc.expected, tc.periodics) 8243 } 8244 if tc.expectedError != "" && errMsg != tc.expectedError { 8245 t.Errorf("expected error '%s', got error '%s'", tc.expectedError, errMsg) 8246 } 8247 } 8248 } 8249 8250 func TestValidateStorageBucket(t *testing.T) { 8251 testCases := []struct { 8252 name string 8253 yaml string 8254 bucket string 8255 expectedErr string 8256 }{ 8257 { 8258 name: "unspecified config means no validation", 8259 yaml: ``, 8260 bucket: "who-knows", 8261 expectedErr: "", 8262 }, 8263 { 8264 name: "validation disabled", 8265 yaml: ` 8266 deck: 8267 skip_storage_path_validation: true`, 8268 bucket: "random-unknown-bucket", 8269 expectedErr: "", 8270 }, 8271 { 8272 name: "validation enabled", 8273 yaml: ` 8274 deck: 8275 skip_storage_path_validation: false`, 8276 bucket: "random-unknown-bucket", 8277 expectedErr: "bucket \"random-unknown-bucket\" not in allowed list", 8278 }, 8279 { 8280 name: "DecorationConfig allowed bucket", 8281 yaml: ` 8282 deck: 8283 skip_storage_path_validation: false 8284 plank: 8285 default_decoration_configs: 8286 '*': 8287 gcs_configuration: 8288 bucket: "kubernetes-jenkins"`, 8289 bucket: "kubernetes-jenkins", 8290 expectedErr: "", 8291 }, 8292 { 8293 name: "custom allowed bucket", 8294 yaml: ` 8295 deck: 8296 skip_storage_path_validation: false 8297 additional_allowed_buckets: 8298 - "kubernetes-prow"`, 8299 bucket: "kubernetes-prow", 8300 expectedErr: "", 8301 }, 8302 { 8303 name: "unknown bucket path", 8304 yaml: ` 8305 deck: 8306 skip_storage_path_validation: false`, 8307 bucket: "istio-prow", 8308 expectedErr: "bucket \"istio-prow\" not in allowed list", 8309 }, 8310 } 8311 8312 for _, tc := range testCases { 8313 t.Run(tc.name, func(nested *testing.T) { 8314 cfg, err := loadConfigYaml(tc.yaml, nested) 8315 if err != nil { 8316 nested.Fatalf("failed to load prow config: err=%v\nYAML=%v", err, tc.yaml) 8317 } 8318 expectingErr := len(tc.expectedErr) > 0 8319 8320 err = cfg.ValidateStorageBucket(tc.bucket) 8321 8322 if expectingErr && err == nil { 8323 nested.Fatalf("no errors, but was expecting error: %v", tc.expectedErr) 8324 } 8325 if err != nil && !expectingErr { 8326 nested.Fatalf("expecting no errors, but got: %v", err) 8327 } 8328 if expectingErr && err != nil && !strings.Contains(err.Error(), tc.expectedErr) { 8329 nested.Fatalf("expecting error substring \"%v\", but got error: %v", tc.expectedErr, err) 8330 } 8331 }) 8332 } 8333 } 8334 8335 func loadConfigYaml(prowConfigYaml string, t *testing.T, supplementalProwConfigs ...string) (*Config, error) { 8336 prowConfigDir := t.TempDir() 8337 8338 prowConfig := filepath.Join(prowConfigDir, "config.yaml") 8339 if err := os.WriteFile(prowConfig, []byte(prowConfigYaml), 0666); err != nil { 8340 t.Fatalf("fail to write prow config: %v", err) 8341 } 8342 8343 var supplementalProwConfigDirs []string 8344 for idx, cfg := range supplementalProwConfigs { 8345 dir := filepath.Join(prowConfigDir, strconv.Itoa(idx)) 8346 supplementalProwConfigDirs = append(supplementalProwConfigDirs, dir) 8347 if err := os.Mkdir(dir, 0755); err != nil { 8348 t.Fatalf("failed to create dir %s for supplemental prow config: %v", dir, err) 8349 } 8350 8351 // use a random prefix for the file to make sure that the loading correctly loads all supplemental configs with the 8352 // right suffix. 8353 if err := os.WriteFile(filepath.Join(dir, strconv.Itoa(time.Now().Nanosecond())+"_prowconfig.yaml"), []byte(cfg), 0644); err != nil { 8354 t.Fatalf("failed to write supplemental prow config: %v", err) 8355 } 8356 } 8357 8358 return Load(prowConfig, "", supplementalProwConfigDirs, "_prowconfig.yaml") 8359 } 8360 8361 func TestProwConfigMerging(t *testing.T) { 8362 t.Parallel() 8363 testCases := []struct { 8364 name string 8365 prowConfig string 8366 supplementalProwConfigs []string 8367 expectedErrorSubstr string 8368 expectedProwConfig string 8369 }{ 8370 { 8371 name: "Additional branch protection config gets merged in", 8372 prowConfig: "config_version_sha: abc", 8373 supplementalProwConfigs: []string{ 8374 ` 8375 branch-protection: 8376 allow_disabled_job_policies: true`, 8377 }, 8378 expectedProwConfig: `branch-protection: 8379 allow_disabled_job_policies: true 8380 config_version_sha: abc 8381 deck: 8382 spyglass: 8383 gcs_browser_prefixes: 8384 '*': "" 8385 gcs_browser_prefixes_by_bucket: 8386 '*': "" 8387 size_limit: 100000000 8388 tide_update_period: 10s 8389 default_job_timeout: 24h0m0s 8390 gangway: {} 8391 gerrit: 8392 ratelimit: 5 8393 tick_interval: 1m0s 8394 github: 8395 link_url: https://github.com 8396 github_reporter: 8397 job_types_to_report: 8398 - presubmit 8399 - postsubmit 8400 horologium: {} 8401 in_repo_config: 8402 allowed_clusters: 8403 '*': 8404 - default 8405 log_level: info 8406 managed_webhooks: 8407 auto_accept_invitation: false 8408 respect_legacy_global_token: false 8409 moonraker: 8410 client_timeout: 10m0s 8411 plank: 8412 max_goroutines: 20 8413 pod_pending_timeout: 10m0s 8414 pod_running_timeout: 48h0m0s 8415 pod_unscheduled_timeout: 5m0s 8416 pod_namespace: default 8417 prowjob_namespace: default 8418 push_gateway: 8419 interval: 1m0s 8420 serve_metrics: false 8421 scheduler: {} 8422 sinker: 8423 max_pod_age: 24h0m0s 8424 max_prowjob_age: 168h0m0s 8425 resync_period: 1h0m0s 8426 terminated_pod_ttl: 24h0m0s 8427 status_error_link: https://github.com/kubernetes/test-infra/issues 8428 tide: 8429 context_options: {} 8430 max_goroutines: 20 8431 status_update_period: 1m0s 8432 sync_period: 1m0s 8433 `, 8434 }, 8435 { 8436 name: "Additional branch protection config with duplication errors", 8437 prowConfig: "config_version_sha: abc", 8438 supplementalProwConfigs: []string{ 8439 ` 8440 branch-protection: 8441 allow_disabled_job_policies: true`, 8442 ` 8443 branch-protection: 8444 allow_disabled_job_policies: true`, 8445 }, 8446 expectedErrorSubstr: "both branchprotection configs set allow_disabled_job_policies", 8447 }, 8448 { 8449 name: "Config not supported by merge logic errors", 8450 prowConfig: "config_version_sha: abc", 8451 supplementalProwConfigs: []string{ 8452 ` 8453 plank: 8454 JobURLPrefixDisableAppendStorageProvider: true`, 8455 }, 8456 expectedErrorSubstr: "may be set via additional config, all other fields have no merging logic yet. Diff:", 8457 }, 8458 { 8459 name: "Additional merge method config gets merged in", 8460 supplementalProwConfigs: []string{` 8461 tide: 8462 merge_method: 8463 foo/bar: squash`}, 8464 expectedProwConfig: `branch-protection: {} 8465 deck: 8466 spyglass: 8467 gcs_browser_prefixes: 8468 '*': "" 8469 gcs_browser_prefixes_by_bucket: 8470 '*': "" 8471 size_limit: 100000000 8472 tide_update_period: 10s 8473 default_job_timeout: 24h0m0s 8474 gangway: {} 8475 gerrit: 8476 ratelimit: 5 8477 tick_interval: 1m0s 8478 github: 8479 link_url: https://github.com 8480 github_reporter: 8481 job_types_to_report: 8482 - presubmit 8483 - postsubmit 8484 horologium: {} 8485 in_repo_config: 8486 allowed_clusters: 8487 '*': 8488 - default 8489 log_level: info 8490 managed_webhooks: 8491 auto_accept_invitation: false 8492 respect_legacy_global_token: false 8493 moonraker: 8494 client_timeout: 10m0s 8495 plank: 8496 max_goroutines: 20 8497 pod_pending_timeout: 10m0s 8498 pod_running_timeout: 48h0m0s 8499 pod_unscheduled_timeout: 5m0s 8500 pod_namespace: default 8501 prowjob_namespace: default 8502 push_gateway: 8503 interval: 1m0s 8504 serve_metrics: false 8505 scheduler: {} 8506 sinker: 8507 max_pod_age: 24h0m0s 8508 max_prowjob_age: 168h0m0s 8509 resync_period: 1h0m0s 8510 terminated_pod_ttl: 24h0m0s 8511 status_error_link: https://github.com/kubernetes/test-infra/issues 8512 tide: 8513 context_options: {} 8514 max_goroutines: 20 8515 merge_method: 8516 foo/bar: squash 8517 status_update_period: 1m0s 8518 sync_period: 1m0s 8519 `, 8520 }, 8521 { 8522 name: "Additional tide queries get merged in and de-duplicated", 8523 prowConfig: ` 8524 tide: 8525 queries: 8526 - labels: 8527 - lgtm 8528 - approved 8529 repos: 8530 - a/repo 8531 `, 8532 supplementalProwConfigs: []string{` 8533 tide: 8534 queries: 8535 - labels: 8536 - lgtm 8537 - approved 8538 repos: 8539 - another/repo 8540 `}, 8541 expectedProwConfig: `branch-protection: {} 8542 deck: 8543 spyglass: 8544 gcs_browser_prefixes: 8545 '*': "" 8546 gcs_browser_prefixes_by_bucket: 8547 '*': "" 8548 size_limit: 100000000 8549 tide_update_period: 10s 8550 default_job_timeout: 24h0m0s 8551 gangway: {} 8552 gerrit: 8553 ratelimit: 5 8554 tick_interval: 1m0s 8555 github: 8556 link_url: https://github.com 8557 github_reporter: 8558 job_types_to_report: 8559 - presubmit 8560 - postsubmit 8561 horologium: {} 8562 in_repo_config: 8563 allowed_clusters: 8564 '*': 8565 - default 8566 log_level: info 8567 managed_webhooks: 8568 auto_accept_invitation: false 8569 respect_legacy_global_token: false 8570 moonraker: 8571 client_timeout: 10m0s 8572 plank: 8573 max_goroutines: 20 8574 pod_pending_timeout: 10m0s 8575 pod_running_timeout: 48h0m0s 8576 pod_unscheduled_timeout: 5m0s 8577 pod_namespace: default 8578 prowjob_namespace: default 8579 push_gateway: 8580 interval: 1m0s 8581 serve_metrics: false 8582 scheduler: {} 8583 sinker: 8584 max_pod_age: 24h0m0s 8585 max_prowjob_age: 168h0m0s 8586 resync_period: 1h0m0s 8587 terminated_pod_ttl: 24h0m0s 8588 status_error_link: https://github.com/kubernetes/test-infra/issues 8589 tide: 8590 context_options: {} 8591 max_goroutines: 20 8592 queries: 8593 - labels: 8594 - approved 8595 - lgtm 8596 repos: 8597 - a/repo 8598 - another/repo 8599 status_update_period: 1m0s 8600 sync_period: 1m0s 8601 `, 8602 }, 8603 { 8604 name: "Additional slack reporter config gets merged in", 8605 prowConfig: "config_version_sha: abc", 8606 supplementalProwConfigs: []string{ 8607 ` 8608 slack_reporter_configs: 8609 my-org: 8610 channel: '#channel' 8611 job_states_to_report: 8612 - failure 8613 - error 8614 job_types_to_report: 8615 - periodic 8616 report_template: Job {{.Spec.Job}} ended with state {{.Status.State}}. 8617 my-org/my-repo: 8618 channel: '#other-channel' 8619 report_template: Job {{.Spec.Job}} ended with state {{.Status.State}}. 8620 `, 8621 }, 8622 expectedProwConfig: `branch-protection: {} 8623 config_version_sha: abc 8624 deck: 8625 spyglass: 8626 gcs_browser_prefixes: 8627 '*': "" 8628 gcs_browser_prefixes_by_bucket: 8629 '*': "" 8630 size_limit: 100000000 8631 tide_update_period: 10s 8632 default_job_timeout: 24h0m0s 8633 gangway: {} 8634 gerrit: 8635 ratelimit: 5 8636 tick_interval: 1m0s 8637 github: 8638 link_url: https://github.com 8639 github_reporter: 8640 job_types_to_report: 8641 - presubmit 8642 - postsubmit 8643 horologium: {} 8644 in_repo_config: 8645 allowed_clusters: 8646 '*': 8647 - default 8648 log_level: info 8649 managed_webhooks: 8650 auto_accept_invitation: false 8651 respect_legacy_global_token: false 8652 moonraker: 8653 client_timeout: 10m0s 8654 plank: 8655 max_goroutines: 20 8656 pod_pending_timeout: 10m0s 8657 pod_running_timeout: 48h0m0s 8658 pod_unscheduled_timeout: 5m0s 8659 pod_namespace: default 8660 prowjob_namespace: default 8661 push_gateway: 8662 interval: 1m0s 8663 serve_metrics: false 8664 scheduler: {} 8665 sinker: 8666 max_pod_age: 24h0m0s 8667 max_prowjob_age: 168h0m0s 8668 resync_period: 1h0m0s 8669 terminated_pod_ttl: 24h0m0s 8670 slack_reporter_configs: 8671 my-org: 8672 channel: '#channel' 8673 job_states_to_report: 8674 - failure 8675 - error 8676 job_types_to_report: 8677 - periodic 8678 report_template: Job {{.Spec.Job}} ended with state {{.Status.State}}. 8679 my-org/my-repo: 8680 channel: '#other-channel' 8681 report_template: Job {{.Spec.Job}} ended with state {{.Status.State}}. 8682 status_error_link: https://github.com/kubernetes/test-infra/issues 8683 tide: 8684 context_options: {} 8685 max_goroutines: 20 8686 status_update_period: 1m0s 8687 sync_period: 1m0s 8688 `, 8689 }, 8690 { 8691 name: "Additional slack reporter config with duplication errors", 8692 prowConfig: "config_version_sha: abc", 8693 supplementalProwConfigs: []string{ 8694 ` 8695 slack_reporter_configs: 8696 my-org: 8697 channel: '#channel'`, 8698 ` 8699 slack_reporter_configs: 8700 my-org: 8701 channel: '#other-channel'`, 8702 }, 8703 expectedErrorSubstr: "config for org or repo my-org passed more than once", 8704 }, 8705 } 8706 8707 for _, tc := range testCases { 8708 t.Run(tc.name, func(t *testing.T) { 8709 config, err := loadConfigYaml(tc.prowConfig, t, tc.supplementalProwConfigs...) 8710 if !strings.Contains(fmt.Sprintf("%v", err), tc.expectedErrorSubstr) { 8711 t.Fatalf("expected error %v to contain string %s", err, tc.expectedErrorSubstr) 8712 } else if err != nil && tc.expectedErrorSubstr == "" { 8713 t.Fatalf("config loading errored: %v", err) 8714 } 8715 if config == nil && tc.expectedProwConfig == "" { 8716 return 8717 } 8718 8719 serialized, err := yaml.Marshal(config) 8720 if err != nil { 8721 t.Fatalf("failed to serialize prow config: %v", err) 8722 } 8723 if diff := cmp.Diff(tc.expectedProwConfig, string(serialized)); diff != "" { 8724 t.Errorf("expected prow config differs from actual: %s", diff) 8725 } 8726 }) 8727 } 8728 } 8729 8730 func TestContextDescriptionWithBaseShaRoundTripping(t *testing.T) { 8731 t.Parallel() 8732 testCases := []struct { 8733 name string 8734 shaIn string 8735 expectedSha string 8736 }{ 8737 { 8738 name: "Valid SHA is returned", 8739 shaIn: "8d287a3aeae90fd0aef4a70009c715712ff302cd", 8740 expectedSha: "8d287a3aeae90fd0aef4a70009c715712ff302cd", 8741 }, 8742 { 8743 name: "Invalid sha is not returned", 8744 shaIn: "abc", 8745 expectedSha: "", 8746 }, 8747 } 8748 8749 for _, tc := range testCases { 8750 t.Run(tc.name, func(t *testing.T) { 8751 for i := 0; i < 100; i++ { 8752 var humanReadable string 8753 fuzz.New().Fuzz(&humanReadable) 8754 contextDescription := ContextDescriptionWithBaseSha(humanReadable, tc.shaIn) 8755 if l := len(contextDescription); l > contextDescriptionMaxLen { 8756 t.Errorf("Context description %q generated from humanReadable %q and baseSHa %q is longer than %d (%d)", contextDescription, humanReadable, tc.shaIn, contextDescriptionMaxLen, l) 8757 } 8758 8759 if expected, actual := tc.expectedSha, BaseSHAFromContextDescription(contextDescription); expected != actual { 8760 t.Errorf("expected to get sha %q back, got %q", expected, actual) 8761 } 8762 } 8763 }) 8764 } 8765 } 8766 8767 func shout(i int) string { 8768 if i == 0 { 8769 return "start" 8770 } 8771 return fmt.Sprintf("%s part%d", shout(i-1), i) 8772 } 8773 8774 func TestTruncate(t *testing.T) { 8775 if el := len(elide) * 2; contextDescriptionMaxLen < el { 8776 t.Fatalf("maxLen must be at least %d (twice %s), got %d", el, elide, contextDescriptionMaxLen) 8777 } 8778 if s := shout(contextDescriptionMaxLen); len(s) <= contextDescriptionMaxLen { 8779 t.Fatalf("%s should be at least %d, got %d", s, contextDescriptionMaxLen, len(s)) 8780 } 8781 big := shout(contextDescriptionMaxLen) 8782 outLen := contextDescriptionMaxLen 8783 if (contextDescriptionMaxLen-len(elide))%2 == 1 { 8784 outLen-- 8785 } 8786 cases := []struct { 8787 name string 8788 in string 8789 out string 8790 outLen int 8791 front string 8792 back string 8793 middle string 8794 }{ 8795 { 8796 name: "do not change short strings", 8797 in: "foo", 8798 out: "foo", 8799 }, 8800 { 8801 name: "do not change at boundary", 8802 in: big[:contextDescriptionMaxLen], 8803 out: big[:contextDescriptionMaxLen], 8804 }, 8805 { 8806 name: "do not change boundary-1", 8807 in: big[:contextDescriptionMaxLen-1], 8808 out: big[:contextDescriptionMaxLen-1], 8809 }, 8810 { 8811 name: "truncated messages have the right length", 8812 in: big, 8813 outLen: outLen, 8814 }, 8815 { 8816 name: "truncated message include beginning", 8817 in: big, 8818 front: big[:contextDescriptionMaxLen/4], // include a lot of the start 8819 }, 8820 { 8821 name: "truncated messages include ending", 8822 in: big, 8823 back: big[len(big)-contextDescriptionMaxLen/4:], 8824 }, 8825 { 8826 name: "truncated messages include a ...", 8827 in: big, 8828 middle: elide, 8829 }, 8830 } 8831 8832 for _, tc := range cases { 8833 t.Run(tc.name, func(t *testing.T) { 8834 out := truncate(tc.in, contextDescriptionMaxLen) 8835 exact := true 8836 if tc.front != "" { 8837 exact = false 8838 if !strings.HasPrefix(out, tc.front) { 8839 t.Errorf("%s does not start with %s", out, tc.front) 8840 } 8841 } 8842 if tc.middle != "" { 8843 exact = false 8844 if !strings.Contains(out, tc.middle) { 8845 t.Errorf("%s does not contain %s", out, tc.middle) 8846 } 8847 } 8848 if tc.back != "" { 8849 exact = false 8850 if !strings.HasSuffix(out, tc.back) { 8851 t.Errorf("%s does not end with %s", out, tc.back) 8852 } 8853 } 8854 if tc.outLen > 0 { 8855 exact = false 8856 if len(out) != tc.outLen { 8857 t.Errorf("%s len %d != expected %d", out, len(out), tc.outLen) 8858 } 8859 } 8860 if exact && out != tc.out { 8861 t.Errorf("%s != expected %s", out, tc.out) 8862 } 8863 }) 8864 } 8865 } 8866 8867 func TestHasConfigFor(t *testing.T) { 8868 t.Parallel() 8869 testCases := []struct { 8870 name string 8871 resultGenerator func(fuzzedConfig *ProwConfig) (toCheck *ProwConfig, exceptGlobal bool, expectOrgs, expectRepos sets.Set[string]) 8872 }{ 8873 { 8874 name: "Any non-empty config with empty branchprotection and Tide properties is considered global", 8875 resultGenerator: func(fuzzedConfig *ProwConfig) (toCheck *ProwConfig, exceptGlobal bool, expectOrgs, expectRepos sets.Set[string]) { 8876 fuzzedConfig.BranchProtection = BranchProtection{} 8877 fuzzedConfig.SlackReporterConfigs = SlackReporterConfigs{} 8878 fuzzedConfig.Tide.MergeType = nil 8879 fuzzedConfig.Tide.Queries = nil 8880 return fuzzedConfig, true, nil, nil 8881 }, 8882 }, 8883 { 8884 name: "Any config that is empty except for branchprotection.orgs with empty repo is considered to be for those orgs", 8885 resultGenerator: func(fuzzedConfig *ProwConfig) (toCheck *ProwConfig, exceptGlobal bool, expectOrgs, expectRepos sets.Set[string]) { 8886 expectOrgs = sets.Set[string]{} 8887 result := &ProwConfig{BranchProtection: BranchProtection{Orgs: map[string]Org{}}} 8888 for org, orgVal := range fuzzedConfig.BranchProtection.Orgs { 8889 orgVal.Repos = nil 8890 expectOrgs.Insert(org) 8891 result.BranchProtection.Orgs[org] = orgVal 8892 } 8893 return result, false, expectOrgs, nil 8894 }, 8895 }, 8896 { 8897 name: "Any config that is empty except for repos in branchprotection config is considered to be for those repos", 8898 resultGenerator: func(fuzzedConfig *ProwConfig) (toCheck *ProwConfig, exceptGlobal bool, expectOrgs, expectRepos sets.Set[string]) { 8899 expectRepos = sets.Set[string]{} 8900 result := &ProwConfig{BranchProtection: BranchProtection{Orgs: map[string]Org{}}} 8901 for org, orgVal := range fuzzedConfig.BranchProtection.Orgs { 8902 result.BranchProtection.Orgs[org] = Org{Repos: map[string]Repo{}} 8903 for repo, repoVal := range orgVal.Repos { 8904 expectRepos.Insert(org + "/" + repo) 8905 result.BranchProtection.Orgs[org].Repos[repo] = repoVal 8906 } 8907 } 8908 return result, false, nil, expectRepos 8909 }, 8910 }, 8911 { 8912 name: "Any config that is empty except for tide.merge_method is considered to be for those orgs or repos", 8913 resultGenerator: func(fuzzedConfig *ProwConfig) (toCheck *ProwConfig, exceptGlobal bool, expectOrgs, expectRepos sets.Set[string]) { 8914 expectOrgs, expectRepos = sets.Set[string]{}, sets.Set[string]{} 8915 result := &ProwConfig{Tide: Tide{TideGitHubConfig: TideGitHubConfig{MergeType: fuzzedConfig.Tide.MergeType}}} 8916 for orgOrRepo := range result.Tide.MergeType { 8917 if strings.Contains(orgOrRepo, "/") { 8918 expectRepos.Insert(orgOrRepo) 8919 } else { 8920 expectOrgs.Insert(orgOrRepo) 8921 } 8922 } 8923 8924 return result, false, expectOrgs, expectRepos 8925 }, 8926 }, 8927 { 8928 name: "Any config that is empty except for tide.queries is considered to be for those orgs or repos", 8929 resultGenerator: func(fuzzedConfig *ProwConfig) (toCheck *ProwConfig, exceptGlobal bool, expectOrgs, expectRepos sets.Set[string]) { 8930 expectOrgs, expectRepos = sets.Set[string]{}, sets.Set[string]{} 8931 result := &ProwConfig{Tide: Tide{TideGitHubConfig: TideGitHubConfig{Queries: fuzzedConfig.Tide.Queries}}} 8932 for _, query := range result.Tide.Queries { 8933 expectOrgs.Insert(query.Orgs...) 8934 expectRepos.Insert(query.Repos...) 8935 } 8936 8937 return result, false, expectOrgs, expectRepos 8938 }, 8939 }, 8940 } 8941 8942 seed := time.Now().UnixNano() 8943 // Print the seed so failures can easily be reproduced 8944 t.Logf("Seed: %d", seed) 8945 fuzzer := fuzz.NewWithSeed(seed). 8946 // The fuzzer doesn't know what to put into interface fields, so we have to custom handle them. 8947 Funcs( 8948 // This is not an interface, but it contains an interface type. Handling the interface type 8949 // itself makes the bazel-built tests panic with a nullpointer deref but works fine with 8950 // go test. 8951 func(t *template.Template, _ fuzz.Continue) { 8952 *t = *template.New("whatever") 8953 }, 8954 func(*labels.Selector, fuzz.Continue) {}, 8955 ) 8956 8957 for _, tc := range testCases { 8958 t.Run(tc.name, func(t *testing.T) { 8959 for i := 0; i < 100; i++ { 8960 fuzzedConfig := &ProwConfig{} 8961 fuzzedConfig.SlackReporterConfigs = SlackReporterConfigs{} 8962 fuzzer.Fuzz(fuzzedConfig) 8963 8964 fuzzedAndManipulatedConfig, expectIsGlobal, expectOrgs, expectRepos := tc.resultGenerator(fuzzedConfig) 8965 actualIsGlobal, actualOrgs, actualRepos := fuzzedAndManipulatedConfig.HasConfigFor() 8966 8967 if expectIsGlobal != actualIsGlobal { 8968 t.Errorf("exepcted isGlobal: %t, got: %t", expectIsGlobal, actualIsGlobal) 8969 } 8970 8971 if diff := cmp.Diff(expectOrgs, actualOrgs); diff != "" { 8972 t.Errorf("expected orgs differ from actual: %s", diff) 8973 } 8974 8975 if diff := cmp.Diff(expectRepos, actualRepos); diff != "" { 8976 t.Errorf("expected repos differ from actual: %s", diff) 8977 } 8978 } 8979 8980 }) 8981 } 8982 } 8983 8984 func TestCalculateStorageBuckets(t *testing.T) { 8985 t.Parallel() 8986 testCases := []struct { 8987 name string 8988 in *Config 8989 expected sets.Set[string] 8990 }{ 8991 { 8992 name: "S3 provider prefix gets removed from Plank config", 8993 in: &Config{ProwConfig: ProwConfig{Plank: Plank{DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{{Config: &prowapi.DecorationConfig{ 8994 GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "s3://prow-logs"}, 8995 }}}}}}, 8996 expected: sets.New[string]("prow-logs"), 8997 }, 8998 { 8999 name: "GS provider prefix gets removed from Plank config", 9000 in: &Config{ProwConfig: ProwConfig{Plank: Plank{DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{{Config: &prowapi.DecorationConfig{ 9001 GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "gs://prow-logs"}, 9002 }}}}}}, 9003 expected: sets.New[string]("prow-logs"), 9004 }, 9005 { 9006 name: "No provider prefix, nothing to do", 9007 in: &Config{ProwConfig: ProwConfig{Plank: Plank{DefaultDecorationConfigs: []*DefaultDecorationConfigEntry{{Config: &prowapi.DecorationConfig{ 9008 GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "kubernetes-jenkins"}, 9009 }}}}}}, 9010 expected: sets.New[string]("kubernetes-jenkins"), 9011 }, 9012 { 9013 name: "S3 provider prefix gets removed from periodic config", 9014 in: &Config{JobConfig: JobConfig{Periodics: []Periodic{{JobBase: JobBase{UtilityConfig: UtilityConfig{DecorationConfig: &prowapi.DecorationConfig{ 9015 GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "s3://prow-logs"}, 9016 }}}}}}}, 9017 expected: sets.New[string]("prow-logs"), 9018 }, 9019 { 9020 name: "S3 provider prefix gets removed from presubmit config", 9021 in: &Config{JobConfig: JobConfig{PresubmitsStatic: map[string][]Presubmit{"": {{JobBase: JobBase{UtilityConfig: UtilityConfig{DecorationConfig: &prowapi.DecorationConfig{ 9022 GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "s3://prow-logs"}, 9023 }}}}}}}}, 9024 expected: sets.New[string]("prow-logs"), 9025 }, 9026 { 9027 name: "S3 provider prefix gets removed from postsubmit config", 9028 in: &Config{JobConfig: JobConfig{PostsubmitsStatic: map[string][]Postsubmit{"": {{JobBase: JobBase{UtilityConfig: UtilityConfig{DecorationConfig: &prowapi.DecorationConfig{ 9029 GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "s3://prow-logs"}, 9030 }}}}}}}}, 9031 expected: sets.New[string]("prow-logs"), 9032 }, 9033 } 9034 9035 for _, tc := range testCases { 9036 t.Run(tc.name, func(t *testing.T) { 9037 actual := calculateStorageBuckets(tc.in) 9038 if diff := cmp.Diff(tc.expected, actual); diff != "" { 9039 t.Errorf("actual differs from expected") 9040 } 9041 }) 9042 } 9043 } 9044 9045 func TestProwConfigMergingProperties(t *testing.T) { 9046 t.Parallel() 9047 testCases := []struct { 9048 name string 9049 makeMergeable func(*ProwConfig) 9050 }{ 9051 { 9052 name: "Branchprotection config", 9053 makeMergeable: func(pc *ProwConfig) { 9054 *pc = ProwConfig{BranchProtection: pc.BranchProtection} 9055 }, 9056 }, 9057 { 9058 name: "Tide merge method", 9059 makeMergeable: func(pc *ProwConfig) { 9060 *pc = ProwConfig{Tide: Tide{TideGitHubConfig: TideGitHubConfig{MergeType: pc.Tide.MergeType}}} 9061 }, 9062 }, 9063 { 9064 name: "Tide queries", 9065 makeMergeable: func(pc *ProwConfig) { 9066 *pc = ProwConfig{Tide: Tide{TideGitHubConfig: TideGitHubConfig{Queries: pc.Tide.Queries}}} 9067 }, 9068 }, 9069 { 9070 name: "SlackReporter configurations", 9071 makeMergeable: func(pc *ProwConfig) { 9072 *pc = ProwConfig{SlackReporterConfigs: pc.SlackReporterConfigs} 9073 }, 9074 }, 9075 } 9076 9077 expectedProperties := []struct { 9078 name string 9079 verification func(t *testing.T, fuzzedConfig *ProwConfig) 9080 }{ 9081 { 9082 name: "Merging into empty config always succeeds and makes the empty config equal to the one that was merged in", 9083 verification: func(t *testing.T, fuzzedMergeableConfig *ProwConfig) { 9084 newConfig := &ProwConfig{} 9085 if err := newConfig.mergeFrom(fuzzedMergeableConfig); err != nil { 9086 t.Fatalf("merging fuzzed mergeable config into empty config failed: %v", err) 9087 } 9088 if diff := cmp.Diff(newConfig, fuzzedMergeableConfig, DefaultDiffOpts...); diff != "" { 9089 t.Errorf("after merging config into an empty config, the config that was merged into differs from the one we merged from:\n%s\n", diff) 9090 } 9091 }, 9092 }, 9093 { 9094 name: "Merging empty config in always succeeds", 9095 verification: func(t *testing.T, fuzzedMergeableConfig *ProwConfig) { 9096 if err := fuzzedMergeableConfig.mergeFrom(&ProwConfig{}); err != nil { 9097 t.Errorf("merging empty config in failed: %v", err) 9098 } 9099 }, 9100 }, 9101 { 9102 name: "Merging a config into itself always fails", 9103 verification: func(t *testing.T, fuzzedMergeableConfig *ProwConfig) { 9104 if apiequality.Semantic.DeepEqual(fuzzedMergeableConfig, &ProwConfig{}) { 9105 return 9106 } 9107 9108 // One exception: Tide queries can be merged into themselves, as we just de-duplicate them 9109 // later on. 9110 if len(fuzzedMergeableConfig.Tide.Queries) > 0 { 9111 return 9112 } 9113 9114 // Another exception: A non-nil branchprotection config with only empty policies 9115 // can be merged into itself so make sure this can't happen. 9116 if !apiequality.Semantic.DeepEqual(fuzzedMergeableConfig.BranchProtection, BranchProtection{}) { 9117 fuzzedMergeableConfig.BranchProtection.Exclude = []string{"foo"} 9118 } 9119 9120 if err := fuzzedMergeableConfig.mergeFrom(fuzzedMergeableConfig); err == nil { 9121 serialized, serializeErr := yaml.Marshal(fuzzedMergeableConfig) 9122 if serializeErr != nil { 9123 t.Fatalf("merging non-empty config into itself did not yield an error and serializing it afterwards failed: %v. Raw object: %+v", serializeErr, fuzzedMergeableConfig) 9124 } 9125 t.Errorf("merging a config into itself did not produce an error. Serialized config:\n%s", string(serialized)) 9126 } 9127 }, 9128 }, 9129 } 9130 9131 seed := time.Now().UnixNano() 9132 // Print the seed so failures can easily be reproduced 9133 t.Logf("Seed: %d", seed) 9134 var i int 9135 fuzzer := fuzz.NewWithSeed(seed). 9136 // The fuzzer doesn't know what to put into interface fields, so we have to custom handle them. 9137 Funcs( 9138 // This is not an interface, but it contains an interface type. Handling the interface type 9139 // itself makes the bazel-built tests panic with a nullpointer deref but works fine with 9140 // go test. 9141 func(t *template.Template, _ fuzz.Continue) { 9142 *t = *template.New("whatever") 9143 }, 9144 func(*labels.Selector, fuzz.Continue) {}, 9145 func(p *Policy, c fuzz.Continue) { 9146 // Make sure we always have a good sample of non-nil but empty Policies so 9147 // we check that they get copied over. Today, the meaning of an empty and 9148 // an unset Policy is identical because all the fields are pointers that 9149 // will get ignored if unset. However, this might change in the future and 9150 // caused flakes when we didn't copy over map entries with an empty Policy, 9151 // as an entry with no value and no entry are different things for cmp.Diff. 9152 if i%2 == 0 { 9153 c.Fuzz(p) 9154 } 9155 i++ 9156 }, 9157 func(config *SlackReporterConfigs, c fuzz.Continue) { 9158 for _, reporter := range *config { 9159 c.Fuzz(reporter) 9160 } 9161 }, 9162 ) 9163 9164 // Do not parallelize, the PRNG used by the fuzzer is not threadsafe 9165 for _, tc := range testCases { 9166 t.Run(tc.name, func(t *testing.T) { 9167 9168 for _, propertyTest := range expectedProperties { 9169 t.Run(propertyTest.name, func(t *testing.T) { 9170 9171 for i := 0; i < 100; i++ { 9172 fuzzedConfig := &ProwConfig{} 9173 fuzzedConfig.SlackReporterConfigs = map[string]SlackReporter{} 9174 fuzzer.Fuzz(fuzzedConfig) 9175 9176 tc.makeMergeable(fuzzedConfig) 9177 9178 propertyTest.verification(t, fuzzedConfig) 9179 } 9180 }) 9181 } 9182 }) 9183 } 9184 } 9185 9186 func TestEnsureConfigIsDiffable(t *testing.T) { 9187 // This will panic in case it is not able to diff 'Config'. 9188 _ = cmp.Diff(Config{}, Config{}, DefaultDiffOpts...) 9189 } 9190 9191 // TestDeduplicateTideQueriesDoesntLoseData simply uses deduplicateTideQueries 9192 // on a single fuzzed tidequery, which should never result in any change as 9193 // there is nothing that could be deduplicated. This is mostly to ensure we 9194 // don't forget to change our code when new fields get added to the type. 9195 func TestDeduplicateTideQueriesDoesntLoseData(t *testing.T) { 9196 config := &Config{} 9197 for i := 0; i < 100; i++ { 9198 t.Run(strconv.Itoa(i), func(t *testing.T) { 9199 query := TideQuery{} 9200 fuzz.New().Fuzz(&query) 9201 result, err := config.deduplicateTideQueries(TideQueries{query}) 9202 if err != nil { 9203 t.Fatalf("error: %v", err) 9204 } 9205 9206 if diff := cmp.Diff(result[0], query); diff != "" { 9207 t.Errorf("result differs from initial query: %s", diff) 9208 } 9209 }) 9210 } 9211 } 9212 9213 func TestDeduplicateTideQueries(t *testing.T) { 9214 testCases := []struct { 9215 name string 9216 in TideQueries 9217 prowJobDefaultEntries []*ProwJobDefaultEntry 9218 expected TideQueries 9219 }{ 9220 { 9221 name: "No overlap", 9222 in: TideQueries{ 9223 {Orgs: []string{"kubernetes"}, Labels: []string{"merge-me"}}, 9224 {Orgs: []string{"kubernetes-priv"}, Labels: []string{"merge-me-differently"}}, 9225 }, 9226 expected: TideQueries{ 9227 {Orgs: []string{"kubernetes"}, Labels: []string{"merge-me"}}, 9228 {Orgs: []string{"kubernetes-priv"}, Labels: []string{"merge-me-differently"}}, 9229 }, 9230 }, 9231 { 9232 name: "Queries get deduplicated", 9233 in: TideQueries{ 9234 {Orgs: []string{"kubernetes"}, Labels: []string{"merge-me"}}, 9235 {Orgs: []string{"kubernetes-priv"}, Labels: []string{"merge-me"}}, 9236 }, 9237 expected: TideQueries{{Orgs: []string{"kubernetes", "kubernetes-priv"}, Labels: []string{"merge-me"}}}, 9238 }, 9239 { 9240 name: "Queries get deduplicated regardless of element order", 9241 in: TideQueries{ 9242 {Orgs: []string{"kubernetes"}, Labels: []string{"lgtm", "merge-me"}}, 9243 {Orgs: []string{"kubernetes-priv"}, Labels: []string{"merge-me", "lgtm"}}, 9244 }, 9245 expected: TideQueries{{Orgs: []string{"kubernetes", "kubernetes-priv"}, Labels: []string{"lgtm", "merge-me"}}}, 9246 }, 9247 { 9248 name: "Queries with different tenantIds don't get deduplicated", 9249 in: TideQueries{ 9250 {Repos: []string{"kubernetes/test-infra"}, Labels: []string{"merge-me"}}, 9251 {Repos: []string{"kubernetes/tenanted"}, Labels: []string{"merge-me"}}, 9252 {Repos: []string{"kubernetes/other"}, Labels: []string{"merge-me"}}, 9253 }, 9254 prowJobDefaultEntries: []*ProwJobDefaultEntry{ 9255 {OrgRepo: "kubernetes/tenanted", Config: &prowapi.ProwJobDefault{TenantID: "tenanted"}}, 9256 }, 9257 expected: TideQueries{ 9258 {Repos: []string{"kubernetes/tenanted"}, Labels: []string{"merge-me"}}, 9259 {Repos: []string{"kubernetes/test-infra", "kubernetes/other"}, Labels: []string{"merge-me"}}, 9260 }, 9261 }, 9262 } 9263 9264 for _, tc := range testCases { 9265 t.Run(tc.name, func(t *testing.T) { 9266 config := &Config{} 9267 config.ProwConfig.ProwJobDefaultEntries = append(config.ProwConfig.ProwJobDefaultEntries, tc.prowJobDefaultEntries...) 9268 result, err := config.deduplicateTideQueries(tc.in) 9269 if err != nil { 9270 t.Fatalf("failed: %v", err) 9271 } 9272 9273 if diff := cmp.Diff(result, tc.expected); diff != "" { 9274 t.Errorf("Result differs from expected: %v", diff) 9275 } 9276 }) 9277 } 9278 } 9279 9280 func TestParseTideMergeType(t *testing.T) { 9281 exportRegexp := cmp.AllowUnexported(TideBranchMergeType{}) 9282 regexpComparer := cmp.Comparer(func(a, b *regexp.Regexp) bool { 9283 return (a == nil && b == nil) || (a != nil && b != nil) && (a.String() == b.String()) 9284 }) 9285 errComparer := cmp.Comparer(func(a, b error) bool { 9286 return (a == nil && b == nil) || (a != nil && b != nil) && (a.Error() == b.Error()) 9287 }) 9288 sortSlices := cmpopts.SortSlices(func(a, b error) bool { 9289 return a.Error() < b.Error() 9290 }) 9291 for _, tc := range []struct { 9292 name string 9293 mergeTypes map[string]TideOrgMergeType 9294 wantTypes map[string]TideOrgMergeType 9295 wantErrs []error 9296 }{ 9297 { 9298 name: "No errors", 9299 mergeTypes: map[string]TideOrgMergeType{ 9300 "k8s": { 9301 MergeType: types.MergeRebase, 9302 }, 9303 "k8s/test": { 9304 MergeType: types.MergeSquash, 9305 }, 9306 "k8s/test@test": { 9307 MergeType: types.MergeSquash, 9308 }, 9309 "kubernetes": { 9310 Repos: map[string]TideRepoMergeType{ 9311 "test-infra": { 9312 MergeType: types.MergeSquash, 9313 }, 9314 }, 9315 }, 9316 "golang": { 9317 Repos: map[string]TideRepoMergeType{ 9318 "go": { 9319 Branches: map[string]TideBranchMergeType{ 9320 "master": { 9321 MergeType: types.MergeMerge, 9322 }, 9323 "main.+": { 9324 MergeType: types.MergeRebase, 9325 }, 9326 }, 9327 }, 9328 }, 9329 }, 9330 }, 9331 wantTypes: map[string]TideOrgMergeType{ 9332 "k8s": { 9333 MergeType: types.MergeRebase, 9334 }, 9335 "k8s/test": { 9336 MergeType: types.MergeSquash, 9337 }, 9338 "k8s/test@test": { 9339 MergeType: types.MergeSquash, 9340 }, 9341 "kubernetes": { 9342 Repos: map[string]TideRepoMergeType{ 9343 "test-infra": { 9344 MergeType: types.MergeSquash, 9345 }, 9346 }, 9347 }, 9348 "golang": { 9349 Repos: map[string]TideRepoMergeType{ 9350 "go": { 9351 Branches: map[string]TideBranchMergeType{ 9352 "master": { 9353 MergeType: types.MergeMerge, 9354 Regexpr: regexp.MustCompile("master"), 9355 }, 9356 "main.+": { 9357 MergeType: types.MergeRebase, 9358 Regexpr: regexp.MustCompile("main.+"), 9359 }, 9360 }, 9361 }, 9362 }, 9363 }, 9364 }, 9365 }, 9366 { 9367 name: "Errors on every config level", 9368 mergeTypes: map[string]TideOrgMergeType{ 9369 "k8s": { 9370 MergeType: "fake-org-mm", 9371 }, 9372 "kubernetes": { 9373 Repos: map[string]TideRepoMergeType{ 9374 "test-infra": { 9375 MergeType: "fake-repo-mm", 9376 }, 9377 "kubernetes": { 9378 Branches: map[string]TideBranchMergeType{ 9379 "main": { 9380 MergeType: "fake-br-mm", 9381 }, 9382 "invalid-regex[": { 9383 MergeType: types.MergeMerge, 9384 }, 9385 }, 9386 }, 9387 }, 9388 }, 9389 }, 9390 wantErrs: []error{ 9391 errors.New(`merge type "fake-org-mm" for k8s is not a valid type`), 9392 errors.New(`merge type "fake-repo-mm" for kubernetes/test-infra is not a valid type`), 9393 errors.New(`merge type "fake-br-mm" for kubernetes/kubernetes@main is not a valid type`), 9394 errors.New(`regex "invalid-regex[" is not valid`), 9395 }, 9396 }, 9397 } { 9398 t.Run(tc.name, func(t *testing.T) { 9399 err := parseTideMergeType(tc.mergeTypes) 9400 if len(tc.wantErrs) > 0 { 9401 if err == nil { 9402 t.Errorf("expected err '%v', got nil", tc.wantErrs) 9403 } 9404 // Resulting errors are not guaranteed to be always in the same order, due to how 9405 // hashmap is implemented in Go. We need to sort first and then compare. 9406 if diff := cmp.Diff(tc.wantErrs, err.Errors(), errComparer, sortSlices); diff != "" { 9407 t.Errorf("errors don't match: %v", diff) 9408 } 9409 } else { 9410 if err != nil { 9411 t.Errorf("expected err nil, got '%v'", err) 9412 } 9413 9414 if diff := cmp.Diff(tc.wantTypes, tc.mergeTypes, regexpComparer, exportRegexp); diff != "" { 9415 t.Errorf("tide configs doesn't match: %v", diff) 9416 } 9417 } 9418 }) 9419 } 9420 } 9421 9422 func TestGetAndCheckRefs(t *testing.T) { 9423 type expected struct { 9424 baseSHA string 9425 headSHAs []string 9426 err string 9427 } 9428 9429 for _, tc := range []struct { 9430 name string 9431 baseSHAGetter RefGetter 9432 headSHAGetters []RefGetter 9433 expected expected 9434 }{ 9435 { 9436 name: "Basic", 9437 baseSHAGetter: goodSHAGetter("ba5e"), 9438 headSHAGetters: []RefGetter{ 9439 goodSHAGetter("abcd"), 9440 goodSHAGetter("ef01")}, 9441 expected: expected{ 9442 baseSHA: "ba5e", 9443 headSHAs: []string{"abcd", "ef01"}, 9444 err: "", 9445 }, 9446 }, 9447 { 9448 name: "NoHeadSHAGetters", 9449 baseSHAGetter: goodSHAGetter("ba5e"), 9450 headSHAGetters: []RefGetter{}, 9451 expected: expected{ 9452 baseSHA: "ba5e", 9453 headSHAs: nil, 9454 err: "", 9455 }, 9456 }, 9457 { 9458 name: "BaseSHAGetterFailure", 9459 baseSHAGetter: badSHAGetter, 9460 headSHAGetters: []RefGetter{ 9461 goodSHAGetter("abcd"), 9462 goodSHAGetter("ef01")}, 9463 expected: expected{ 9464 baseSHA: "", 9465 headSHAs: nil, 9466 err: "failed to get baseSHA: badSHAGetter", 9467 }, 9468 }, 9469 { 9470 name: "HeadSHAGetterFailure", 9471 baseSHAGetter: goodSHAGetter("ba5e"), 9472 headSHAGetters: []RefGetter{ 9473 goodSHAGetter("abcd"), 9474 badSHAGetter}, 9475 expected: expected{ 9476 baseSHA: "", 9477 headSHAs: nil, 9478 err: "failed to get headRef: badSHAGetter", 9479 }, 9480 }, 9481 } { 9482 t.Run(tc.name, func(t1 *testing.T) { 9483 baseSHA, headSHAs, err := GetAndCheckRefs(tc.baseSHAGetter, tc.headSHAGetters...) 9484 9485 if tc.expected.err == "" { 9486 if err != nil { 9487 t.Errorf("Expected error 'nil' got '%v'", err.Error()) 9488 } 9489 if tc.expected.baseSHA != baseSHA { 9490 t.Errorf("Expected baseSHA '%v', got '%v'", tc.expected.baseSHA, baseSHA) 9491 } 9492 if !reflect.DeepEqual(tc.expected.headSHAs, headSHAs) { 9493 t.Errorf("headSHAs do not match:\n%s", diff.ObjectReflectDiff(tc.expected.headSHAs, headSHAs)) 9494 } 9495 } else { 9496 if err == nil { 9497 t.Fatal("Expected non-nil error, got nil") 9498 } 9499 9500 if tc.expected.err != err.Error() { 9501 t.Errorf("Expected error '%v', got '%v'", tc.expected.err, err.Error()) 9502 } 9503 } 9504 }) 9505 } 9506 } 9507 9508 func TestSplitRepoName(t *testing.T) { 9509 tests := []struct { 9510 name string 9511 full string 9512 wantOrg string 9513 wantRepo string 9514 wantErr bool 9515 }{ 9516 { 9517 name: "github repo", 9518 full: "orgA/repoB", 9519 wantOrg: "orgA", 9520 wantRepo: "repoB", 9521 wantErr: false, 9522 }, 9523 { 9524 name: "ref name with http://", 9525 full: "http://orgA/repoB", 9526 wantOrg: "http://orgA", 9527 wantRepo: "repoB", 9528 wantErr: false, 9529 }, 9530 { 9531 name: "ref name with https://", 9532 full: "https://orgA/repoB", 9533 wantOrg: "https://orgA", 9534 wantRepo: "repoB", 9535 wantErr: false, 9536 }, 9537 { 9538 name: "repo name contains /", 9539 full: "orgA/repoB/subC", 9540 wantOrg: "orgA", 9541 wantRepo: "repoB/subC", 9542 wantErr: false, 9543 }, 9544 { 9545 name: "invalid", 9546 full: "repoB", 9547 wantOrg: "", 9548 wantRepo: "", 9549 wantErr: true, 9550 }, 9551 } 9552 9553 for _, tt := range tests { 9554 tt := tt 9555 t.Run(tt.name, func(t *testing.T) { 9556 gotOrg, gotRepo, err := SplitRepoName(tt.full) 9557 if gotOrg != tt.wantOrg { 9558 t.Errorf("org mismatch. Want: %v, got: %v", tt.wantOrg, gotOrg) 9559 } 9560 if gotRepo != tt.wantRepo { 9561 t.Errorf("repo mismatch. Want: %v, got: %v", tt.wantRepo, gotRepo) 9562 } 9563 gotErr := (err != nil) 9564 if gotErr != (tt.wantErr && gotErr) { 9565 t.Errorf("err mismatch. Want: %v, got: %v", tt.wantErr, gotErr) 9566 } 9567 }) 9568 } 9569 }