k8s.io/client-go@v0.31.1/util/csaupgrade/upgrade_test.go (about) 1 /* 2 Copyright 2022 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 csaupgrade_test 18 19 import ( 20 "encoding/json" 21 "reflect" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/stretchr/testify/require" 26 jsonpatch "gopkg.in/evanphx/json-patch.v4" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/util/sets" 30 "k8s.io/apimachinery/pkg/util/yaml" 31 "k8s.io/client-go/util/csaupgrade" 32 "sigs.k8s.io/structured-merge-diff/v4/fieldpath" 33 ) 34 35 func TestFindOwners(t *testing.T) { 36 testCases := []struct { 37 Name string 38 ManagedFieldsYAML string 39 Operation metav1.ManagedFieldsOperationType 40 Fields *fieldpath.Set 41 Expectation []string 42 }{ 43 { 44 // Field a root field path owner 45 Name: "Basic", 46 ManagedFieldsYAML: ` 47 managedFields: 48 - apiVersion: v1 49 fieldsType: FieldsV1 50 fieldsV1: 51 f:data: 52 .: {} 53 f:key: {} 54 f:legacy: {} 55 f:metadata: 56 f:annotations: 57 .: {} 58 f:kubectl.kubernetes.io/last-applied-configuration: {} 59 manager: kubectl-client-side-apply 60 operation: Update 61 time: "2022-08-22T23:08:23Z" 62 `, 63 Operation: metav1.ManagedFieldsOperationUpdate, 64 Fields: fieldpath.NewSet(fieldpath.MakePathOrDie("data")), 65 Expectation: []string{"kubectl-client-side-apply"}, 66 }, 67 { 68 // Find a fieldpath nested inside another field 69 Name: "Nested", 70 ManagedFieldsYAML: ` 71 managedFields: 72 - apiVersion: v1 73 fieldsType: FieldsV1 74 fieldsV1: 75 f:data: 76 .: {} 77 f:key: {} 78 f:legacy: {} 79 f:metadata: 80 f:annotations: 81 .: {} 82 f:kubectl.kubernetes.io/last-applied-configuration: {} 83 manager: kubectl-client-side-apply 84 operation: Update 85 time: "2022-08-22T23:08:23Z" 86 `, 87 Operation: metav1.ManagedFieldsOperationUpdate, 88 Fields: fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")), 89 Expectation: []string{"kubectl-client-side-apply"}, 90 }, 91 { 92 // Search for an operaiton/fieldpath combination that is not found on both 93 // axes 94 Name: "NotFound", 95 ManagedFieldsYAML: ` 96 managedFields: 97 - apiVersion: v1 98 fieldsType: FieldsV1 99 fieldsV1: 100 f:data: 101 .: {} 102 f:key: {} 103 f:legacy: {} 104 f:metadata: 105 f:annotations: 106 .: {} 107 f:kubectl.kubernetes.io/last-applied-configuration: {} 108 manager: kubectl 109 operation: Apply 110 time: "2022-08-23T23:08:23Z" 111 `, 112 Operation: metav1.ManagedFieldsOperationUpdate, 113 Fields: fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")), 114 Expectation: []string{}, 115 }, 116 { 117 // Test using apply operation 118 Name: "ApplyOperation", 119 ManagedFieldsYAML: ` 120 managedFields: 121 - apiVersion: v1 122 fieldsType: FieldsV1 123 fieldsV1: 124 f:data: 125 .: {} 126 f:key: {} 127 f:legacy: {} 128 f:metadata: 129 f:annotations: 130 .: {} 131 f:kubectl.kubernetes.io/last-applied-configuration: {} 132 manager: kubectl 133 operation: Apply 134 time: "2022-08-23T23:08:23Z" 135 `, 136 Operation: metav1.ManagedFieldsOperationApply, 137 Fields: fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")), 138 Expectation: []string{"kubectl"}, 139 }, 140 { 141 // Of multiple field managers, match a single one 142 Name: "OneOfMultiple", 143 ManagedFieldsYAML: ` 144 managedFields: 145 - apiVersion: v1 146 fieldsType: FieldsV1 147 fieldsV1: 148 f:metadata: 149 f:annotations: 150 .: {} 151 f:kubectl.kubernetes.io/last-applied-configuration: {} 152 manager: kubectl-client-side-apply 153 operation: Update 154 time: "2022-08-23T23:08:23Z" 155 - apiVersion: v1 156 fieldsType: FieldsV1 157 fieldsV1: 158 f:data: 159 .: {} 160 f:key: {} 161 f:legacy: {} 162 manager: kubectl 163 operation: Apply 164 time: "2022-08-23T23:08:23Z" 165 `, 166 Operation: metav1.ManagedFieldsOperationUpdate, 167 Fields: fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")), 168 Expectation: []string{"kubectl-client-side-apply"}, 169 }, 170 { 171 // have multiple field managers, and match more than one but not all of them 172 Name: "ManyOfMultiple", 173 ManagedFieldsYAML: ` 174 managedFields: 175 - apiVersion: v1 176 fieldsType: FieldsV1 177 fieldsV1: 178 f:metadata: 179 f:annotations: 180 .: {} 181 f:kubectl.kubernetes.io/last-applied-configuration: {} 182 manager: kubectl-client-side-apply 183 operation: Update 184 time: "2022-08-23T23:08:23Z" 185 - apiVersion: v1 186 fieldsType: FieldsV1 187 fieldsV1: 188 f:metadata: 189 f:annotations: 190 .: {} 191 f:kubectl.kubernetes.io/last-applied-configuration: {} 192 f:data: 193 .: {} 194 f:key: {} 195 f:legacy: {} 196 manager: kubectl 197 operation: Apply 198 time: "2022-08-23T23:08:23Z" 199 - apiVersion: v1 200 fieldsType: FieldsV1 201 fieldsV1: 202 f:metadata: 203 f:annotations: 204 .: {} 205 f:kubectl.kubernetes.io/last-applied-configuration: {} 206 manager: kubectl-client-side-apply2 207 operation: Update 208 time: "2022-08-23T23:08:23Z" 209 `, 210 Operation: metav1.ManagedFieldsOperationUpdate, 211 Fields: fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")), 212 Expectation: []string{"kubectl-client-side-apply", "kubectl-client-side-apply2"}, 213 }, 214 { 215 // Test with multiple fields to match against 216 Name: "BasicMultipleFields", 217 ManagedFieldsYAML: ` 218 managedFields: 219 - apiVersion: v1 220 fieldsType: FieldsV1 221 fieldsV1: 222 f:metadata: 223 f:annotations: 224 .: {} 225 f:kubectl.kubernetes.io/last-applied-configuration: {} 226 f:data: 227 .: {} 228 f:key: {} 229 f:legacy: {} 230 manager: kubectl-client-side-apply 231 operation: Update 232 time: "2022-08-23T23:08:23Z" 233 `, 234 Operation: metav1.ManagedFieldsOperationUpdate, 235 Fields: fieldpath.NewSet( 236 fieldpath.MakePathOrDie("data", "key"), 237 fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration"), 238 ), 239 Expectation: []string{"kubectl-client-side-apply"}, 240 }, 241 { 242 // Test with multiplle fields but the manager is missing one of the fields 243 // requested so it does not match 244 Name: "MissingOneField", 245 ManagedFieldsYAML: ` 246 managedFields: 247 - apiVersion: v1 248 fieldsType: FieldsV1 249 fieldsV1: 250 f:metadata: 251 f:annotations: 252 .: {} 253 f:kubectl.kubernetes.io/last-applied-configuration: {} 254 f:data: 255 .: {} 256 f:legacy: {} 257 manager: kubectl-client-side-apply 258 operation: Update 259 time: "2022-08-23T23:08:23Z" 260 `, 261 Operation: metav1.ManagedFieldsOperationUpdate, 262 Fields: fieldpath.NewSet( 263 fieldpath.MakePathOrDie("data", "key"), 264 fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration"), 265 ), 266 Expectation: []string{}, 267 }, 268 } 269 for _, tcase := range testCases { 270 t.Run(tcase.Name, func(t *testing.T) { 271 var entries struct { 272 ManagedFields []metav1.ManagedFieldsEntry `json:"managedFields"` 273 } 274 err := yaml.Unmarshal([]byte(tcase.ManagedFieldsYAML), &entries) 275 require.NoError(t, err) 276 277 result := csaupgrade.FindFieldsOwners(entries.ManagedFields, tcase.Operation, tcase.Fields) 278 279 // Compare owner names since they uniquely identify the selected entries 280 // (given that the operation is provided) 281 ownerNames := []string{} 282 for _, entry := range result { 283 ownerNames = append(ownerNames, entry.Manager) 284 require.Equal(t, tcase.Operation, entry.Operation) 285 } 286 require.ElementsMatch(t, tcase.Expectation, ownerNames) 287 }) 288 } 289 } 290 291 func TestUpgradeCSA(t *testing.T) { 292 293 cases := []struct { 294 Name string 295 CSAManagers []string 296 SSAManager string 297 Options []csaupgrade.Option 298 OriginalObject []byte 299 ExpectedObject []byte 300 }{ 301 { 302 // Case where there is a CSA entry with the given name, but no SSA entry 303 // is found. Expect that the CSA entry is converted to an SSA entry 304 // and renamed. 305 Name: "csa-basic-direct-conversion", 306 CSAManagers: []string{"kubectl-client-side-apply"}, 307 SSAManager: "kubectl", 308 OriginalObject: []byte(` 309 apiVersion: v1 310 data: {} 311 kind: ConfigMap 312 metadata: 313 resourceVersion: "1" 314 annotations: 315 kubectl.kubernetes.io/last-applied-configuration: | 316 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 317 creationTimestamp: "2022-08-22T23:08:23Z" 318 managedFields: 319 - apiVersion: v1 320 fieldsType: FieldsV1 321 fieldsV1: 322 f:data: 323 .: {} 324 f:key: {} 325 f:legacy: {} 326 f:metadata: 327 f:annotations: 328 .: {} 329 f:kubectl.kubernetes.io/last-applied-configuration: {} 330 manager: kubectl-client-side-apply 331 operation: Update 332 time: "2022-08-22T23:08:23Z" 333 name: test 334 namespace: default 335 `), 336 ExpectedObject: []byte(` 337 apiVersion: v1 338 data: {} 339 kind: ConfigMap 340 metadata: 341 resourceVersion: "1" 342 annotations: 343 kubectl.kubernetes.io/last-applied-configuration: | 344 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 345 creationTimestamp: "2022-08-22T23:08:23Z" 346 managedFields: 347 - apiVersion: v1 348 fieldsType: FieldsV1 349 fieldsV1: 350 f:data: 351 .: {} 352 f:key: {} 353 f:legacy: {} 354 f:metadata: 355 f:annotations: 356 .: {} 357 f:kubectl.kubernetes.io/last-applied-configuration: {} 358 manager: kubectl 359 operation: Apply 360 time: "2022-08-22T23:08:23Z" 361 name: test 362 namespace: default 363 `), 364 }, 365 { 366 // This is the case when kubectl --server-side is used for the first time 367 // Server creates duplicate managed fields entry - one for Update and another 368 // for Apply. Expect entries to be merged into one entry, which is unchanged 369 // from initial SSA. 370 Name: "csa-combine-with-ssa-duplicate-keys", 371 CSAManagers: []string{"kubectl-client-side-apply"}, 372 SSAManager: "kubectl", 373 OriginalObject: []byte(` 374 apiVersion: v1 375 data: {} 376 kind: ConfigMap 377 metadata: 378 resourceVersion: "1" 379 annotations: 380 kubectl.kubernetes.io/last-applied-configuration: | 381 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 382 creationTimestamp: "2022-08-22T23:08:23Z" 383 managedFields: 384 - apiVersion: v1 385 fieldsType: FieldsV1 386 fieldsV1: 387 f:data: 388 .: {} 389 f:key: {} 390 f:legacy: {} 391 f:metadata: 392 f:annotations: 393 .: {} 394 f:kubectl.kubernetes.io/last-applied-configuration: {} 395 manager: kubectl 396 operation: Apply 397 time: "2022-08-23T23:08:23Z" 398 - apiVersion: v1 399 fieldsType: FieldsV1 400 fieldsV1: 401 f:data: 402 .: {} 403 f:key: {} 404 f:legacy: {} 405 f:metadata: 406 f:annotations: 407 .: {} 408 f:kubectl.kubernetes.io/last-applied-configuration: {} 409 manager: kubectl-client-side-apply 410 operation: Update 411 time: "2022-08-22T23:08:23Z" 412 name: test 413 namespace: default 414 `), 415 ExpectedObject: []byte(` 416 apiVersion: v1 417 data: {} 418 kind: ConfigMap 419 metadata: 420 resourceVersion: "1" 421 annotations: 422 kubectl.kubernetes.io/last-applied-configuration: | 423 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 424 creationTimestamp: "2022-08-22T23:08:23Z" 425 managedFields: 426 - apiVersion: v1 427 fieldsType: FieldsV1 428 fieldsV1: 429 f:data: 430 .: {} 431 f:key: {} 432 f:legacy: {} 433 f:metadata: 434 f:annotations: 435 .: {} 436 f:kubectl.kubernetes.io/last-applied-configuration: {} 437 manager: kubectl 438 operation: Apply 439 time: "2022-08-23T23:08:23Z" 440 name: test 441 namespace: default 442 `), 443 }, 444 { 445 // This is the case when kubectl --server-side is used for the first time, 446 // but then a key is removed. A bug would take place where key is left in 447 // CSA entry but no longer present in SSA entry, so it would not be pruned. 448 // This shows that upgrading such an object results in correct behavior next 449 // time SSA applier 450 // Expect final object to have unioned keys from both entries 451 Name: "csa-combine-with-ssa-additional-keys", 452 CSAManagers: []string{"kubectl-client-side-apply"}, 453 SSAManager: "kubectl", 454 OriginalObject: []byte(` 455 apiVersion: v1 456 data: {} 457 kind: ConfigMap 458 metadata: 459 resourceVersion: "1" 460 annotations: 461 kubectl.kubernetes.io/last-applied-configuration: | 462 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 463 creationTimestamp: "2022-08-22T23:08:23Z" 464 managedFields: 465 - apiVersion: v1 466 fieldsType: FieldsV1 467 fieldsV1: 468 f:data: 469 .: {} 470 f:key: {} 471 f:metadata: 472 f:annotations: 473 .: {} 474 f:kubectl.kubernetes.io/last-applied-configuration: {} 475 manager: kubectl 476 operation: Apply 477 time: "2022-08-23T23:08:23Z" 478 - apiVersion: v1 479 fieldsType: FieldsV1 480 fieldsV1: 481 f:data: 482 .: {} 483 f:key: {} 484 f:legacy: {} 485 f:metadata: 486 f:annotations: 487 .: {} 488 f:kubectl.kubernetes.io/last-applied-configuration: {} 489 manager: kubectl-client-side-apply 490 operation: Update 491 time: "2022-08-22T23:08:23Z" 492 name: test 493 namespace: default 494 `), 495 ExpectedObject: []byte(` 496 apiVersion: v1 497 data: {} 498 kind: ConfigMap 499 metadata: 500 resourceVersion: "1" 501 annotations: 502 kubectl.kubernetes.io/last-applied-configuration: | 503 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 504 creationTimestamp: "2022-08-22T23:08:23Z" 505 managedFields: 506 - apiVersion: v1 507 fieldsType: FieldsV1 508 fieldsV1: 509 f:data: 510 .: {} 511 f:key: {} 512 f:legacy: {} 513 f:metadata: 514 f:annotations: 515 .: {} 516 f:kubectl.kubernetes.io/last-applied-configuration: {} 517 manager: kubectl 518 operation: Apply 519 time: "2022-08-23T23:08:23Z" 520 name: test 521 namespace: default 522 `), 523 }, 524 { 525 // Case when there are multiple CSA versions on the object which do not 526 // match the version from the apply entry. Shows they are tossed away 527 // without being merged. 528 Name: "csa-no-applicable-version", 529 CSAManagers: []string{"kubectl-client-side-apply"}, 530 SSAManager: "kubectl", 531 OriginalObject: []byte(` 532 apiVersion: v1 533 data: {} 534 kind: ConfigMap 535 metadata: 536 resourceVersion: "1" 537 annotations: 538 kubectl.kubernetes.io/last-applied-configuration: | 539 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 540 creationTimestamp: "2022-08-22T23:08:23Z" 541 managedFields: 542 - apiVersion: v5 543 fieldsType: FieldsV1 544 fieldsV1: 545 f:data: 546 .: {} 547 f:key: {} 548 f:legacy: {} 549 f:metadata: 550 f:annotations: 551 .: {} 552 f:kubectl.kubernetes.io/last-applied-configuration: {} 553 manager: kubectl 554 operation: Apply 555 time: "2022-08-23T23:08:23Z" 556 - apiVersion: v1 557 fieldsType: FieldsV1 558 fieldsV1: 559 f:data: 560 f:key2: {} 561 f:metadata: 562 f:annotations: 563 f:hello2: {} 564 manager: kubectl-client-side-apply 565 operation: Update 566 time: "2022-08-22T23:08:23Z" 567 - apiVersion: v2 568 fieldsType: FieldsV1 569 fieldsV1: 570 f:data: 571 f:key3: {} 572 f:metadata: 573 f:annotations: 574 f:hello3: {} 575 manager: kubectl-client-side-apply 576 operation: Update 577 time: "2022-08-22T23:08:23Z" 578 - apiVersion: v3 579 fieldsType: FieldsV1 580 fieldsV1: 581 f:data: 582 f:key4: {} 583 f:metadata: 584 f:annotations: 585 f:hello3: {} 586 manager: kubectl-client-side-apply 587 operation: Update 588 time: "2022-08-22T23:08:23Z" 589 - apiVersion: v4 590 fieldsType: FieldsV1 591 fieldsV1: 592 f:data: 593 f:key5: {} 594 f:metadata: 595 f:annotations: 596 f:hello4: {} 597 manager: kubectl-client-side-apply 598 operation: Update 599 time: "2022-08-22T23:08:23Z" 600 name: test 601 namespace: default 602 `), 603 ExpectedObject: []byte(` 604 apiVersion: v1 605 data: {} 606 kind: ConfigMap 607 metadata: 608 resourceVersion: "1" 609 annotations: 610 kubectl.kubernetes.io/last-applied-configuration: | 611 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 612 creationTimestamp: "2022-08-22T23:08:23Z" 613 managedFields: 614 - apiVersion: v5 615 fieldsType: FieldsV1 616 fieldsV1: 617 f:data: 618 .: {} 619 f:key: {} 620 f:legacy: {} 621 f:metadata: 622 f:annotations: 623 .: {} 624 f:kubectl.kubernetes.io/last-applied-configuration: {} 625 manager: kubectl 626 operation: Apply 627 time: "2022-08-23T23:08:23Z" 628 name: test 629 namespace: default 630 `), 631 }, 632 { 633 // Case when there are multiple CSA versions on the object which do not 634 // match the version from the apply entry, and one which does. 635 // Shows that CSA entry with matching version is unioned into the SSA entry. 636 Name: "csa-single-applicable-version", 637 CSAManagers: []string{"kubectl-client-side-apply"}, 638 SSAManager: "kubectl", 639 OriginalObject: []byte(` 640 apiVersion: v1 641 data: {} 642 kind: ConfigMap 643 metadata: 644 resourceVersion: "1" 645 annotations: 646 kubectl.kubernetes.io/last-applied-configuration: | 647 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 648 creationTimestamp: "2022-08-22T23:08:23Z" 649 managedFields: 650 - apiVersion: v5 651 fieldsType: FieldsV1 652 fieldsV1: 653 f:data: 654 .: {} 655 f:key: {} 656 f:legacy: {} 657 f:metadata: 658 f:annotations: 659 .: {} 660 f:kubectl.kubernetes.io/last-applied-configuration: {} 661 manager: kubectl 662 operation: Apply 663 time: "2022-08-23T23:08:23Z" 664 - apiVersion: v5 665 fieldsType: FieldsV1 666 fieldsV1: 667 f:data: 668 f:key2: {} 669 f:metadata: 670 f:annotations: 671 f:hello2: {} 672 manager: kubectl-client-side-apply 673 operation: Update 674 time: "2022-08-22T23:08:23Z" 675 - apiVersion: v2 676 fieldsType: FieldsV1 677 fieldsV1: 678 f:data: 679 f:key3: {} 680 f:metadata: 681 f:annotations: 682 f:hello3: {} 683 manager: kubectl-client-side-apply 684 operation: Update 685 time: "2022-08-22T23:08:23Z" 686 - apiVersion: v3 687 fieldsType: FieldsV1 688 fieldsV1: 689 f:data: 690 f:key4: {} 691 f:metadata: 692 f:annotations: 693 f:hello4: {} 694 manager: kubectl-client-side-apply 695 operation: Update 696 time: "2022-08-22T23:08:23Z" 697 - apiVersion: v4 698 fieldsType: FieldsV1 699 fieldsV1: 700 f:data: 701 f:key5: {} 702 f:metadata: 703 f:annotations: 704 f:hello5: {} 705 manager: kubectl-client-side-apply 706 operation: Update 707 time: "2022-08-22T23:08:23Z" 708 name: test 709 namespace: default 710 `), 711 ExpectedObject: []byte(` 712 apiVersion: v1 713 data: {} 714 kind: ConfigMap 715 metadata: 716 resourceVersion: "1" 717 annotations: 718 kubectl.kubernetes.io/last-applied-configuration: | 719 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 720 creationTimestamp: "2022-08-22T23:08:23Z" 721 managedFields: 722 - apiVersion: v5 723 fieldsType: FieldsV1 724 fieldsV1: 725 f:data: 726 .: {} 727 f:key: {} 728 f:key2: {} 729 f:legacy: {} 730 f:metadata: 731 f:annotations: 732 .: {} 733 f:hello2: {} 734 f:kubectl.kubernetes.io/last-applied-configuration: {} 735 manager: kubectl 736 operation: Apply 737 time: "2022-08-23T23:08:23Z" 738 name: test 739 namespace: default 740 `), 741 }, 742 { 743 // Do nothing to object with nothing to migrate and no existing SSA manager 744 Name: "noop", 745 CSAManagers: []string{"kubectl-client-side-apply"}, 746 SSAManager: "not-already-in-object", 747 OriginalObject: []byte(` 748 apiVersion: v1 749 data: {} 750 kind: ConfigMap 751 metadata: 752 resourceVersion: "1" 753 annotations: 754 kubectl.kubernetes.io/last-applied-configuration: | 755 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 756 creationTimestamp: "2022-08-22T23:08:23Z" 757 managedFields: 758 - apiVersion: v5 759 fieldsType: FieldsV1 760 fieldsV1: 761 f:data: 762 .: {} 763 f:key: {} 764 f:legacy: {} 765 f:metadata: 766 f:annotations: 767 .: {} 768 f:kubectl.kubernetes.io/last-applied-configuration: {} 769 manager: kubectl 770 operation: Apply 771 time: "2022-08-23T23:08:23Z" 772 name: test 773 namespace: default 774 `), 775 ExpectedObject: []byte(` 776 apiVersion: v1 777 data: {} 778 kind: ConfigMap 779 metadata: 780 resourceVersion: "1" 781 annotations: 782 kubectl.kubernetes.io/last-applied-configuration: | 783 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 784 creationTimestamp: "2022-08-22T23:08:23Z" 785 managedFields: 786 - apiVersion: v5 787 fieldsType: FieldsV1 788 fieldsV1: 789 f:data: 790 .: {} 791 f:key: {} 792 f:legacy: {} 793 f:metadata: 794 f:annotations: 795 .: {} 796 f:kubectl.kubernetes.io/last-applied-configuration: {} 797 manager: kubectl 798 operation: Apply 799 time: "2022-08-23T23:08:23Z" 800 name: test 801 namespace: default 802 `), 803 }, 804 { 805 // Expect multiple targets to be merged into existing ssa manager 806 Name: "multipleTargetsExisting", 807 CSAManagers: []string{"kube-scheduler", "kubectl-client-side-apply"}, 808 SSAManager: "kubectl", 809 OriginalObject: []byte(` 810 apiVersion: v1 811 data: {} 812 kind: ConfigMap 813 metadata: 814 resourceVersion: "1" 815 annotations: 816 kubectl.kubernetes.io/last-applied-configuration: | 817 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 818 creationTimestamp: "2022-08-22T23:08:23Z" 819 managedFields: 820 - apiVersion: v1 821 fieldsType: FieldsV1 822 fieldsV1: 823 f:metadata: 824 f:labels: 825 f:name: {} 826 f:spec: 827 f:containers: 828 k:{"name":"kubernetes-pause"}: 829 .: {} 830 f:image: {} 831 f:name: {} 832 manager: kubectl 833 operation: Apply 834 - apiVersion: v1 835 fieldsType: FieldsV1 836 fieldsV1: 837 f:status: 838 f:conditions: 839 .: {} 840 k:{"type":"PodScheduled"}: 841 .: {} 842 f:lastProbeTime: {} 843 f:lastTransitionTime: {} 844 f:message: {} 845 f:reason: {} 846 f:status: {} 847 f:type: {} 848 manager: kube-scheduler 849 operation: Update 850 time: "2022-11-03T23:22:40Z" 851 - apiVersion: v1 852 fieldsType: FieldsV1 853 fieldsV1: 854 f:metadata: 855 f:annotations: 856 .: {} 857 f:kubectl.kubernetes.io/last-applied-configuration: {} 858 f:labels: 859 .: {} 860 f:name: {} 861 f:spec: 862 f:containers: 863 k:{"name":"kubernetes-pause"}: 864 .: {} 865 f:image: {} 866 f:imagePullPolicy: {} 867 f:name: {} 868 f:resources: {} 869 f:terminationMessagePath: {} 870 f:terminationMessagePolicy: {} 871 f:dnsPolicy: {} 872 f:enableServiceLinks: {} 873 f:restartPolicy: {} 874 f:schedulerName: {} 875 f:securityContext: {} 876 f:terminationGracePeriodSeconds: {} 877 manager: kubectl-client-side-apply 878 operation: Update 879 time: "2022-11-03T23:22:40Z" 880 name: test 881 namespace: default 882 `), 883 ExpectedObject: []byte(` 884 apiVersion: v1 885 data: {} 886 kind: ConfigMap 887 metadata: 888 resourceVersion: "1" 889 annotations: 890 kubectl.kubernetes.io/last-applied-configuration: | 891 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 892 creationTimestamp: "2022-08-22T23:08:23Z" 893 managedFields: 894 - apiVersion: v1 895 fieldsType: FieldsV1 896 fieldsV1: 897 f:status: 898 f:conditions: 899 .: {} 900 k:{"type":"PodScheduled"}: 901 .: {} 902 f:lastProbeTime: {} 903 f:lastTransitionTime: {} 904 f:message: {} 905 f:reason: {} 906 f:status: {} 907 f:type: {} 908 f:metadata: 909 f:annotations: 910 .: {} 911 f:kubectl.kubernetes.io/last-applied-configuration: {} 912 f:labels: 913 .: {} 914 f:name: {} 915 f:spec: 916 f:containers: 917 k:{"name":"kubernetes-pause"}: 918 .: {} 919 f:image: {} 920 f:imagePullPolicy: {} 921 f:name: {} 922 f:resources: {} 923 f:terminationMessagePath: {} 924 f:terminationMessagePolicy: {} 925 f:dnsPolicy: {} 926 f:enableServiceLinks: {} 927 f:restartPolicy: {} 928 f:schedulerName: {} 929 f:securityContext: {} 930 f:terminationGracePeriodSeconds: {} 931 manager: kubectl 932 operation: Apply 933 name: test 934 namespace: default 935 `), 936 }, 937 { 938 // Expect multiple targets to be merged into a new ssa manager 939 Name: "multipleTargetsNewInsertion", 940 CSAManagers: []string{"kubectl-client-side-apply", "kube-scheduler"}, 941 SSAManager: "newly-inserted-manager", 942 OriginalObject: []byte(` 943 apiVersion: v1 944 data: {} 945 kind: ConfigMap 946 metadata: 947 resourceVersion: "1" 948 annotations: 949 kubectl.kubernetes.io/last-applied-configuration: | 950 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 951 creationTimestamp: "2022-08-22T23:08:23Z" 952 managedFields: 953 - apiVersion: v1 954 fieldsType: FieldsV1 955 fieldsV1: 956 f:metadata: 957 f:labels: 958 f:name: {} 959 f:spec: 960 f:containers: 961 k:{"name":"kubernetes-pause"}: 962 .: {} 963 f:image: {} 964 f:name: {} 965 manager: kubectl 966 operation: Apply 967 - apiVersion: v1 968 fieldsType: FieldsV1 969 fieldsV1: 970 f:status: 971 f:conditions: 972 .: {} 973 k:{"type":"PodScheduled"}: 974 .: {} 975 f:lastProbeTime: {} 976 f:lastTransitionTime: {} 977 f:message: {} 978 f:reason: {} 979 f:status: {} 980 f:type: {} 981 manager: kube-scheduler 982 operation: Update 983 time: "2022-11-03T23:22:40Z" 984 - apiVersion: v1 985 fieldsType: FieldsV1 986 fieldsV1: 987 f:metadata: 988 f:annotations: 989 .: {} 990 f:kubectl.kubernetes.io/last-applied-configuration: {} 991 f:labels: 992 .: {} 993 f:name: {} 994 f:spec: 995 f:containers: 996 k:{"name":"kubernetes-pause"}: 997 .: {} 998 f:image: {} 999 f:imagePullPolicy: {} 1000 f:name: {} 1001 f:resources: {} 1002 f:terminationMessagePath: {} 1003 f:terminationMessagePolicy: {} 1004 f:dnsPolicy: {} 1005 f:enableServiceLinks: {} 1006 f:restartPolicy: {} 1007 f:schedulerName: {} 1008 f:securityContext: {} 1009 f:terminationGracePeriodSeconds: {} 1010 manager: kubectl-client-side-apply 1011 operation: Update 1012 time: "2022-11-03T23:22:40Z" 1013 name: test 1014 namespace: default 1015 `), 1016 ExpectedObject: []byte(` 1017 apiVersion: v1 1018 data: {} 1019 kind: ConfigMap 1020 metadata: 1021 resourceVersion: "1" 1022 annotations: 1023 kubectl.kubernetes.io/last-applied-configuration: | 1024 {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}} 1025 creationTimestamp: "2022-08-22T23:08:23Z" 1026 managedFields: 1027 - apiVersion: v1 1028 fieldsType: FieldsV1 1029 fieldsV1: 1030 f:metadata: 1031 f:labels: 1032 f:name: {} 1033 f:spec: 1034 f:containers: 1035 k:{"name":"kubernetes-pause"}: 1036 .: {} 1037 f:image: {} 1038 f:name: {} 1039 manager: kubectl 1040 operation: Apply 1041 - apiVersion: v1 1042 fieldsType: FieldsV1 1043 fieldsV1: 1044 f:metadata: 1045 f:annotations: 1046 .: {} 1047 f:kubectl.kubernetes.io/last-applied-configuration: {} 1048 f:labels: 1049 .: {} 1050 f:name: {} 1051 f:spec: 1052 f:containers: 1053 k:{"name":"kubernetes-pause"}: 1054 .: {} 1055 f:image: {} 1056 f:imagePullPolicy: {} 1057 f:name: {} 1058 f:resources: {} 1059 f:terminationMessagePath: {} 1060 f:terminationMessagePolicy: {} 1061 f:dnsPolicy: {} 1062 f:enableServiceLinks: {} 1063 f:restartPolicy: {} 1064 f:schedulerName: {} 1065 f:securityContext: {} 1066 f:terminationGracePeriodSeconds: {} 1067 f:status: 1068 f:conditions: 1069 .: {} 1070 k:{"type":"PodScheduled"}: 1071 .: {} 1072 f:lastProbeTime: {} 1073 f:lastTransitionTime: {} 1074 f:message: {} 1075 f:reason: {} 1076 f:status: {} 1077 f:type: {} 1078 manager: newly-inserted-manager 1079 operation: Apply 1080 time: "2022-11-03T23:22:40Z" 1081 name: test 1082 namespace: default 1083 `), 1084 }, 1085 { 1086 // Expect multiple targets to be merged into a new ssa manager 1087 Name: "subresource", 1088 CSAManagers: []string{"kube-controller-manager"}, 1089 SSAManager: "kube-controller-manager", 1090 Options: []csaupgrade.Option{csaupgrade.Subresource("status")}, 1091 OriginalObject: []byte(` 1092 apiVersion: v1 1093 kind: PersistentVolumeClaim 1094 metadata: 1095 annotations: 1096 pv.kubernetes.io/bind-completed: "yes" 1097 pv.kubernetes.io/bound-by-controller: "yes" 1098 volume.beta.kubernetes.io/storage-provisioner: openshift-storage.cephfs.csi.ceph.com 1099 volume.kubernetes.io/storage-provisioner: openshift-storage.cephfs.csi.ceph.com 1100 creationTimestamp: "2024-02-24T15:24:31Z" 1101 finalizers: 1102 - kubernetes.io/pvc-protection 1103 managedFields: 1104 - apiVersion: v1 1105 fieldsType: FieldsV1 1106 fieldsV1: 1107 f:spec: 1108 f:accessModes: {} 1109 f:resources: 1110 f:requests: 1111 .: {} 1112 f:storage: {} 1113 f:storageClassName: {} 1114 f:volumeMode: {} 1115 manager: Mozilla 1116 operation: Update 1117 time: "2024-02-24T15:24:31Z" 1118 - apiVersion: v1 1119 fieldsType: FieldsV1 1120 fieldsV1: 1121 f:metadata: 1122 f:annotations: 1123 .: {} 1124 f:pv.kubernetes.io/bind-completed: {} 1125 f:pv.kubernetes.io/bound-by-controller: {} 1126 f:volume.beta.kubernetes.io/storage-provisioner: {} 1127 f:volume.kubernetes.io/storage-provisioner: {} 1128 f:spec: 1129 f:volumeName: {} 1130 manager: kube-controller-manager 1131 operation: Update 1132 time: "2024-02-24T15:24:32Z" 1133 - apiVersion: v1 1134 fieldsType: FieldsV1 1135 fieldsV1: 1136 f:status: 1137 f:accessModes: {} 1138 f:capacity: 1139 .: {} 1140 f:storage: {} 1141 f:phase: {} 1142 manager: kube-controller-manager 1143 operation: Update 1144 subresource: status 1145 time: "2024-02-24T15:24:32Z" 1146 name: test 1147 namespace: default 1148 resourceVersion: "948647140" 1149 uid: f0692a61-0ffe-4fd5-b00f-0b95f3654fb9 1150 spec: 1151 accessModes: 1152 - ReadWriteOnce 1153 resources: 1154 requests: 1155 storage: 1Gi 1156 storageClassName: ocs-storagecluster-cephfs 1157 volumeMode: Filesystem 1158 volumeName: pvc-f0692a61-0ffe-4fd5-b00f-0b95f3654fb9 1159 status: 1160 accessModes: 1161 - ReadWriteOnce 1162 capacity: 1163 storage: 1Gi 1164 phase: Bound 1165 `), 1166 ExpectedObject: []byte(` 1167 apiVersion: v1 1168 kind: PersistentVolumeClaim 1169 metadata: 1170 annotations: 1171 pv.kubernetes.io/bind-completed: "yes" 1172 pv.kubernetes.io/bound-by-controller: "yes" 1173 volume.beta.kubernetes.io/storage-provisioner: openshift-storage.cephfs.csi.ceph.com 1174 volume.kubernetes.io/storage-provisioner: openshift-storage.cephfs.csi.ceph.com 1175 creationTimestamp: "2024-02-24T15:24:31Z" 1176 finalizers: 1177 - kubernetes.io/pvc-protection 1178 managedFields: 1179 - apiVersion: v1 1180 fieldsType: FieldsV1 1181 fieldsV1: 1182 f:spec: 1183 f:accessModes: {} 1184 f:resources: 1185 f:requests: 1186 .: {} 1187 f:storage: {} 1188 f:storageClassName: {} 1189 f:volumeMode: {} 1190 manager: Mozilla 1191 operation: Update 1192 time: "2024-02-24T15:24:31Z" 1193 - apiVersion: v1 1194 fieldsType: FieldsV1 1195 fieldsV1: 1196 f:metadata: 1197 f:annotations: 1198 .: {} 1199 f:pv.kubernetes.io/bind-completed: {} 1200 f:pv.kubernetes.io/bound-by-controller: {} 1201 f:volume.beta.kubernetes.io/storage-provisioner: {} 1202 f:volume.kubernetes.io/storage-provisioner: {} 1203 f:spec: 1204 f:volumeName: {} 1205 manager: kube-controller-manager 1206 operation: Update 1207 time: "2024-02-24T15:24:32Z" 1208 - apiVersion: v1 1209 fieldsType: FieldsV1 1210 fieldsV1: 1211 f:status: 1212 f:accessModes: {} 1213 f:capacity: 1214 .: {} 1215 f:storage: {} 1216 f:phase: {} 1217 manager: kube-controller-manager 1218 operation: Apply 1219 subresource: status 1220 time: "2024-02-24T15:24:32Z" 1221 name: test 1222 namespace: default 1223 resourceVersion: "948647140" 1224 uid: f0692a61-0ffe-4fd5-b00f-0b95f3654fb9 1225 spec: 1226 accessModes: 1227 - ReadWriteOnce 1228 resources: 1229 requests: 1230 storage: 1Gi 1231 storageClassName: ocs-storagecluster-cephfs 1232 volumeMode: Filesystem 1233 volumeName: pvc-f0692a61-0ffe-4fd5-b00f-0b95f3654fb9 1234 status: 1235 accessModes: 1236 - ReadWriteOnce 1237 capacity: 1238 storage: 1Gi 1239 phase: Bound 1240 `), 1241 }, 1242 } 1243 1244 for _, testCase := range cases { 1245 t.Run(testCase.Name, func(t *testing.T) { 1246 initialObject := unstructured.Unstructured{} 1247 err := yaml.Unmarshal(testCase.OriginalObject, &initialObject.Object) 1248 if err != nil { 1249 t.Fatal(err) 1250 } 1251 1252 upgraded := initialObject.DeepCopy() 1253 err = csaupgrade.UpgradeManagedFields( 1254 upgraded, 1255 sets.New(testCase.CSAManagers...), 1256 testCase.SSAManager, 1257 testCase.Options..., 1258 ) 1259 1260 if err != nil { 1261 t.Fatal(err) 1262 } 1263 1264 expectedObject := unstructured.Unstructured{} 1265 err = yaml.Unmarshal(testCase.ExpectedObject, &expectedObject.Object) 1266 if err != nil { 1267 t.Fatal(err) 1268 } 1269 1270 if !reflect.DeepEqual(&expectedObject, upgraded) { 1271 t.Fatal(cmp.Diff(&expectedObject, upgraded)) 1272 } 1273 1274 // Show that the UpgradeManagedFieldsPatch yields a patch that does 1275 // nothing more and nothing less than make the object equal to output 1276 // of UpgradeManagedFields 1277 1278 initialCopy := initialObject.DeepCopyObject() 1279 patchBytes, err := csaupgrade.UpgradeManagedFieldsPatch( 1280 initialCopy, sets.New(testCase.CSAManagers...), testCase.SSAManager, testCase.Options...) 1281 1282 if err != nil { 1283 t.Fatal(err) 1284 } else if patchBytes != nil { 1285 patch, err := jsonpatch.DecodePatch(patchBytes) 1286 if err != nil { 1287 t.Fatal(err) 1288 } 1289 1290 initialJSON, err := json.Marshal(initialObject.Object) 1291 if err != nil { 1292 t.Fatal(err) 1293 } 1294 1295 patchedBytes, err := patch.Apply(initialJSON) 1296 if err != nil { 1297 t.Fatal(err) 1298 } 1299 1300 var patched unstructured.Unstructured 1301 if err := json.Unmarshal(patchedBytes, &patched.Object); err != nil { 1302 t.Fatal(err) 1303 } 1304 1305 if !reflect.DeepEqual(&patched, upgraded) { 1306 t.Fatalf("expected patch to produce an upgraded object: %v", cmp.Diff(patched, upgraded)) 1307 } 1308 } 1309 }) 1310 } 1311 }