github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/pkg/kptfile/kptfileutil/util_test.go (about) 1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kptfileutil 16 17 import ( 18 "os" 19 "path/filepath" 20 "strings" 21 "testing" 22 23 "github.com/GoogleContainerTools/kpt/internal/pkg" 24 kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" 25 "github.com/stretchr/testify/assert" 26 "sigs.k8s.io/kustomize/kyaml/yaml" 27 ) 28 29 // TestValidateInventory tests the ValidateInventory function. 30 func TestValidateInventory(t *testing.T) { 31 // nil inventory should not validate 32 isValid, err := ValidateInventory(nil) 33 if isValid || err == nil { 34 t.Errorf("nil inventory should not validate") 35 } 36 // Empty inventory should not validate 37 inv := &kptfilev1.Inventory{} 38 isValid, err = ValidateInventory(inv) 39 if isValid || err == nil { 40 t.Errorf("empty inventory should not validate") 41 } 42 // Empty inventory parameters strings should not validate 43 inv = &kptfilev1.Inventory{ 44 Namespace: "", 45 Name: "", 46 InventoryID: "", 47 } 48 isValid, err = ValidateInventory(inv) 49 if isValid || err == nil { 50 t.Errorf("empty inventory parameters strings should not validate") 51 } 52 // Inventory with non-empty namespace, name, and id should validate. 53 inv = &kptfilev1.Inventory{ 54 Namespace: "test-namespace", 55 Name: "test-name", 56 InventoryID: "test-id", 57 } 58 isValid, err = ValidateInventory(inv) 59 if !isValid || err != nil { 60 t.Errorf("inventory with non-empty namespace, name, and id should validate") 61 } 62 } 63 64 func TestUpdateKptfile(t *testing.T) { 65 writeKptfileToTemp := func(tt *testing.T, content string) string { 66 dir := tt.TempDir() 67 err := os.WriteFile(filepath.Join(dir, kptfilev1.KptFileName), []byte(content), 0600) 68 if !assert.NoError(t, err) { 69 t.FailNow() 70 } 71 return dir 72 } 73 74 testCases := map[string]struct { 75 origin string 76 updated string 77 local string 78 updateUpstream bool 79 expected string 80 }{ 81 "no pipeline and no upstream info": { 82 origin: ` 83 apiVersion: kpt.dev/v1 84 kind: Kptfile 85 metadata: 86 name: base 87 `, 88 updated: ` 89 apiVersion: kpt.dev/v1 90 kind: Kptfile 91 metadata: 92 name: base 93 `, 94 local: ` 95 apiVersion: kpt.dev/v1 96 kind: Kptfile 97 metadata: 98 name: foo 99 `, 100 updateUpstream: false, 101 expected: ` 102 apiVersion: kpt.dev/v1 103 kind: Kptfile 104 metadata: 105 name: foo 106 `, 107 }, 108 109 "upstream information is not copied from upstream unless updateUpstream is true": { 110 origin: ` 111 apiVersion: kpt.dev/v1 112 kind: Kptfile 113 metadata: 114 name: foo 115 upstream: 116 type: git 117 git: 118 repo: github.com/GoogleContainerTools/kpt 119 directory: / 120 ref: v1 121 `, 122 updated: ` 123 apiVersion: kpt.dev/v1 124 kind: Kptfile 125 metadata: 126 name: foo 127 upstream: 128 type: git 129 git: 130 repo: github.com/GoogleContainerTools/kpt 131 directory: / 132 ref: v2 133 `, 134 local: ` 135 apiVersion: kpt.dev/v1 136 kind: Kptfile 137 metadata: 138 name: foo 139 `, 140 updateUpstream: false, 141 expected: ` 142 apiVersion: kpt.dev/v1 143 kind: Kptfile 144 metadata: 145 name: foo 146 `, 147 }, 148 149 "upstream information is copied from upstream when updateUpstream is true": { 150 origin: ` 151 apiVersion: kpt.dev/v1 152 kind: Kptfile 153 metadata: 154 name: foo 155 upstream: 156 type: git 157 git: 158 repo: github.com/GoogleContainerTools/kpt 159 directory: / 160 ref: v1 161 `, 162 updated: ` 163 apiVersion: kpt.dev/v1 164 kind: Kptfile 165 metadata: 166 name: foo 167 upstream: 168 type: git 169 git: 170 repo: github.com/GoogleContainerTools/kpt 171 directory: / 172 ref: v2 173 upstreamLock: 174 type: git 175 git: 176 repo: github.com/GoogleContainerTools/kpt 177 directory: / 178 ref: v2 179 commit: abc123 180 `, 181 local: ` 182 apiVersion: kpt.dev/v1 183 kind: Kptfile 184 metadata: 185 name: foo 186 `, 187 updateUpstream: true, 188 expected: ` 189 apiVersion: kpt.dev/v1 190 kind: Kptfile 191 metadata: 192 name: foo 193 upstream: 194 type: git 195 git: 196 repo: github.com/GoogleContainerTools/kpt 197 directory: / 198 ref: v2 199 upstreamLock: 200 type: git 201 git: 202 repo: github.com/GoogleContainerTools/kpt 203 directory: / 204 ref: v2 205 commit: abc123 206 `, 207 }, 208 209 "pipeline in local remains if there are no changes in upstream": { 210 origin: ` 211 apiVersion: kpt.dev/v1 212 kind: Kptfile 213 metadata: 214 name: foo 215 pipeline: 216 mutators: 217 - image: foo:bar 218 `, 219 updated: ` 220 apiVersion: kpt.dev/v1 221 kind: Kptfile 222 metadata: 223 name: foo 224 pipeline: 225 mutators: 226 - image: foo:bar 227 `, 228 local: ` 229 apiVersion: kpt.dev/v1 230 kind: Kptfile 231 metadata: 232 name: foo 233 pipeline: 234 mutators: 235 - image: my:image 236 configMap: 237 foo: bar 238 - image: foo:bar 239 `, 240 updateUpstream: true, 241 expected: ` 242 apiVersion: kpt.dev/v1 243 kind: Kptfile 244 metadata: 245 name: foo 246 pipeline: 247 mutators: 248 - image: my:image 249 configMap: 250 foo: bar 251 - image: foo:bar 252 `, 253 }, 254 255 "pipeline remains if it is only added locally": { 256 origin: ` 257 apiVersion: kpt.dev/v1 258 kind: Kptfile 259 metadata: 260 name: foo 261 `, 262 updated: ` 263 apiVersion: kpt.dev/v1 264 kind: Kptfile 265 metadata: 266 name: foo 267 `, 268 local: ` 269 apiVersion: kpt.dev/v1 270 kind: Kptfile 271 metadata: 272 name: foo 273 pipeline: 274 mutators: 275 - image: my:image 276 - image: foo:bar 277 `, 278 updateUpstream: true, 279 expected: ` 280 apiVersion: kpt.dev/v1 281 kind: Kptfile 282 metadata: 283 name: foo 284 pipeline: 285 mutators: 286 - image: my:image 287 - image: foo:bar 288 `, 289 }, 290 291 "pipeline in local is emptied if it is gone from upstream": { 292 origin: ` 293 apiVersion: kpt.dev/v1 294 kind: Kptfile 295 metadata: 296 name: foo 297 pipeline: 298 mutators: 299 - image: foo:bar 300 `, 301 updated: ` 302 apiVersion: kpt.dev/v1 303 kind: Kptfile 304 metadata: 305 name: foo 306 `, 307 local: ` 308 apiVersion: kpt.dev/v1 309 kind: Kptfile 310 metadata: 311 name: foo 312 pipeline: 313 mutators: 314 - image: my:image 315 - image: foo:bar 316 `, 317 updateUpstream: false, 318 expected: ` 319 apiVersion: kpt.dev/v1 320 kind: Kptfile 321 metadata: 322 name: foo 323 pipeline: {} 324 `, 325 }, 326 "first readinessGate and condition added in upstream": { 327 origin: ` 328 apiVersion: kpt.dev/v1 329 kind: Kptfile 330 metadata: 331 name: foo 332 `, 333 updated: ` 334 apiVersion: kpt.dev/v1 335 kind: Kptfile 336 metadata: 337 name: foo 338 info: 339 readinessGates: 340 - conditionType: foo 341 status: 342 conditions: 343 - type: foo 344 status: "True" 345 reason: reason 346 message: message 347 `, 348 local: ` 349 apiVersion: kpt.dev/v1 350 kind: Kptfile 351 metadata: 352 name: foo 353 `, 354 updateUpstream: false, 355 expected: ` 356 apiVersion: kpt.dev/v1 357 kind: Kptfile 358 metadata: 359 name: foo 360 info: 361 readinessGates: 362 - conditionType: foo 363 status: 364 conditions: 365 - type: foo 366 status: "True" 367 reason: reason 368 message: message 369 `, 370 }, 371 "additional readinessGate and condition added in upstream": { 372 origin: ` 373 apiVersion: kpt.dev/v1 374 kind: Kptfile 375 metadata: 376 name: foo 377 info: 378 readinessGates: 379 - conditionType: foo 380 status: 381 conditions: 382 - type: foo 383 status: "True" 384 reason: reason 385 message: message 386 `, 387 updated: ` 388 apiVersion: kpt.dev/v1 389 kind: Kptfile 390 metadata: 391 name: foo 392 info: 393 readinessGates: 394 - conditionType: foo 395 - conditionType: bar 396 status: 397 conditions: 398 - type: foo 399 status: "True" 400 reason: reason 401 message: message 402 - type: bar 403 status: "False" 404 reason: reason 405 message: message 406 `, 407 local: ` 408 apiVersion: kpt.dev/v1 409 kind: Kptfile 410 metadata: 411 name: foo 412 info: 413 readinessGates: 414 - conditionType: foo 415 status: 416 conditions: 417 - type: foo 418 status: "True" 419 reason: reason 420 message: message 421 `, 422 updateUpstream: false, 423 expected: ` 424 apiVersion: kpt.dev/v1 425 kind: Kptfile 426 metadata: 427 name: foo 428 info: 429 readinessGates: 430 - conditionType: foo 431 - conditionType: bar 432 status: 433 conditions: 434 - type: foo 435 status: "True" 436 reason: reason 437 message: message 438 - type: bar 439 status: "False" 440 reason: reason 441 message: message 442 `, 443 }, 444 "readinessGate added removed in upstream": { 445 origin: ` 446 apiVersion: kpt.dev/v1 447 kind: Kptfile 448 metadata: 449 name: foo 450 info: 451 readinessGates: 452 - conditionType: foo 453 status: 454 conditions: 455 - type: foo 456 status: "True" 457 reason: reason 458 message: message 459 `, 460 updated: ` 461 apiVersion: kpt.dev/v1 462 kind: Kptfile 463 metadata: 464 name: foo 465 `, 466 local: ` 467 apiVersion: kpt.dev/v1 468 kind: Kptfile 469 metadata: 470 name: foo 471 info: 472 readinessGates: 473 - conditionType: foo 474 status: 475 conditions: 476 - type: foo 477 status: "True" 478 reason: reason 479 message: message 480 `, 481 updateUpstream: false, 482 expected: ` 483 apiVersion: kpt.dev/v1 484 kind: Kptfile 485 metadata: 486 name: foo 487 info: {} 488 status: {} 489 `, 490 }, 491 "readinessGates removed and added in both upstream and local": { 492 origin: ` 493 apiVersion: kpt.dev/v1 494 kind: Kptfile 495 metadata: 496 name: foo 497 info: 498 readinessGates: 499 - conditionType: foo 500 - conditionType: bar 501 status: 502 conditions: 503 - type: foo 504 status: "True" 505 reason: reason 506 message: message 507 - type: bar 508 status: "False" 509 reason: reason 510 message: message 511 `, 512 updated: ` 513 apiVersion: kpt.dev/v1 514 kind: Kptfile 515 metadata: 516 name: foo 517 info: 518 readinessGates: 519 - conditionType: foo 520 - conditionType: zork 521 status: 522 conditions: 523 - type: foo 524 status: "True" 525 reason: reason 526 message: message 527 - type: zork 528 status: "Unknown" 529 reason: reason 530 message: message 531 `, 532 local: ` 533 apiVersion: kpt.dev/v1 534 kind: Kptfile 535 metadata: 536 name: foo 537 info: 538 readinessGates: 539 - conditionType: xandar 540 - conditionType: foo 541 status: 542 conditions: 543 - type: xandar 544 status: "True" 545 reason: reason 546 message: message 547 - type: foo 548 status: "True" 549 reason: reason 550 message: message 551 `, 552 updateUpstream: false, 553 expected: ` 554 apiVersion: kpt.dev/v1 555 kind: Kptfile 556 metadata: 557 name: foo 558 info: 559 readinessGates: 560 - conditionType: foo 561 - conditionType: zork 562 status: 563 conditions: 564 - type: foo 565 status: "True" 566 reason: reason 567 message: message 568 - type: zork 569 status: Unknown 570 reason: reason 571 message: message 572 `, 573 }, 574 } 575 576 for tn, tc := range testCases { 577 t.Run(tn, func(t *testing.T) { 578 files := map[string]string{ 579 "origin": tc.origin, 580 "updated": tc.updated, 581 "local": tc.local, 582 } 583 dirs := make(map[string]string) 584 for n, content := range files { 585 dir := writeKptfileToTemp(t, content) 586 dirs[n] = dir 587 } 588 589 err := UpdateKptfile(dirs["local"], dirs["updated"], dirs["origin"], tc.updateUpstream) 590 if !assert.NoError(t, err) { 591 t.FailNow() 592 } 593 594 c, err := os.ReadFile(filepath.Join(dirs["local"], kptfilev1.KptFileName)) 595 if !assert.NoError(t, err) { 596 t.FailNow() 597 } 598 599 assert.Equal(t, strings.TrimSpace(tc.expected)+"\n", string(c)) 600 }) 601 } 602 } 603 604 func TestMerge(t *testing.T) { 605 testCases := map[string]struct { 606 origin string 607 update string 608 local string 609 expected string 610 err error 611 }{ 612 // With no associative key, there is no merge, just a replacement 613 // of the pipeline with upstream. This is aligned with the general behavior 614 // of kyaml merge where in conflicts the upstream version win.s 615 "no associative key, additions in both upstream and local": { 616 origin: ` 617 apiVersion: kpt.dev/v1 618 kind: Kptfile 619 metadata: 620 name: pipeline 621 `, 622 update: ` 623 apiVersion: kpt.dev/v1 624 kind: Kptfile 625 metadata: 626 name: pipeline 627 pipeline: 628 mutators: 629 - image: gcr.io/kpt/gen-folders 630 `, 631 local: ` 632 apiVersion: kpt.dev/v1 633 kind: Kptfile 634 metadata: 635 name: pipeline 636 pipeline: 637 mutators: 638 - image: gcr.io/kpt/folder-ref 639 `, 640 expected: ` 641 apiVersion: kpt.dev/v1 642 kind: Kptfile 643 metadata: 644 name: pipeline 645 pipeline: 646 mutators: 647 - image: gcr.io/kpt/folder-ref 648 - image: gcr.io/kpt/gen-folders 649 `, 650 }, 651 652 "exec: no associative key, additions in both upstream and local": { 653 origin: ` 654 apiVersion: kpt.dev/v1 655 kind: Kptfile 656 metadata: 657 name: pipeline 658 `, 659 update: ` 660 apiVersion: kpt.dev/v1 661 kind: Kptfile 662 metadata: 663 name: pipeline 664 pipeline: 665 mutators: 666 - exec: gen-folders 667 `, 668 local: ` 669 apiVersion: kpt.dev/v1 670 kind: Kptfile 671 metadata: 672 name: pipeline 673 pipeline: 674 mutators: 675 - exec: folder-ref 676 `, 677 expected: ` 678 apiVersion: kpt.dev/v1 679 kind: Kptfile 680 metadata: 681 name: pipeline 682 pipeline: 683 mutators: 684 - exec: folder-ref 685 - exec: gen-folders 686 `, 687 }, 688 689 "add new setter in upstream, update local setter value": { 690 origin: ` 691 apiVersion: kpt.dev/v1 692 kind: Kptfile 693 metadata: 694 name: pipeline 695 pipeline: 696 mutators: 697 - image: gcr.io/kpt-fn/apply-setters:v0.1 698 configMap: 699 image: nginx 700 tag: 1.0.1 701 `, 702 update: ` 703 apiVersion: kpt.dev/v1 704 kind: Kptfile 705 metadata: 706 name: pipeline 707 pipeline: 708 mutators: 709 - image: gcr.io/kpt-fn/apply-setters:v0.1 710 configMap: 711 image: nginx 712 tag: 1.0.1 713 new-setter: new-setter-value // new setter is added 714 `, 715 local: ` 716 apiVersion: kpt.dev/v1 717 kind: Kptfile 718 metadata: 719 name: pipeline 720 pipeline: 721 mutators: 722 - image: gcr.io/kpt-fn/apply-setters:v0.1 723 configMap: 724 image: nginx 725 tag: 1.2.0 // value of tag is updated 726 `, 727 expected: ` 728 apiVersion: kpt.dev/v1 729 kind: Kptfile 730 metadata: 731 name: pipeline 732 pipeline: 733 mutators: 734 - image: gcr.io/kpt-fn/apply-setters:v0.1 735 configMap: 736 image: nginx 737 new-setter: new-setter-value // new setter is added 738 tag: 1.2.0 // value of tag is updated 739 `, 740 }, 741 742 "both upstream and local configPath is updated, take upstream": { 743 origin: ` 744 apiVersion: kpt.dev/v1 745 kind: Kptfile 746 metadata: 747 name: pipeline 748 pipeline: 749 mutators: 750 - image: gcr.io/kpt-fn/apply-setters:v0.1 751 configPath: setters.yaml 752 `, 753 update: ` 754 apiVersion: kpt.dev/v1 755 kind: Kptfile 756 metadata: 757 name: pipeline 758 pipeline: 759 mutators: 760 - image: gcr.io/kpt-fn/apply-setters:v0.1 761 configPath: setters-updated.yaml 762 `, 763 local: ` 764 apiVersion: kpt.dev/v1 765 kind: Kptfile 766 metadata: 767 name: pipeline 768 pipeline: 769 mutators: 770 - image: gcr.io/kpt-fn/apply-setters:v0.1 771 configPath: setters-local.yaml 772 `, 773 expected: ` 774 apiVersion: kpt.dev/v1 775 kind: Kptfile 776 metadata: 777 name: pipeline 778 pipeline: 779 mutators: 780 - image: gcr.io/kpt-fn/apply-setters:v0.1 781 configPath: setters-updated.yaml 782 `, 783 }, 784 785 "both upstream and local version is updated, take upstream": { 786 origin: ` 787 apiVersion: kpt.dev/v1 788 kind: Kptfile 789 metadata: 790 name: pipeline 791 pipeline: 792 mutators: 793 - image: gcr.io/kpt-fn/apply-setters:v0.1 794 configPath: setters.yaml 795 `, 796 update: ` 797 apiVersion: kpt.dev/v1 798 kind: Kptfile 799 metadata: 800 name: pipeline 801 pipeline: 802 mutators: 803 - image: gcr.io/kpt-fn/apply-setters:v0.1.2 804 configPath: setters.yaml 805 `, 806 local: ` 807 apiVersion: kpt.dev/v1 808 kind: Kptfile 809 metadata: 810 name: pipeline 811 pipeline: 812 mutators: 813 - image: gcr.io/kpt-fn/apply-setters:v0.1.1 814 configPath: setters.yaml 815 `, 816 expected: ` 817 apiVersion: kpt.dev/v1 818 kind: Kptfile 819 metadata: 820 name: pipeline 821 pipeline: 822 mutators: 823 - image: gcr.io/kpt-fn/apply-setters:v0.1.2 824 configPath: setters.yaml 825 `, 826 }, 827 828 "newly added upstream function": { 829 origin: ` 830 apiVersion: kpt.dev/v1 831 kind: Kptfile 832 metadata: 833 name: pipeline 834 pipeline: 835 mutators: 836 - image: gcr.io/kpt-fn/apply-setters:v0.1 837 configPath: setters.yaml 838 `, 839 update: ` 840 apiVersion: kpt.dev/v1 841 kind: Kptfile 842 metadata: 843 name: pipeline 844 pipeline: 845 mutators: 846 - image: gcr.io/kpt-fn/apply-setters:v0.1 847 configPath: setters.yaml 848 - image: gcr.io/kpt-fn/generate-folders:v0.1 849 `, 850 local: ` 851 apiVersion: kpt.dev/v1 852 kind: Kptfile 853 metadata: 854 name: pipeline 855 pipeline: 856 mutators: 857 - image: gcr.io/kpt-fn/apply-setters:v0.1 858 configPath: setters.yaml 859 - image: gcr.io/kpt-fn/set-namespace:v0.1 860 configMap: 861 namespace: foo 862 `, 863 expected: ` 864 apiVersion: kpt.dev/v1 865 kind: Kptfile 866 metadata: 867 name: pipeline 868 pipeline: 869 mutators: 870 - image: gcr.io/kpt-fn/apply-setters:v0.1 871 configPath: setters.yaml 872 - image: gcr.io/kpt-fn/set-namespace:v0.1 873 configMap: 874 namespace: foo 875 - image: gcr.io/kpt-fn/generate-folders:v0.1 876 `, 877 }, 878 879 "deleted function in the upstream, deleted on local if not changed": { 880 origin: ` 881 apiVersion: kpt.dev/v1 882 kind: Kptfile 883 metadata: 884 name: pipeline 885 pipeline: 886 validators: 887 - image: gcr.io/kpt-fn/apply-setters:v0.1 888 configPath: setters.yaml 889 - image: gcr.io/kpt-fn/generate-folders:v0.1 890 `, 891 update: ` 892 apiVersion: kpt.dev/v1 893 kind: Kptfile 894 metadata: 895 name: pipeline 896 pipeline: 897 validators: 898 - image: gcr.io/kpt-fn/apply-setters:v0.1 899 configPath: setters.yaml 900 `, 901 local: ` 902 apiVersion: kpt.dev/v1 903 kind: Kptfile 904 metadata: 905 name: pipeline 906 pipeline: 907 validators: 908 - image: gcr.io/kpt-fn/apply-setters:v0.1 909 configPath: setters.yaml 910 - image: gcr.io/kpt-fn/generate-folders:v0.1 911 - image: gcr.io/kpt-fn/set-namespace:v0.1 912 configMap: 913 namespace: foo 914 `, 915 expected: ` 916 apiVersion: kpt.dev/v1 917 kind: Kptfile 918 metadata: 919 name: pipeline 920 pipeline: 921 validators: 922 - image: gcr.io/kpt-fn/apply-setters:v0.1 923 configPath: setters.yaml 924 - image: gcr.io/kpt-fn/set-namespace:v0.1 925 configMap: 926 namespace: foo 927 `, 928 }, 929 930 "multiple declarations of same function": { 931 origin: ` 932 apiVersion: kpt.dev/v1 933 kind: Kptfile 934 metadata: 935 name: pipeline 936 pipeline: 937 mutators: 938 - image: gcr.io/kpt-fn/search-replace:v0.1 939 configMap: 940 by-value: foo 941 put-value: bar 942 - image: gcr.io/kpt-fn/search-replace:v0.1 943 configMap: 944 by-value: abc 945 put-comment: ${some-setter-name} 946 `, 947 update: ` 948 apiVersion: kpt.dev/v1 949 kind: Kptfile 950 metadata: 951 name: pipeline 952 pipeline: 953 mutators: 954 - image: gcr.io/kpt-fn/search-replace:v0.1 955 configMap: 956 by-value: foo 957 put-value: bar-new 958 - image: gcr.io/kpt-fn/search-replace:v0.1 959 configMap: 960 by-value: abc 961 put-comment: ${updated-setter-name} 962 `, 963 local: ` 964 apiVersion: kpt.dev/v1 965 kind: Kptfile 966 metadata: 967 name: pipeline 968 pipeline: 969 mutators: 970 - image: gcr.io/kpt-fn/generate-folders:v0.1 971 - image: gcr.io/kpt-fn/search-replace:v0.1 972 configMap: 973 by-value: foo 974 put-value: bar 975 - image: gcr.io/kpt-fn/set-labels:v0.1 976 configMap: 977 app: db 978 - image: gcr.io/kpt-fn/search-replace:v0.1 979 configMap: 980 by-value: abc 981 put-comment: ${some-setter-name} 982 - image: gcr.io/kpt-fn/search-replace:v0.1 983 configMap: 984 by-value: YOUR_TEAM 985 put-value: my-team 986 `, 987 expected: ` 988 apiVersion: kpt.dev/v1 989 kind: Kptfile 990 metadata: 991 name: pipeline 992 pipeline: 993 mutators: 994 - image: gcr.io/kpt-fn/search-replace:v0.1 995 configMap: 996 by-value: foo 997 put-value: bar-new 998 - image: gcr.io/kpt-fn/search-replace:v0.1 999 configMap: 1000 by-value: abc 1001 put-comment: ${updated-setter-name} 1002 `, 1003 }, 1004 1005 "add function at random location with name specified": { 1006 origin: ` 1007 apiVersion: kpt.dev/v1 1008 kind: Kptfile 1009 metadata: 1010 name: pipeline 1011 pipeline: 1012 mutators: 1013 - image: gcr.io/kpt-fn/search-replace:v0.1 1014 configMap: 1015 by-value: foo 1016 put-value: bar 1017 - image: gcr.io/kpt-fn/search-replace:v0.1 1018 configMap: 1019 by-value: abc 1020 put-comment: ${some-setter-name} 1021 `, 1022 update: ` 1023 apiVersion: kpt.dev/v1 1024 kind: Kptfile 1025 metadata: 1026 name: pipeline 1027 pipeline: 1028 mutators: 1029 - image: gcr.io/kpt-fn/search-replace:v0.1 1030 configMap: 1031 by-value: foo 1032 put-value: bar-new 1033 - image: gcr.io/kpt-fn/search-replace:v0.1 1034 configMap: 1035 by-value: abc 1036 put-comment: ${updated-setter-name} 1037 `, 1038 local: ` 1039 apiVersion: kpt.dev/v1 1040 kind: Kptfile 1041 metadata: 1042 name: pipeline 1043 pipeline: 1044 mutators: 1045 - image: gcr.io/kpt-fn/search-replace:v0.1 1046 name: my-new-function 1047 configMap: 1048 by-value: YOUR_TEAM 1049 put-value: my-team 1050 - image: gcr.io/kpt-fn/generate-folders:v0.1 1051 - image: gcr.io/kpt-fn/search-replace:v0.1 1052 configMap: 1053 by-value: foo 1054 put-value: bar 1055 - image: gcr.io/kpt-fn/set-labels:v0.1 1056 configMap: 1057 app: db 1058 - image: gcr.io/kpt-fn/search-replace:v0.1 1059 configMap: 1060 by-value: abc 1061 put-comment: ${some-setter-name} 1062 `, 1063 expected: ` 1064 apiVersion: kpt.dev/v1 1065 kind: Kptfile 1066 metadata: 1067 name: pipeline 1068 pipeline: 1069 mutators: 1070 - image: gcr.io/kpt-fn/search-replace:v0.1 1071 configMap: 1072 by-value: foo 1073 put-value: bar-new 1074 - image: gcr.io/kpt-fn/search-replace:v0.1 1075 configMap: 1076 by-value: abc 1077 put-comment: ${updated-setter-name} 1078 `, 1079 }, 1080 1081 "Ideal deterministic behavior: add function at random location with name specified in all sources": { 1082 origin: ` 1083 apiVersion: kpt.dev/v1 1084 kind: Kptfile 1085 metadata: 1086 name: pipeline 1087 pipeline: 1088 mutators: 1089 - image: gcr.io/kpt-fn/search-replace:v0.1 1090 name: sr1 1091 configMap: 1092 by-value: foo 1093 put-value: bar 1094 - image: gcr.io/kpt-fn/search-replace:v0.1 1095 name: sr2 1096 configMap: 1097 by-value: abc 1098 put-comment: ${some-setter-name} 1099 `, 1100 update: ` 1101 apiVersion: kpt.dev/v1 1102 kind: Kptfile 1103 metadata: 1104 name: pipeline 1105 pipeline: 1106 mutators: 1107 - image: gcr.io/kpt-fn/search-replace:v0.1 1108 name: sr1 1109 configMap: 1110 by-value: foo 1111 put-value: bar-new 1112 - image: gcr.io/kpt-fn/search-replace:v0.1 1113 name: sr2 1114 configMap: 1115 by-value: abc 1116 put-comment: ${updated-setter-name} 1117 `, 1118 local: ` 1119 apiVersion: kpt.dev/v1 1120 kind: Kptfile 1121 metadata: 1122 name: pipeline 1123 pipeline: 1124 mutators: 1125 - image: gcr.io/kpt-fn/search-replace:v0.1 1126 name: my-new-function 1127 configMap: 1128 by-value: YOUR_TEAM 1129 put-value: my-team 1130 - image: gcr.io/kpt-fn/generate-folders:v0.1 1131 name: gf1 1132 - image: gcr.io/kpt-fn/search-replace:v0.1 1133 name: sr1 1134 configMap: 1135 by-value: foo 1136 put-value: bar 1137 - image: gcr.io/kpt-fn/set-labels:v0.1 1138 name: sl1 1139 configMap: 1140 app: db 1141 - image: gcr.io/kpt-fn/search-replace:v0.1 1142 name: sr2 1143 configMap: 1144 by-value: abc 1145 put-comment: ${some-setter-name} 1146 `, 1147 expected: ` 1148 apiVersion: kpt.dev/v1 1149 kind: Kptfile 1150 metadata: 1151 name: pipeline 1152 pipeline: 1153 mutators: 1154 - image: gcr.io/kpt-fn/search-replace:v0.1 1155 configMap: 1156 by-value: YOUR_TEAM 1157 put-value: my-team 1158 name: my-new-function 1159 - image: gcr.io/kpt-fn/generate-folders:v0.1 1160 name: gf1 1161 - image: gcr.io/kpt-fn/search-replace:v0.1 1162 configMap: 1163 by-value: foo 1164 put-value: bar-new 1165 name: sr1 1166 - image: gcr.io/kpt-fn/set-labels:v0.1 1167 configMap: 1168 app: db 1169 name: sl1 1170 - image: gcr.io/kpt-fn/search-replace:v0.1 1171 configMap: 1172 by-value: abc 1173 put-comment: ${updated-setter-name} 1174 name: sr2 1175 `, 1176 }, 1177 1178 // When adding an associative key, we get a real merge of the pipeline. 1179 // In this case, we have an initial empty list in origin and different 1180 // functions are added in upstream and local. In this case the element 1181 // added in local are placed first in the resulting list. 1182 "associative key name, additions in both upstream and local": { 1183 origin: ` 1184 apiVersion: kpt.dev/v1 1185 kind: Kptfile 1186 metadata: 1187 name: pipeline 1188 `, 1189 update: ` 1190 apiVersion: kpt.dev/v1 1191 kind: Kptfile 1192 metadata: 1193 name: pipeline 1194 pipeline: 1195 mutators: 1196 - name: gen-folders 1197 image: gcr.io/kpt/gen-folders 1198 `, 1199 local: ` 1200 apiVersion: kpt.dev/v1 1201 kind: Kptfile 1202 metadata: 1203 name: pipeline 1204 pipeline: 1205 mutators: 1206 - name: folder-ref 1207 image: gcr.io/kpt/folder-ref 1208 `, 1209 // The reordering of elements in the results is a bug in the 1210 // merge logic I think. 1211 expected: ` 1212 apiVersion: kpt.dev/v1 1213 kind: Kptfile 1214 metadata: 1215 name: pipeline 1216 pipeline: 1217 mutators: 1218 - image: gcr.io/kpt/folder-ref 1219 name: folder-ref 1220 - image: gcr.io/kpt/gen-folders 1221 name: gen-folders 1222 `, 1223 }, 1224 1225 // Even with multiple elements added in both upstream and local, all 1226 // elements from local comes before upstream, and the order of elements 1227 // from each source is preserved. There is no lexicographical 1228 // ordering. 1229 "associative key name, multiple additions in both upstream and local": { 1230 origin: ` 1231 apiVersion: kpt.dev/v1 1232 kind: Kptfile 1233 metadata: 1234 name: pipeline 1235 `, 1236 update: ` 1237 apiVersion: kpt.dev/v1 1238 kind: Kptfile 1239 metadata: 1240 name: pipeline 1241 pipeline: 1242 mutators: 1243 - name: z-upstream 1244 image: z-gcr.io/kpt/gen-folders 1245 - name: a-upstream 1246 image: a-gcr.io/kpt/gen-folders 1247 `, 1248 local: ` 1249 apiVersion: kpt.dev/v1 1250 kind: Kptfile 1251 metadata: 1252 name: pipeline 1253 pipeline: 1254 mutators: 1255 - name: x-local 1256 image: x-gcr.io/kpt/gen-folders 1257 - name: b-local 1258 image: b-gcr.io/kpt/gen-folders 1259 `, 1260 expected: ` 1261 apiVersion: kpt.dev/v1 1262 kind: Kptfile 1263 metadata: 1264 name: pipeline 1265 pipeline: 1266 mutators: 1267 - image: x-gcr.io/kpt/gen-folders 1268 name: x-local 1269 - image: b-gcr.io/kpt/gen-folders 1270 name: b-local 1271 - image: z-gcr.io/kpt/gen-folders 1272 name: z-upstream 1273 - image: a-gcr.io/kpt/gen-folders 1274 name: a-upstream 1275 `, 1276 }, 1277 1278 // If elements with the same associative key are added in both upstream 1279 // and local, it will be merged. It will keep the location in the list 1280 // from local. 1281 "same element in both local and upstream does not create duplicate": { 1282 origin: ` 1283 apiVersion: kpt.dev/v1 1284 kind: Kptfile 1285 metadata: 1286 name: pipeline 1287 `, 1288 update: ` 1289 apiVersion: kpt.dev/v1 1290 kind: Kptfile 1291 metadata: 1292 name: pipeline 1293 pipeline: 1294 mutators: 1295 - name: gen-folder-upstream 1296 image: gcr.io/kpt/gen-folders 1297 - name: ref-folders 1298 image: gcr.io/kpt/ref-folders 1299 configMap: 1300 foo: bar 1301 `, 1302 local: ` 1303 apiVersion: kpt.dev/v1 1304 kind: Kptfile 1305 metadata: 1306 name: pipeline 1307 pipeline: 1308 mutators: 1309 - name: ref-folders 1310 image: gcr.io/kpt/ref-folders 1311 configMap: 1312 bar: foo 1313 - name: gen-folder-local 1314 image: gcr.io/kpt/gen-folders 1315 `, 1316 expected: ` 1317 apiVersion: kpt.dev/v1 1318 kind: Kptfile 1319 metadata: 1320 name: pipeline 1321 pipeline: 1322 mutators: 1323 - image: gcr.io/kpt/ref-folders 1324 configMap: 1325 bar: foo 1326 foo: bar 1327 name: ref-folders 1328 - image: gcr.io/kpt/gen-folders 1329 name: gen-folder-local 1330 - image: gcr.io/kpt/gen-folders 1331 name: gen-folder-upstream 1332 `, 1333 }, 1334 1335 // If a field are set in both upstream and local, the value from 1336 // upstream will be chosen. 1337 "If there is a field-level conflict, upstream will win": { 1338 origin: ` 1339 apiVersion: kpt.dev/v1 1340 kind: Kptfile 1341 metadata: 1342 name: pipeline 1343 `, 1344 update: ` 1345 apiVersion: kpt.dev/v1 1346 kind: Kptfile 1347 metadata: 1348 name: pipeline 1349 pipeline: 1350 mutators: 1351 - name: ref-folders 1352 image: gcr.io/kpt/ref-folders 1353 configMap: 1354 band: sleater-kinney 1355 `, 1356 local: ` 1357 apiVersion: kpt.dev/v1 1358 kind: Kptfile 1359 metadata: 1360 name: pipeline 1361 pipeline: 1362 mutators: 1363 - name: ref-folders 1364 image: gcr.io/kpt/ref-folders 1365 configMap: 1366 band: Hüsker Dü 1367 `, 1368 expected: ` 1369 apiVersion: kpt.dev/v1 1370 kind: Kptfile 1371 metadata: 1372 name: pipeline 1373 pipeline: 1374 mutators: 1375 - image: gcr.io/kpt/ref-folders 1376 configMap: 1377 band: sleater-kinney 1378 name: ref-folders 1379 `, 1380 }, 1381 } 1382 for tn, tc := range testCases { 1383 t.Run(tn, func(t *testing.T) { 1384 localKf, err := pkg.DecodeKptfile(strings.NewReader(tc.local)) 1385 assert.NoError(t, err) 1386 updatedKf, err := pkg.DecodeKptfile(strings.NewReader(tc.update)) 1387 assert.NoError(t, err) 1388 originKf, err := pkg.DecodeKptfile(strings.NewReader(tc.origin)) 1389 assert.NoError(t, err) 1390 err = merge(localKf, updatedKf, originKf) 1391 if tc.err == nil { 1392 if !assert.NoError(t, err) { 1393 t.FailNow() 1394 } 1395 actual, err := yaml.Marshal(localKf) 1396 assert.NoError(t, err) 1397 if !assert.Equal(t, 1398 strings.TrimSpace(tc.expected), strings.TrimSpace(string(actual))) { 1399 t.FailNow() 1400 } 1401 } else { 1402 if !assert.Error(t, err) { 1403 t.FailNow() 1404 } 1405 if !assert.Contains(t, tc.err.Error(), err.Error()) { 1406 t.FailNow() 1407 } 1408 } 1409 }) 1410 } 1411 }